Question

Following Google's advice here and using their code as a guide. I've tried using their encrypted hyperlocal string to test the decryption method that I wrote (most of it from Google though as my own attempts at writing such a method caused me much headache).

I'm getting Signature Mismatch for some reason even though I'm providing the proper integrity and encryption keys and I'm using the byte array that they provided in their example decryption (lower down Google's RTB decrypt hyperlocal page).

I have the code here:

package anon.bidder.adx;

import java.io.ByteArrayOutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Hex;
import org.apache.log4j.Logger;

public class AdxBidRequestDecryptor {

    private static final int INITIALIZATION_VECTOR_SIZE = 16;
    private static final int SIGNATURE_SIZE = 4;
    private static final int BLOCK_SIZE = 20;

    public static class DecrypterException extends Exception {
          public DecrypterException(String message) {
            super(message);
          }
        }

    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                                 + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }

    public static void main(String args[]){
        byte[] ciphertext = hexStringToByteArray("E2014EA201246E6F6E636520736F7572636501414243C0ADF6B9B6AC17DA218FB50331EDB376701309CAAA01246E6F6E636520736F7572636501414243C09ED4ECF2DB7143A9341FDEFD125D96844E25C3C202466E6F6E636520736F7572636502414243517C16BAFADCFAB841DE3A8C617B2F20A1FB7F9EA3A3600256D68151C093C793B0116DB3D0B8BE9709304134EC9235A026844F276797");
        byte[] encryptionKey = {(byte)0x02, (byte)0xEE, (byte)0xa8, (byte)0x3c, (byte)0x6c, (byte)0x12, (byte)0x11, (byte)0xe1, (byte)0x0b,
                (byte) 0x9f, (byte) 0x88, (byte) 0x96, (byte) 0x6c, (byte) 0xee, (byte) 0xc3, (byte) 0x49, (byte) 0x08, (byte) 0xeb, (byte) 0x94, (byte) 0x6f, (byte) 0x7e,
                (byte) 0xd6, (byte) 0xe4, (byte) 0x41, (byte) 0xaf, (byte) 0x42, (byte) 0xb3, (byte) 0xc0, (byte) 0xf3, (byte) 0x21, (byte) 0x81, (byte) 0x40};
        byte[] integrityKey = {(byte) 0xbf, (byte) 0xFF, (byte) 0xec, (byte) 0x55, (byte) (byte) 0xc3, (byte) 0x01, (byte) 0x30, (byte) 0xc1, (byte) 0xd8,
                (byte) 0xcd, (byte) 0x18, (byte) 0x62, (byte) 0xed, (byte) 0x2a, (byte) 0x4c, (byte) 0xd2, (byte) 0xc7, (byte) 0x6a, (byte) 0xc3, (byte) 0x3b, (byte) 0xc0,
                (byte) 0xc4, (byte) 0xce, (byte) 0x8a, (byte) 0x3d, (byte) 0x3b, (byte) 0xbd, (byte) 0x3a, (byte) 0xd5, (byte) 0x68, (byte) 0x77, (byte) 0x92};
        try {
            byte[] plain = decrypt(ciphertext, new SecretKeySpec(encryptionKey,"HmacSHA1"),new SecretKeySpec(integrityKey,"HmacSHA1"));
        } catch (DecrypterException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static byte[] decrypt(byte[] ciphertext,
            SecretKey encryptionKey,
            SecretKey integrityKey)
                    throws DecrypterException {
        try {
            // Step 1. find the length of initialization vector and clear text.
            final int plaintext_length =
                    ciphertext.length - INITIALIZATION_VECTOR_SIZE - SIGNATURE_SIZE;
            if (plaintext_length < 0) {
                throw new RuntimeException("The plain text length can't be negative.");
            }

            byte[] iv = Arrays.copyOf(ciphertext, INITIALIZATION_VECTOR_SIZE);

            // Step 2. recover clear text
            final Mac hmacer = Mac.getInstance("HmacSHA1");
            final int ciphertext_end = INITIALIZATION_VECTOR_SIZE + plaintext_length;
            final byte[] plaintext = new byte[plaintext_length];
            boolean add_iv_counter_byte = true;
            for (int ciphertext_begin = INITIALIZATION_VECTOR_SIZE, plaintext_begin = 0;
                    ciphertext_begin < ciphertext_end;) {
                hmacer.reset();
                hmacer.init(encryptionKey);
                final byte[] pad = hmacer.doFinal(iv);

                int i = 0;
                while (i < BLOCK_SIZE && ciphertext_begin != ciphertext_end) {
                    plaintext[plaintext_begin++] =
                            (byte)(ciphertext[ciphertext_begin++] ^ pad[i++]);
                }

                if (!add_iv_counter_byte) {
                    final int index = iv.length - 1;
                    add_iv_counter_byte = ++iv[index] == 0;
                }

                if (add_iv_counter_byte) {
                    add_iv_counter_byte = false;
                    iv = Arrays.copyOf(iv, iv.length + 1);
                }
            }

            // Step 3. Compute integrity hash. The input to the HMAC is clear_text
            // followed by initialization vector, which is stored in the 1st section
            // or ciphertext.
            hmacer.reset();
            hmacer.init(integrityKey);
            hmacer.update(plaintext);
            hmacer.update(Arrays.copyOf(ciphertext, INITIALIZATION_VECTOR_SIZE));
            final byte[] computedSignature = Arrays.copyOf(hmacer.doFinal(), SIGNATURE_SIZE);
            final byte[] signature = Arrays.copyOfRange(
                    ciphertext, ciphertext_end, ciphertext_end + SIGNATURE_SIZE);
            if (!Arrays.equals(signature, computedSignature)) {
                throw new DecrypterException("Signature mismatch.");
            }
            return plaintext;
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("HmacSHA1 not supported.", e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException("Key is invalid for this purpose.", e);
        }
    }
}
Was it helpful?

Solution

The example in the documentation appears to be broken. The test encrypted data embedded in the example archive at the end of java/Decrypter.java conveniently uses the same encryption and integrity keys, and provides us with known encrypted and plaintext data (all bytes are 0xfe).

When I patch your test main with the archive data it passes the signature test and your plaintext result is correct. When I patch Google's program with the data from the documentation page, it fails to match the signature. I believe your code is working but your test data is bad.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top