13

First thing first. Some time ago I needed a simple AES encryption in Android to encrypt a password and send it as a parameter for a .net web service where the password was decrypted.

The following is my Android encryption:

    private static String Encrypt(String text, String key)
        throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] keyBytes= new byte[16];
        byte[] b= key.getBytes("UTF-8");
        int len= b.length;
        if (len > keyBytes.length) len = keyBytes.length;
        System.arraycopy(b, 0, keyBytes, 0, len);
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(keyBytes);
        cipher.init(Cipher.ENCRYPT_MODE,keySpec,ivSpec);

        byte[] results = cipher.doFinal(text.getBytes("UTF-8"));
        String result = Base64.encodeBytes(results);
        return result;
        }

And then I decrypted it in C# with:

        public static string Decrypt(string textToDecrypt, string key)
    {
        System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();

        RijndaelManaged rijndaelCipher = new RijndaelManaged();
        rijndaelCipher.Mode = CipherMode.CBC;
        rijndaelCipher.Padding = PaddingMode.PKCS7;

        rijndaelCipher.KeySize = 0x80;
        rijndaelCipher.BlockSize = 0x80;

        string decodedUrl = HttpUtility.UrlDecode(textToDecrypt);
        byte[] encryptedData = Convert.FromBase64String(decodedUrl);
        byte[] pwdBytes = Encoding.UTF8.GetBytes(key);
        byte[] keyBytes = new byte[0x10];
        int len = pwdBytes.Length;
        if (len > keyBytes.Length)
        {
            len = keyBytes.Length;
        }
        Array.Copy(pwdBytes, keyBytes, len);
        rijndaelCipher.Key = keyBytes;
        rijndaelCipher.IV = keyBytes;
        byte[] plainText = rijndaelCipher.CreateDecryptor().TransformFinalBlock(encryptedData, 0, encryptedData.Length);
        return encoding.GetString(plainText);
    }

This worked like a charm, but the problems came when I tried to do the same in iOS. I am pretty new developing applications for the iphone/ipad, so ofcause I googled it, and almost every code sample provided was the following:

- (NSData *)AESEncryptionWithKey:(NSString *)key {
char keyPtr[kCCKeySizeAES128]; // room for terminator (unused)
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

// fetch key data
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

NSUInteger dataLength = [self length];

size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);

size_t numBytesEncrypted = 0;

CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                      keyPtr, kCCKeySizeAES128,
                                      NULL /* initialization vector (optional) */,
                                      [self bytes], [self length], /* input */
                                      buffer, bufferSize, /* output */
                                      &numBytesEncrypted);
if (cryptStatus == kCCSuccess) {
    //the returned NSData takes ownership of the buffer and will free it on deallocation
    return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
}

free(buffer); //free the buffer;
return nil;

}

Maybe I was a little bit too optimistic, when I was hoping for a smooth transition here, because when the Android is throwing me something like:

"EgQVKvCLS4VKLoR0xEGexA=="

then the iOS gives me:

"yP42c9gajUra7n0zSEuVJQ=="

Hopefully it is just something I forgot, or some of the settings are wrong?

[UPDATE] The results are now showed after the base64 encoding.

Giru Bhai
  • 14,370
  • 5
  • 46
  • 74
MBX
  • 828
  • 2
  • 10
  • 20
  • 1
    The android version is a URL encoded base64 string which is equal to `"YHH+gTxyIxvAx1cPFLcP0IEW2HcVHQVi9X11656CFsk="` `(60 71 fe 81 3c 72 23 1b c0 c7 57 0f 14 b7 0f d0 81 16 d8 77 15 1d 05 62 f5 7d 75 eb 9e 82 16 c9)`. – Joe Aug 14 '12 at 13:17
  • Woops, sorry I forgot to mention that. I am encoding the result from the iOS-version as well, just in another method, but the results are different before the encoding. – MBX Aug 14 '12 at 13:21
  • Updated the question to show the results after the base64 encoding. The same password and the same key is used. – MBX Aug 14 '12 at 13:45
  • Have you verified that the bytes before the encoding are equal? For iOS add `NSLog("%@", self);`; – Joe Aug 14 '12 at 14:16
  • Have a look at this post: http://stackoverflow.com/questions/17535918/aes-gets-different-results-in-ios-and-java/19219704#19219704 – A.S. Oct 07 '13 at 09:37
  • @ Morten Engell : Even I have same requirement. Can you please help me for this. I have an app running on iOS and Android. This app connects to the backend written in .net. I want to send the encrypted data over Internet which will be decrypted on receiver's end. Please help me to get it done. – Shane Jun 10 '14 at 15:49

1 Answers1

5

First note is that you have significant security issues in this code. You're taking a string password and just dropping that into a key. If that string human-typable, then you've dramatically constricted your keyspace (turning AES-128 into more like AES-40 or AES-50, maybe even worse). You need to salt and stretch the key using PBKDF2. See Properly encrypting with AES with CommonCrypto for a fuller discussion.

You also have a significant security problem because you're using your key as your IV (see more below; this is actually the cause of your symptom). This is not the correct way to pick an IV and makes your ciphertext predictable. Identical plaintext encrypted with the same key will give the same result. This is similar to having no IV at all. The IV needs to be random. See the above link for more discussion.

Now to your actual symptom. The problem is that you are using the key as the IV in Java and C#, but you're using 0 (NULL) as the IV on iOS (the IV is not optional; you're just passing 0). You need to use the same IV in all cases.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • I know there are some security issues, but it is only a temporary solution. This is only ment to be a quick fix, so we are not passing readable text, until our final solution is done. I can't really test anything right now, because it is a problem at work but, if I understand it correctly, it should be fixed if I changed the following: In the C#-decryption: rijndaelCipher.IV = X; In the Java-encryption: IvParameterSpec ivSpec = new IvParameterSpec(X); ...and changed the NULL-parameter to X, where X ofcause is the same value? By the way, thank you very much for your answer... – MBX Aug 14 '12 at 16:18
  • Currently "X" in your above code is a copy of the key everywhere but iOS. You can either make it be the key on iOS, or make it 0 of C# and Java. I wouldn't make it some static value (other than 0). – Rob Napier Aug 14 '12 at 16:33
  • Okay, thank you very much for your help. I will try it tomorrow, and mark your answer as "accepted" as well... – MBX Aug 14 '12 at 17:04
  • Well, had to make som adjustments but it finally worked... Thank you very much for your help! Marked as answer... – MBX Aug 15 '12 at 11:47
  • @RobNapier I have a similar requirement now. I wont be able to have the same encrypted text for the same plain text in Android and IOS if i make the IV random .right? I need only one decryption algorithm in PHP. can you throw light on this.? – shanavascet Sep 29 '13 at 11:43
  • You should never have the same encrypted text for two encryptions of the same plain text. That's the point of a random IV, and is an important security requirement. You send the IV along with the ciphertext and it is used as part of the decryption. RNCryptor supports iOS and PHP, and JNCryptor supports the same format in Java. They will handle the IV and other metadata for you. (You can google both of them.) – Rob Napier Sep 29 '13 at 18:00