Search code examples
androidc++encryptioncrypto++javax.crypto

Android and Crypto++ AES 128bit encrypted results not matching


I am trying to use the same key and VI to encrypt and decryp the same message, say [email protected]. The key length is 128 bit as I know that in Java/Android, 256 is not easy to implement.

Here is my function to do the AES encryption using Crypto++

string encryptString(string toBeEncrypted) {
//
// Create Cipher Text
//
CryptoPP::AES::Encryption aesEncryption(key, CryptoPP::AES::DEFAULT_KEYLENGTH);
CryptoPP::CBC_Mode_ExternalCipher::Encryption cbcEncryption(aesEncryption, iv);

std::string ciphertext;

std::cout << "To be encrypted (" << toBeEncrypted.size() << " bytes)" << std::endl;
std::cout << toBeEncrypted;
std::cout << std::endl << std::endl;

CryptoPP::StreamTransformationFilter stfEncryptor(cbcEncryption, new CryptoPP::StringSink(ciphertext), CryptoPP::StreamTransformationFilter::PKCS_PADDING);
stfEncryptor.Put(reinterpret_cast<const unsigned char*> (toBeEncrypted.c_str()), toBeEncrypted.length() + 1);
stfEncryptor.MessageEnd();

}

key is "4ff539a893fed04840749287bb3e4152" and IV is "79f564e83be16711759ac7c730072bd0".

They are stored in binary in a ubuntu running in VMWare on a x86 Windows.

The function to convert key and iv from byte to hex array is:

std::string hexToStr(unsigned char *data, int len)
{
    std::stringstream ss;
    ss<<std::hex;
    for(int i(0);i<len;++i){
        ss<<std::setfill('0')<<std::setw(2)<<(int)data[i];
    }

return ss.str();
}

I checked the hex string vs the memory of byte array key and iv, and they are matching.

The results for encrypting [email protected] is c08a50b45ff16650542e290e05390a6c6fe533e11e9f802ad7d47681fd41f964 from C++.

I obtained this by passing the returned string ciphertext into the function hexToStr like cout<<TFFHelper::hexStr((unsigned char *)ciphertext.c_str(), ciphertext.length())<<endl;

I can also decrpt it with the following function, and I passed the raw string rather than the hex string into this function.

string TFFEncryption::decryptString(string toBeDecrypted) {

string decryptedtext;
CryptoPP::AES::Decryption aesDecryption(key, CryptoPP::AES::DEFAULT_KEYLENGTH);
CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv);

CryptoPP::StreamTransformationFilter stfDecryptor(cbcDecryption, new CryptoPP::StringSink(decryptedtext), CryptoPP::StreamTransformationFilter::PKCS_PADDING);
stfDecryptor.Put(reinterpret_cast<const unsigned char*> (toBeDecrypted.c_str()), toBeDecrypted.size());
stfDecryptor.MessageEnd();

return decryptedtext;
}

I put the same VI and KEY in my Android code, and try to encrypt. It ends up in a half matching results after encrypting.

Android code is as follow:

public class myAES {
private static final String key = "4ff539a893fed04840749287bb3e4152";
private static final String initVector = "79f564e83be16711759ac7c730072bd0";
private final  static char[] hexArray = "0123456789ABCDEF".toCharArray();

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 String bytesToHex(byte[] bytes) {
    char[] hexChars = new char[bytes.length * 2];
    for ( int j = 0; j < bytes.length; j++ ) {
        int v = bytes[j] & 0xFF;
        hexChars[j * 2] = hexArray[v >>> 4];
        hexChars[j * 2 + 1] = hexArray[v & 0x0F];
    }
    return new String(hexChars);
}

public static byte[] encrypt(String value) {
    try {
        IvParameterSpec iv = new IvParameterSpec(hexStringToByteArray(initVector));
        SecretKeySpec skeySpec = new SecretKeySpec(hexStringToByteArray(key), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

        byte[] encrypted = cipher.doFinal(value.getBytes());
        Log.v("Encryption successful", bytesToHex(encrypted));
        return encrypted;
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return null;
}

public static String decrypt(byte[] encrypted) {
    try {
        IvParameterSpec iv = new IvParameterSpec(hexStringToByteArray(initVector));
        SecretKeySpec skeySpec = new SecretKeySpec(hexStringToByteArray(key), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

        byte[] original = cipher.doFinal(encrypted);
        Log.v("Decryption successful", new String(original, "UTF-8"));
        return new String(original);
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return null;
}
}

I got the result of C08A50B45FF16650542E290E05390A6CFE5466FC480F0667517B248410930B69.

I used the same piece of code in Netbeans on Java8, running on the same Ubuntu of the C++ code, and got exactly the same results as what I mentioned on the previous line (Android results). I don't think this is OS dependent, but probably I did something wrong with either Java or C++ in my code.

So the first half of the hex strings are matching, and the later half is not. I tried to reduced the phrase [email protected] into [email protected], which results in complete different results from C++ vs Java (Ubuntu vs Android).

However, if I decrpt that binary array in Java, I got the original phrase [email protected] or [email protected].

I have the following questions.

  1. What did I do wrong?
  2. Is it the proper way to case const char * into unsigned char *? I think it should be OK as I am getting the hex string of the binary
  3. Is the half matching results caused by padding?

Solution

  • The email in the Crypto++ message is '0' terminated, but the message in Java is not.

    As AES is a block cipher with a block length of 128 bits (16 bytes), and your email is exactly 16 bytes long, the first block gets encrypted the same way in both implementations. The '0' in the first position of the second block gives the difference in the second block of the encryption.

    Notice the extra '00' in below screenshot using this online tool. All the '0f' following the '00' is the PKCS5 Padding that this tool did not remove here ..

    enter image description here