iOS 和 android 上的 AES 加密,输出和缓冲区大小不同

AES encryption on iOS and android, output and buffer size is different

使用 CCCrypt 函数在 iOS 上实施 AES256。但是输出和输出缓冲区长度不同于 Android。

Android 中的密码 class 生成 48 字节数据,而在 iOS 中我们得到 80 字节数据。

在IOS中使用kCCAlgorithmAES、kCCOptionPKCS7Padding,在android中使用AES/CBC/PKCS5Padding。

在 IOS 中 IV 为 NULL,在 android 中创建 iv 作为新的 16 字节数组。

请帮忙。

请查找输入和代码以供参考。

 - (void)viewDidLoad {
    [super viewDidLoad];

    NSString *message = [NSString stringWithFormat:@"com.myapp.com|355004059196637|911111111111|11341e5e-9643-4559-bbb7-34d40555e96c"];
    NSString *key = [NSString stringWithFormat:@"4f28d5901b4b7b80d33fda76ca372c2a20bd1a6c2aad7fa215dc79d507330678"];
    NSString *shaEncryptMessage = [self sha256:message length:0];
    NSData *aesEncryptData = [self aesEncrypt:[shaEncryptMessage dataUsingEncoding:NSUTF8StringEncoding] key:key iv:nil];
    NSString *hMac = [aesEncryptData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
    NSLog(@"hMac = %@",hMac);

    // IOS output : Can+oQR79D3/lsQGctzY/d2VBNZbWWtJxGI8iRIu80R2yTskn9gf2oKHaRESX73u
    //                  LpJHLx1Xr6iH11jFPlmqwW7mQz0xAW4uACNAMEoZ0kY=
    // Android output : MiMDkdo5cGsPMj2qCnNobgp7dr5KMvBhGuKTonrqr1lCYte/kKegGMtI/4TPhUNI
}


- (NSString*) sha256:(NSString *)key length:(NSInteger) length{
    const char *s=[key cStringUsingEncoding:NSASCIIStringEncoding];
    NSData *keyData=[NSData dataWithBytes:s length:strlen(s)];

    uint8_t digest[CC_SHA256_DIGEST_LENGTH]={0};
    CC_SHA256(keyData.bytes, (unsigned int)keyData.length, digest);
    NSData *out=[NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
    NSString *hash=[out description];
    hash = [hash stringByReplacingOccurrencesOfString:@" " withString:@""];
    hash = [hash stringByReplacingOccurrencesOfString:@"<" withString:@""];
    hash = [hash stringByReplacingOccurrencesOfString:@">" withString:@""];
    return  hash;
}
-  (NSData *)aesEncrypt:(NSData *)plainText key:(NSString *)key iv:(NSString *)iv {
    char keyPointer[kCCKeySizeAES256+2],// room for terminator (unused) ref: https://devforums.apple.com/message/876053#876053
    ivPointer[kCCBlockSizeAES128];
    BOOL patchNeeded;
    bzero(keyPointer, sizeof(keyPointer)); // fill with zeroes for padding
    //key = [[StringEncryption alloc] md5:key];
    key = [self stringFromHex:key];
    patchNeeded= ([key length] > kCCKeySizeAES256+1);
    if(patchNeeded)
    {
        key = [key substringToIndex:kCCKeySizeAES256]; // Ensure that the key isn't longer than what's needed (kCCKeySizeAES256)
    }

    [key getCString:keyPointer maxLength:sizeof(keyPointer) encoding:NSUTF8StringEncoding];
    [iv getCString:ivPointer maxLength:sizeof(ivPointer) encoding:NSUTF8StringEncoding];

    //    if (patchNeeded) {
    //        keyPointer[0] = '[=12=]';  // Previous iOS version than iOS7 set the first char to '[=12=]' if the key was longer than kCCKeySizeAES256
    //    }

    NSUInteger dataLength = [plainText length];

    // For block ciphers, the output size will always be less than or equal to the input size plus the size of one block.
    size_t buffSize = dataLength + kCCBlockSizeAES128;
    void *buff = malloc(buffSize);

    size_t numBytesEncrypted = 0;



    CCCryptorStatus status = CCCrypt(kCCEncrypt, /* kCCEncrypt, etc. */
                                     kCCAlgorithmAES128, /* kCCAlgorithmAES128, etc. */
                                     kCCOptionPKCS7Padding, /* kCCOptionPKCS7Padding, etc. */
                                     keyPointer, kCCKeySizeAES256, /* key and its length */
                                     NULL, /* initialization vector - use random IV everytime */
                                     [plainText bytes], [plainText length], /* input  */
                                     buff, buffSize,/* data RETURNED here */
                                     &numBytesEncrypted);


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

    free(buff);
    return nil;
}

- (NSString *) stringFromHex:(NSString *)str
{
    NSMutableData *stringData = [[NSMutableData alloc] init];
    unsigned char whole_byte;
    char byte_chars[3] = {'[=12=]','[=12=]','[=12=]'};
    int i;
    for (i=0; i < [str length] / 2; i++) {
        byte_chars[0] = [str characterAtIndex:i*2];
        byte_chars[1] = [str characterAtIndex:i*2+1];
        whole_byte = strtol(byte_chars, NULL, 16);
        [stringData appendBytes:&whole_byte length:1];
    }
    return [[NSString alloc] initWithData:stringData encoding:NSASCIIStringEncoding];
}

请同时查找 android 代码,

   protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    generateHMAC();
}

String K0 = "4f28d5901b4b7b80d33fda76ca372c2a20bd1a6c2aad7fa215dc79d507330678";
String generatedString = "com.myapp.com|355004059196637|911111111111|11341e5e-9643-4559-bbb7-34d40555e96c";

private void generateHMAC() {
    Log.d("Message of Hash", generatedString);
    byte[] var14 = new byte[0];
    try {
        var14 = SHA256(generatedString);
        byte[] var15 = new byte[0];
        var15 = encrypt(var14, hexStringToByteArray(K0));
        String var4 = Base64.encodeToString(var15, 2);
        Log.d("Existing K0", K0);
        Log.d("HMAC", var4);
    } catch (Exception e) {
        e.printStackTrace();
    }
}


public byte[] SHA256(String paramString) throws Exception {
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    md.update(paramString.getBytes("UTF-8"));
    byte[] digest = md.digest();
    return digest;
}

public byte[] encrypt(byte[] var1, byte[] var2) throws Exception {
    SecretKeySpec var3 = new SecretKeySpec(var2, "AES");
    byte[] var4 = new byte[16];
    IvParameterSpec var5 = new IvParameterSpec(var4);
    Cipher var6 = Cipher.getInstance("AES/CBC/PKCS5Padding");
    var6.init(1, var3, var5);
    byte[] var7 = var6.doFinal(var1);
    return var7;
}

public byte[] hexStringToByteArray(String var1) {
    byte[] var2 = new byte[var1.length() / 2];

    for (int var3 = 0; var3 < var2.length; ++var3) {
        int var4 = var3 * 2;
        int var5 = Integer.parseInt(var1.substring(var4, var4 + 2), 16);
        var2[var3] = (byte) var5;
    }

    return var2;
}

在您提供 iOS 代码后更新:

  • aesEncryptData 应该是您的输出。摆脱与 AES 加密无关的 hmac(相反,它是为了消息完整性)。
  • 匹配 Android 代码的唯一方法是使用与 Android 代码相同的 IV。

之前的回复:

输入多长时间?提供源代码和示例数据,可以帮助我们更快的解决问题。

如果没有要求的信息,我无法得到您的答案,但我有一些建议可能会帮助您查明真相:

  • 你的填充没问题。 Java 中的 PKCS5Padding 是 PKCS#7 的 wrongly named 实现,因此它应该与 Apple 的 kCCOptionPKCS7Padding 兼容。
  • Apple by default uses CBC mode 如果未指定模式,则在引擎盖下,因此与 Android 代码一致。所以这也不是问题。
  • 加密时,密文将是 16 字节的倍数(因为 AES 的块大小为 N=16 字节,并且根据 PKCS #7 的定义)。具体来说:
    • 如果输入是 16 字节的倍数,那么输出应该比输入正好多 16 个字节。
    • 如果输入不是 16 字节的倍数,那么输出应该是 16*Ceiling(Input length/16)。示例:47 字节输入应为 16*Ceiling(17/16) = 16*3 = 48 字节输出。
  • 其中一种实现可能将 IV 作为密文的一部分输出。如果发生这种情况,它应该在密文的开头。您应该能够测试是否发生这种情况。 (如果发生这种情况请告诉我)

话虽如此,有些事情很奇怪并且可能实施错误,我们需要代码来查明它。 Android 代码产生 3 个块,每块 16,而 Apple 代码产生 5 个块,每块 16,这是没有意义的。

此外,正如我在上面评论的那样,即使 Apple 告诉您 IV 是可选的,它们的意思是就让代码工作而言它是可选的。是not optional for security。 IV 是必需的,并且对于 CBC 操作模式来说必须是不可预测的,并且它们永远不应重复。如果忽略这一点,就会泄露有关数据的信息,并且在某些情况下,攻击者可能能够解密数据(填充 oracle 攻击)。