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

5
使用CCCrypt函数在iOS中实现AES256。但是输出和输出缓冲区长度与Android不同。
Android中的Cipher类产生48字节数据,而在iOS中我们得到80字节数据。
在iOS中使用kCCAlgorithmAES,kCCOptionPKCS7Padding,在Android中使用AES/CBC/PKCS5Padding。
在iOS中IV为NULL,在Android中创建一个新的16字节数组作为iv。
请帮忙查看。
以下是参考代码和输入内容。
 - (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] = '\0';  // Previous iOS version than iOS7 set the first char to '\0' 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] = {'\0','\0','\0'};
    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;
}

请尝试使用https://github.com/alexeypro/EncryptDecrypt 进行AES256EncryptWithKey加密。 - MAhipal Singh
请问您能否发布代码? - TheGreatContini
虽然这并没有回答你的问题,但你应该知道,尽管苹果告诉你初始化向量(IV)是可选的,但如果你不指定它并以不可预测的方式选择IV,则加密是不安全的。对于这种糟糕的加密API,苹果真是丢脸。 - TheGreatContini
1个回答

3

您提供iOS代码后的更新:

  • aesEncryptData应该是您的输出。删除hmac,因为它与AES加密无关(相反,它用于消息完整性)。
  • 如果您正在使用与Android代码相同的IV,那么您唯一能够匹配Android代码的方式是这样做。

早期回复:

输入有多长?提供源代码和示例数据可能会帮助我们更快地解决问题。

在没有请求的信息的情况下,我没有你的答案,但我有一些指针,可能会帮助您找到问题的根源:

  • 您的填充方式没问题。Java中的PKCS5Padding是PKCS#7的错误命名实现,因此它应与Apple的kCCOptionPKCS7Padding兼容。
  • 如果未指定模式,苹果默认使用CBC模式,因此与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块,这毫无道理。

同时,正如我之前所评论的那样,尽管苹果告诉你IV是可选的,但这只是指在代码能够运行方面它是可选的。在安全方面,IV是不可或缺的。在CBC模式下,IV是必需的并且必须是不可预测的,永远不应该重复使用。如果您忽略了这一点,就会泄露有关您的数据的信息,并且在某些情况下,攻击者可能能够解密数据(填充垃圾邮件攻击)。


感谢@TheGreatContini,为您提供iOS代码和示例输入以供参考。请帮忙解决这个问题。 - Pradip
@Pradip,在AES之后你使用了HMAC-sha256,这是完全不同的算法。移除HMAC,你只需要加密而不需要其他的操作。为了匹配Android,你需要使用和Android相同的IV。你不能简单地替换为nil并期望数据匹配,因为它不会匹配。 - TheGreatContini
请查找更新后的Android代码,其加密逻辑为SHA256后接AES,输出结果再进行base64编码(符合我们所需的逻辑)。我希望iOS中也有类似的加密逻辑。 - Pradip
是的,问题已经解决了。在iOS中,我将SHA输出转换为字符串,并将该字符串传递给AES后,再使用NSUTF8StringEncoding将其转换回NSData,这是错误的。通过直接传递字节到方法中,问题得以解决。 - Pradip

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