AES加密在Android和iOS上针对长度大于15字节的消息会产生不同的结果

5

我在理解两个设备上的密码器/加密器方面遇到了真正的问题。

1. 如果我们使用Cipher AES在iOS和Android上加密一条消息,并且字符串的字符长度不超过16(例如“abcdefghijklmno”),则使用相同的密钥/密码加密后,我们会得到相同的结果。

2. 但是,如果我们使用一个更长的消息,在iOS和Android上得到的结果将不同(例如“abcdefghijklmnop”)。

我进行了大量的研究以找到两个设备上相同的参数,起初我认为这是安全的。

这是我的加密Cipher代码:

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
                    //EDITED AFTER RIGHT ANSWER FROM
                    //*** Cipher cipher = Cipher.getInstance("AES");   ***//
                    // TO  
        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 "";
}


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;
}

这是我同事的iOS配套设备:

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

    // 'key' should be 32 bytes for AES256,
    // 16 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES128 + 1]; // 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;
}

我很想了解它们的区别以及如何避免出现这种情况。通过测试长度超过15个字符的字符串,我获得了一些线索,但我不知道为什么会这样 :)
提前感谢!

2
检查两个系统使用的填充方式。不同的填充方式会导致不同的输出结果。不要依赖默认值,而是在两端明确设置填充方式。你的第二段代码明确设置了PKCS7填充方式。在两端都使用它。 - rossum
哦,谢谢,那请把它作为一个答案吧:Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); - A.S.
3
你的密钥派生函数非常脆弱,密钥很可能会被暴力破解。你应该使用适当的密钥派生函数,例如PBKDF2或bcrypt。此外,你也不应该使用静态IV,因为这会破坏CBC模式的某些安全性质。 - ntoskrnl
为什么你不应该使用固定的IV:http://crypto.stackexchange.com/q/5094 - Christian Strempfer
1个回答

8

请检查两个系统上使用的填充(padding)内容。不同的填充方式将导致不同的输出结果。不要依赖默认值,而是在两侧明确设置填充(padding)。你的第二段代码明确设置了PKCS7填充(PKCS7 padding),请在两端都使用它。

通常情况下,请不要依赖不同系统之间的默认值。明确设置 IV、模式、填充、随机数(nonce)或其他所需内容。如果即使最细微的细节不匹配,加密就可能会严重失败。


谢谢,这个解决了问题:Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding") - A.S.
请注意,非Android Java将其称为“PKCS5Padding”,而不是“PKCS7Padding”。 - ntoskrnl
是的,如果您使用PKCS7Padding,速度会慢得多。在JellyBean及更高版本中,使用AES/CBC/PKCS5Padding会快得多。 - kroot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接