4

I'm a complete newbie to this kind of encryption things but I have a Java app and an iOS, and I want them to both be able to ecrypt a text to a same result. I use AES. I found these codes, with a little modification of course, but they return different result

iOS Code:

- (NSData *)AESEncryptionWithKey:(NSString *)key {    
    unsigned char keyPtr[kCCKeySizeAES128] = { 'T', 'h', 'e', 'B', 'e', 's', 't', 'S', 'e', 'c', 'r','e', 't', 'K', 'e', 'y' };
    size_t bufferSize = 16;
    void *buffer = malloc(bufferSize);
    size_t numBytesEncrypted = 0;
    const char iv2[16] = {  65, 1, 2, 23, 4, 5, 6, 7, 32, 21, 10, 11, 12, 13, 84, 45 };
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmAES128,
                                          kCCOptionECBMode | kCCOptionPKCS7Padding,,
                                          keyPtr,
                                          kCCKeySizeAES128,
                                          iv2,
                                          @"kayvan",
                                          6,
                                          dataInLength,
                                          buffer,
                                          bufferSize,
                                          &numBytesEncrypted);


    if (cryptStatus == kCCSuccess) {
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }

    free(buffer);
    return nil;
}

and the Java code is:

public static void main(String[] args) throws Exception {
    String password = "kayvan";
    String key = "TheBestSecretKey";
    String newPasswordEnc = AESencrp.newEncrypt(password, key);
    System.out.println("Encrypted Text : " + newPasswordEnc);
}

and in another java class (AESencrp.class) I have:

public static final byte[] IV = { 65, 1, 2, 23, 4, 5, 6, 7, 32, 21, 10, 11, 12, 13, 84, 45 };
public static String newEncrypt(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 = 16; 
    System.arraycopy(b, 0, keyBytes, 0, len);
    SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
    IvParameterSpec ivSpec = new IvParameterSpec(IV);
    System.out.println(ivSpec);
    cipher.init(Cipher.ENCRYPT_MODE,keySpec,ivSpec);
    byte[] results = cipher.doFinal(text.getBytes("UTF-8"));
    String result = DatatypeConverter.printBase64Binary(results);
    return result;
}

The string I wanted to encrypt is kayvan with the key TheBestSecretKey. and the results after Base64 encoding are:

for iOS: 9wXUiV+ChoLHmF6KraVtDQ==

for Java: /s5YyKb3tDlUXt7pqA5OFA==

What should I do now?

0xKayvan
  • 368
  • 1
  • 4
  • 18
  • There's some sort of mismatch between how you're configuring the encryption between the two implementations. As you've no doubt seen, there are a lot of configuration parameters for CCCrypt on iOS. I'm not familiar with Java's Cipher, so you may need to read the documentation for both and make sure you're using the same key size, algorithm, padding... Also, in your Java code above I don't see where you're setting the string you want to encrypt. Is it possible you omitted that step and are encrypting an empty string? – Nicholas Hart Jul 08 '13 at 21:23
  • yes, there is a lot of parameters and I'm so confused with them. I have to read them again. But about your final question, the string i want to encrypt is set in main method in java. I've edited the question and added this part. – 0xKayvan Jul 08 '13 at 21:31
  • Again, I'm not certain about Java Cipher... but it looks like you're using different padding in Objective C. ie: kCCOptionPKCS7Padding vs AES/CBC/PKCS5Padding. (Since you're using a block cipher the algorithm needs to know how to pad the last block, if there isn't enough data to fill the whole block.) However, if I recall correctly PKCS5 and PKCS7 are basically the same algorithm, so I'm not sure that's the problem--you might try changing one or the other implementation to make sure they use the same padding. – Nicholas Hart Jul 08 '13 at 22:50
  • 1
    Also, it looks like you're using ECB in iOS and CBC in Java. Another problem. – Nicholas Hart Jul 08 '13 at 22:51
  • Why do you need the encrypted data to be equal? Probably you'll make your code less secure without reason. In most use cases only decrypted data must be equal. See http://crypto.stackexchange.com/q/5094 – Christian Strempfer Aug 07 '14 at 06:38

5 Answers5

8

I made a gist with iOS/Android/Node.js AES256 same result encoding, https://gist.github.com/m1entus/f70d4d1465b90d9ee024

Michal Zaborowski
  • 5,039
  • 36
  • 35
  • Hello, maybe you can add a reference to the posts you copied them from. I was also looking at the code you have inside your gist. It looks like you have a little mistake at line 14 for iOS. Maybe you also have to change the byte size at line 23 and 75 for the java version, looks to me like it must be changed from 16 bytes to 32. In iOS you get an enum with the value of 32 for AES256. – Alex Cio May 27 '14 at 10:39
  • Sure i will add it, maybe it is a little mistake but it works fine, so probably we will not change it because we already implemented some part of system, but thanks for your remarks. – Michal Zaborowski May 27 '14 at 10:52
  • It might get a problem with longer strings you will encrypt. just keep in mind – Alex Cio May 27 '14 at 10:54
  • I not sure if all of these ciphers doing IV padding automatically to 32bit, that's why it is working fine – Michal Zaborowski May 27 '14 at 11:00
  • Thanks a lot, works great. Also iOS AES encryption function from CommonCrypto works great with Java code you've posted. – DarkSun Dec 10 '14 at 15:48
  • 2
    how to call it? – 9to5ios Nov 08 '16 at 11:50
5

And here is the Android Version wich is generating the String for decrypt/encrypt Messages, it uses Cipher and generates the right vector to make the same result as iOS. This is corresponding to the iOS Version of @亚历山大 here in this thread.

public class MyCrypter {

private static String TAG = "MyCrypter";

public MyCrypter() {

}

/**
 * Encodes a String in AES-128 with a given key
 * 
 * @param context
 * @param password
 * @param text
 * @return String Base64 and AES encoded String
 * @throws NoPassGivenException
 * @throws NoTextGivenException
 */
public String encode(Context context, String password, String text)
        throws NoPassGivenException, NoTextGivenException {
    if (password.length() == 0 || password == null) {
        throw new NoPassGivenException("Please give Password");
    }

    if (text.length() == 0 || text == null) {
        throw new NoTextGivenException("Please give text");
    }

    try {
        SecretKeySpec skeySpec = getKey(password);
        byte[] clearText = text.getBytes("UTF8");

        //IMPORTANT TO GET SAME RESULTS ON iOS and ANDROID
        final byte[] iv = new byte[16];
        Arrays.fill(iv, (byte) 0x00);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

        // Cipher is not thread safe
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParameterSpec);

        String encrypedValue = Base64.encodeToString(
                cipher.doFinal(clearText), Base64.DEFAULT);
        Log.d(TAG, "Encrypted: " + text + " -> " + encrypedValue);
        return encrypedValue;

    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    }
    return "";
}

/**
 * Decodes a String using AES-128 and Base64
 * 
 * @param context
 * @param password
 * @param text
 * @return desoded String
 * @throws NoPassGivenException
 * @throws NoTextGivenException
 */
public String decode(Context context, String password, String text)
        throws NoPassGivenException, NoTextGivenException {

    if (password.length() == 0 || password == null) {
        throw new NoPassGivenException("Please give Password");
    }

    if (text.length() == 0 || text == null) {
        throw new NoTextGivenException("Please give text");
    }

    try {
        SecretKey key = getKey(password);

        //IMPORTANT TO GET SAME RESULTS ON iOS and ANDROID
        final byte[] iv = new byte[16];
        Arrays.fill(iv, (byte) 0x00);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

        byte[] encrypedPwdBytes = Base64.decode(text, Base64.DEFAULT);
        // cipher is not thread safe
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
        cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
        byte[] decrypedValueBytes = (cipher.doFinal(encrypedPwdBytes));

        String decrypedValue = new String(decrypedValueBytes);
        Log.d(TAG, "Decrypted: " + text + " -> " + decrypedValue);
        return decrypedValue;

    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    }
    return "";
}

/**
 * Generates a SecretKeySpec for given password
 * @param password
 * @return SecretKeySpec
 * @throws UnsupportedEncodingException
 */
public SecretKeySpec getKey(String password)
        throws UnsupportedEncodingException {


    int keyLength = 128;
    byte[] keyBytes = new byte[keyLength / 8];
    // explicitly fill with zeros
    Arrays.fill(keyBytes, (byte) 0x0);

    // if password is shorter then key length, it will be zero-padded
    // to key length
    byte[] passwordBytes = password.getBytes("UTF-8");
    int length = passwordBytes.length < keyBytes.length ? passwordBytes.length
            : keyBytes.length;
    System.arraycopy(passwordBytes, 0, keyBytes, 0, length);
    SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
    return key;
}

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

public class NoPassGivenException extends Exception {
    public NoPassGivenException(String message) {
        super(message);
    }
}
}
A.S.
  • 4,574
  • 3
  • 26
  • 43
  • I got an error saying that `ECB mode cannot use IV`. What should I do now? is there any problem defining cipher algorithm? – 0xKayvan Oct 17 '13 at 21:33
  • updated my cipher.getInstance(...) please have a look – A.S. Oct 31 '13 at 12:42
  • Correct answer for getting the same encrypted data, but that's almost never necessary. So please don't use a fixed IV in production code. http://crypto.stackexchange.com/a/5095 – Christian Strempfer Aug 07 '14 at 07:01
1

A friend of mine and me created an iOS and Android app which can crypt messages. To use it, you should create an extension of NSData with following code Snippet from this website:

- (NSData *)AES128EncryptWithKey:(NSString *)key {

    // 'key' should be 32 bytes for AES256,
    // 16 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES128 + [key length]]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // insert key in char array
    [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;

    // the encryption method, use always same attributes in android and iPhone (f.e. PKCS7Padding)
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmAES128,
                                          kCCOptionPKCS7Padding,
                                          keyPtr,
                                          kCCKeySizeAES128,
                                          NULL                      /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize,       /* output */
                                          &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {

        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }

    free(buffer);
    return nil;
}

- (NSData *)AES128DecryptWithKey:(NSString *)key {
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES128 + [key length]]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // insert key in char array
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesDecrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          kCCAlgorithmAES128,
                                          kCCOptionPKCS7Padding,
                                          keyPtr,
                                          kCCKeySizeAES128,
                                          NULL                      /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize,       /* output */
                                          &numBytesDecrypted);

    if (cryptStatus == kCCSuccess) {

        return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    }

    free(buffer);
    return nil;
}

+ (NSData *)base64DataFromString: (NSString *)string
{
    unsigned long ixtext, lentext;
    unsigned char ch, inbuf[4], outbuf[3];
    short i, ixinbuf;
    Boolean flignore, flendtext = false;
    const unsigned char *tempcstring;
    NSMutableData *theData;

    if (string == nil){
        return [NSData data];
    }

    ixtext = 0;
    tempcstring = (const unsigned char *)[string UTF8String];
    lentext = [string length];
    theData = [NSMutableData dataWithCapacity: lentext];
    ixinbuf = 0;

    while (true){
        if (ixtext >= lentext){
            break;
        }

        ch = tempcstring [ixtext++];
        flignore = false;

        if ((ch >= 'A') && (ch <= 'Z')){
            ch = ch - 'A';
        } else if ((ch >= 'a') && (ch <= 'z')){
            ch = ch - 'a' + 26;
        } else if ((ch >= '0') && (ch <= '9')){
            ch = ch - '0' + 52;
        } else if (ch == '+'){
            ch = 62;
        } else if (ch == '=') {
            flendtext = true;
        } else if (ch == '/') {
            ch = 63;
        } else {
            flignore = true;
        }

        if (!flignore){
            short ctcharsinbuf = 3;
            Boolean flbreak = false;

            if (flendtext){
                if (ixinbuf == 0){
                    break;
                }

                if ((ixinbuf == 1) || (ixinbuf == 2)) {
                    ctcharsinbuf = 1;
                } else {
                    ctcharsinbuf = 2;
                }

                ixinbuf = 3;
                flbreak = true;
            }

            inbuf [ixinbuf++] = ch;

            if (ixinbuf == 4){
                ixinbuf = 0;

                outbuf[0] = (inbuf[0] << 2) | ((inbuf[1] & 0x30) >> 4);
                outbuf[1] = ((inbuf[1] & 0x0F) << 4) | ((inbuf[2] & 0x3C) >> 2);
                outbuf[2] = ((inbuf[2] & 0x03) << 6) | (inbuf[3] & 0x3F);

                for (i = 0; i < ctcharsinbuf; i++) {
                    [theData appendBytes: &outbuf[i] length: 1];
                }
            }

            if (flbreak) {
                break;
            }
        }
    }

    return theData;
}

Then inside the class you want to use the crypt methods insert at the top:

#import "NSData+Crypt.h"

And than encrypt your strings like that:

 NSData *value = [aString dataUsingEncoding:NSUTF8StringEncoding];
 NSData *encryptedData = [value AES128EncryptWithKey:myKey];
 NSString *myString = [encryptedData base64Encoding];

And decrypt the data like this:

NSData *myData = [NSData base64DataFromString:_textView.text];
NSData *decryptedData = [myData AES128DecryptWithKey:_textField.text];
NSString *myString2 = [[NSString alloc] initWithData:decryptedData
                                            encoding:NSUTF8StringEncoding];

I used the method base64DataFromString from the website of Matt Gallagher otherwise if you use

[[NSData alloc] base64EncodedDataWithOptions:NSUTF8StringEncoding];

the method is just available on >= iOS 7.0

Alex Cio
  • 6,014
  • 5
  • 44
  • 74
1

Few important things to note while implementing AES encryption:
1. Never use plain text as encryption key. Always hash the plain text key and then use for encryption.
2. Always use Random IV (initialization vector) for encryption and decryption. True randomization is important. In the examples above, no initialization vector is set. This is a security flaw.
I recently wrote cross platform AES encryption and decryption library for C#, iOS and Android which I have posted on Github. You can see it here - https://github.com/Pakhee/Cross-platform-AES-encryption

Navneet Kumar
  • 909
  • 7
  • 6
  • The person who downvoted this answer - can you explain me the reason ? – Navneet Kumar Jun 30 '15 at 08:15
  • Random IV (initialization vector) will always be different for Android and iOS...so the encrypted result would always be different for Android and iOS..right? – Jayprakash Dubey Apr 17 '17 at 05:26
  • Because of Randomness IV has to be transmitted with encrypted data which is a common practice . Both parties have to agree that starting digits are IV and needs to be extracted first. I have seen implementations where random Salt is also used so you transmit Random salt + Random IV + Encrypted data. – Amar Deep Singh Jul 29 '22 at 18:47
0

Check this https://github.com/mataprasad/Cross-platform-AES-encryption-128bit working for iOS, Android, Java backend aligned for same result.

e.g.:

password: "abc@123"
Key: "12DA321*2X33%@52"
AES Encryption: "3E95waIL9bw07q4ErjJDSw=="
Hardik Darji
  • 3,633
  • 1
  • 30
  • 30