AES gets different results in iOS (Obj-C) and Android (Java)

前端 未结 4 1880
小蘑菇
小蘑菇 2020-11-27 18:31

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 fo

相关标签:
4条回答
  • 2020-11-27 19:06

    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

    0 讨论(0)
  • 2020-11-27 19:27

    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

    0 讨论(0)
  • 2020-11-27 19:30

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

    0 讨论(0)
  • 2020-11-27 19:33

    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);
        }
    }
    }
    
    0 讨论(0)
提交回复
热议问题