1

I am trying to implement AES128 algorithm on Android, and I have referenced this link for a basic AES implementation (http://java.sun.com/developer/technicalArticles/Security/AES/AES_v1.html).

The problem is,for my project the key is predefined, and it is 36 bytes, not 16/24/32 bytes. So I always got a "key length not 128/194/256 bits" exception. I try the solution from iphone sdk(see this link: The iOS encryption framework) and it works even when I pass a 36 byte predefined key. As I can not find the implementation details for the BlockCipher.c/CommonCryptor.c released by Apple, Can any body help me figure out how they select 16 bytes from 36 bytes?

Thanks.

-----------------------------------update Sep 13th------------------------------------ In order to avoid confusion I provide some sample and my progress. I change some data that is confidential, but the length and format remain the same. And for saving time I only reveal the core functions. No comments for the code as I think the code is self-explained enough.

the iOS sample:

NSString * _key = @"some 36 byte key";
StringEncryption *crypto = [[[StringEncryption alloc] init] autorelease]; 
NSData *_inputData = [inputString dataUsingEncoding:NSUTF8StringEncoding]; 
CCOptions padding = kCCOptionPKCS7Padding;
NSData *encryptedData = [crypto encrypt:_inputData key:[_key dataUsingEncoding:NSUTF8StringEncoding] padding:&padding];
NSString *encryptedString = [encryptedData base64EncodingWithLineLength:0];
return encryptedString;

the [crypto encrypt] implementation is exactly the same as the link I mentioned above. It calls the doCipher in encryption mode. The core functions includes CCCryptorCreate, CCCryptorUpdate and CCCryptorFinal, which are from . The CCCryptorCreate deals with the key length. It passes the raw key bytes, and pass an integer 16 (kCCKeySizeAES128) as the key size and do the trick. The call hierarchy is like CCCryptorCreate/CommonCryptor.c => ccBlockCipherCallouts->CCBlockCipherInit/BlockCipher.c => ccAlgInfo->setkey/BlockCipher.c . setkey is actually a pointer to a function, for AES it points to aes_cc_set_key. And I can not find the aes_cc_set_key implementation, got lost here.

----------------------------------------Update Sep 13th ----------------------------- I change the _key in iOS sample code, manually taking the first 16 byte as the new key, other parts remain the same, and it is working!!! Up to this point I solve the key length problem.

But the Android version outputs different from the iOS version for some long plain text, like 30 or 40 bytes. my java implementation is like below:

String key = "some 16 byte key";
byte[] keyBytes = key.getBytes("UTF-8");
byte[] plainBytes = plainText.getBytes("UTF-8");
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(plainBytes);
String result = Base64.encodeBytes(encrypted);
return result;

Base64 is from org.apache.commons.codec.binary.Base64. What is the problem? or any hints on c/c++ libraries that can do the same thing? I can import it into android as well.

David
  • 33
  • 1
  • 6
  • Does your encryption need to produce the same results as the implementation on some other system? If yes, then you'll need more information about that information so you can reduce your key to 16 bytes. (And did the iPhone produce the same result as the reference implementation?) If not, then you can reduce the key yourself, e.g. by XORing the first 16 bytes with the next 16 bytes and then with the last 4 bytes (expanded to 16 bytes with 0s). – Codo Sep 12 '11 at 10:43
  • The truth is, there is iOS version for the app, and I need to implement the android version based on the iOS version. And Yes, my implementation need to produce the same result as the iOS implementation. As it is a client-server structure, someone else implement the server part, including the decryption part, and provide me the interface. The iOS version works perfect, and I have tested that the server return HeaderDecryption Error unless the android version outputs the same cipher text as the iOS outputs for specified plain text. I will update the question to make it more detailed. – David Sep 13 '11 at 14:26
  • Have a look at this Question and my and the iOS answer: http://stackoverflow.com/questions/17535918/aes-gets-different-results-in-ios-and-java/19219704#19219704 – A.S. Oct 07 '13 at 09:48

4 Answers4

5

The remaining difference (provided that you only used the first 16 bytes of the key) is the cipher streaming mode. The iOS code uses CBC mode with an initialization set to all zeros. The Android code however uses ECB.

So the correct Java/Android code is:

// convert key to bytes
byte[] keyBytes = key.getBytes("UTF-8");
// Use the first 16 bytes (or even less if key is shorter)
byte[] keyBytes16 = new byte[16];
System.arraycopy(keyBytes, 0, keyBytes16, 0, Math.min(keyBytes.length, 16));

// convert plain text to bytes
byte[] plainBytes = plainText.getBytes("UTF-8");

// setup cipher
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes16, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16]; // initialization vector with all 0
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));

// encrypt
byte[] encrypted = cipher.doFinal(plainBytes);

I have tested it with about 100 bytes of data and got exactly the same result on iOS and in Java.

Codo
  • 75,595
  • 17
  • 168
  • 206
  • Exactly!!!!! As I explore the iOS source code I find the same fact. And based on Rossum`s suggestions I notice my mode is not correct. And today I am reading JEC document and considering how to modify it. This answer is definitely what I want, and I test it to connect the server, it is working!!! Thank you Codo!!!!! – David Sep 13 '11 at 21:37
1

There is no such thing as a 36-byte (288 bits) AES key. AES 256 would use a 32 byte key, so maybe that is what you have, with some additional header/trailer bytes. Where did you get this key from? What is the format? The Apple implementation may be throwing away the unneeded bytes, or it already knows about that special format you are using.

Nikolay Elenkov
  • 52,576
  • 10
  • 84
  • 84
  • I understand that acceptable key length for AES is only 128/192/256, and the algorithm is actually AES128. I got the key from the team working on the server part(the decryption part actually), and the server part is implemented in C#. The formats for both plain text and key are UTF-8 bytes. And Yes the Apple implementation must have done something on the extra bytes, for this question I just want to know how they deal with the extra bytes. I will update the question to make it more detailed. – David Sep 13 '11 at 14:34
1

Is the 36 bytes actually a passphrase? If so, then it is likely that the key being used is SHA-256(passphrase) or SHA-512(passphrase).

ETA:
Re your update. I note that your code is using ECB mode. That is insecure. It may well be that Apple is using CBC mode, hence you difficulty in decrypting longer (more than 16 bytes) messages. Try changing the mode to CBC and using 16 more bytes of your mysterious input as the IV. Looking quickly at the Apple code for CommonCryptor.c, they appear to be using PKCS7 padding, so you should use that as well.

rossum
  • 15,344
  • 1
  • 24
  • 38
  • No. It is just password. I will update the question to reveal more details. – David Sep 13 '11 at 14:38
  • You need to look at the detailed documentation for the Apple side to determine exactly what it is doing to get 16 bytes from 36 bytes. – rossum Sep 13 '11 at 15:28
0

In case you want to apply base64 encoding for transporting over the network this is the right code:

public String encryptString(String string, String key)
{
    byte[] aesData;
    String base64="";

    try 
    {
        aesData = encrypt(key, string.getBytes("UTF8"));
        base64 = Base64.encodeToString(aesData, Base64.DEFAULT);
    } 
    catch (Exception e) 
    {
        e.printStackTrace();
    }       

    return base64;
}

public String decryptString(String string, String key)
{
    byte[] debase64 = null;
    String result="";

    try 
    {
        debase64=Base64.decode(string, Base64.DEFAULT);
        byte[] aesDecrypted = decrypt(key, debase64);;

        result = new String(aesDecrypted, "UTF8");
    } 
    catch (Exception e) 
    {
        e.printStackTrace();
    }       

    return result;
}

private byte[] decrypt(String k, byte[] plainBytes) throws Exception 
{
    // convert key to bytes
    byte[] keyBytes = k.getBytes("UTF-8");
    // Use the first 16 bytes (or even less if key is shorter)
    byte[] keyBytes16 = new byte[16];
    System.arraycopy(keyBytes, 0, keyBytes16, 0, Math.min(keyBytes.length, 16));

    // setup cipher
    SecretKeySpec skeySpec = new SecretKeySpec(keyBytes16, "AES");
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    byte[] iv = new byte[16]; // initialization vector with all 0
    cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(iv));

    // encrypt
    byte[] encrypted = cipher.doFinal(plainBytes);

    return encrypted;
}

private byte[] encrypt(String k, byte[] plainBytes) throws Exception 
{
    // convert key to bytes
    byte[] keyBytes = k.getBytes("UTF-8");
    // Use the first 16 bytes (or even less if key is shorter)
    byte[] keyBytes16 = new byte[16];
    System.arraycopy(keyBytes, 0, keyBytes16, 0, Math.min(keyBytes.length, 16));

    // setup cipher
    SecretKeySpec skeySpec = new SecretKeySpec(keyBytes16, "AES");
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    byte[] iv = new byte[16]; // initialization vector with all 0
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));

    // encrypt
    byte[] encrypted = cipher.doFinal(plainBytes);

    return encrypted;
}
Catalin
  • 1,821
  • 4
  • 26
  • 32