RNCryptor AES256匹配PHP MCRYPT_RIJNDAEL_256

时间:2022-09-28 18:33:52

The PHP API I’m calling from within my iOS app requires the payload to be encrypted in a certain customised way. I’m having troubles replicating that approach in Objective-C, with RNCryptor.

我在iOS应用程序中调用的PHP API要求有效负载以某种定制方式加密。我在Objective-C和RNCryptor中很难复制这种方法。

Here is the PHP code used to encrypt a string:

下面是用于加密字符串的PHP代码:

function encrypt($string) {
    $key = 'some-random-key';
    return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, md5(md5($key))));
}

And this how I’m trying to achieve the same encryption result in Objective-C:

这就是我如何在Objective-C中实现同样的加密结果

+ (NSData*)encryptData:(NSData*)sourceData {

    NSString *keyString = @"some-random-key";
    NSData *key = [[keyString MD5String] dataUsingEncoding:NSUTF8StringEncoding];
    NSData *iv = [[[keyString MD5String] MD5String] dataUsingEncoding:NSUTF8StringEncoding];

    NSMutableData *encryptedData = [NSMutableData data];

    RNCryptorEngine *cryptor = [[RNCryptorEngine alloc] initWithOperation:kCCEncrypt settings:kRNCryptorAES256Settings key:key IV:iv error:nil];

    [encryptedData appendData:[cryptor addData:sourceData error:nil]];
    [encryptedData appendData:[cryptor finishWithError:nil]];

    return encryptedData;

}

But the results from the two functions never match. E.g., for the same one-word string, the PHP code returns J39gRcuBEaqMIPP1VlizdA8tRjmyAB6za4zG5wcOB/8=, while in Objective-C (after running base64EncodedStringWithOptions: on the resulting NSData) I’m getting 1FGpZpVm2p4z3BBY6KW2fw==.

但这两个函数的结果并不匹配。例如,对于相同的一个字串,PHP代码返回j39grcubeaqmipp1vlizda8trjab6z4zg5wcob /8=,而在Objective-C(运行base64EncodedStringWithOptions:在生成的NSData上),我得到的是1fgpzpvm2p4z3bbyb = 62fbyw =。

Is there something I need to further tweak in the RNCryptor settings to make it work?

我是否需要在RNCryptor设置中进行进一步的调整以使其工作?

UPDATE

更新

I’ve played around the the native iOS CommonCrypto framework directly, without using the third party RNCryptor lib altogether. I’m consisently getting the same result as with RNCryptor though. I even tried implementing AES128 in both my Objective-C and PHP snippets, but even that never made the results from the two environments match…

我直接使用了本地的iOS CommonCrypto框架,没有使用第三方RNCryptor lib。不过,我认为RNCryptor的结果是一样的。我甚至尝试在我的Objective-C和PHP代码片段中实现AES128,但即便如此,这两种环境的结果也无法匹配……

UPDATE 2

更新2

The MD5String method I‘m using is a category on NSString and is defined as follows:

我使用的MD5String方法是NSString上的一个类别,定义如下:

- (NSString *)MD5String {
    const char *cstr = [self UTF8String];
    unsigned char result[16];
    CC_MD5(cstr, strlen(cstr), result);

    return [[NSString stringWithFormat:
            @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
            result[0], result[1], result[2], result[3],
            result[4], result[5], result[6], result[7],
            result[8], result[9], result[10], result[11],
            result[12], result[13], result[14], result[15]
            ] lowercaseString];
}

4 个解决方案

#1


4  

Although most of the answers focus on the MD5 hash, what's most likely to cause issues is the fact that MCRYPT_RIJNDAEL_256 is not AES. It does not specify a key size of 256 but a block size of 256, while AES always has a block size of 128 bits. As for the other parameters, print out their values in hexadecimal right before the encryption routine in both sides to find out their values.

虽然大多数答案都集中在MD5散列上,但最可能导致问题的是MCRYPT_RIJNDAEL_256不是AES。它没有指定256的键大小,而是指定256的块大小,而AES的块大小总是128位。对于其他参数,在两边的加密例程之前,在十六进制中打印出它们的值,以找出它们的值。

#2


4  

I realize I'm late to the party here and you've probably already moved on. But I want to point out that there's now a full PHP port of RNCryptor that is bytestream compatible with the Objective-C implementation. I myself contributed it last summer, and I am currently maintaining it. :-)

我意识到我迟到了,你可能已经离开了。但是我想指出的是,现在有一个完整的RNCryptor端口,它是与Objective-C实现兼容的bytestream。我自己在去年夏天捐了它,现在我正在维护它。:-)

So it should be quite easy now to encrypt with RNCryptor PHP and decrypt with RNCryptor Objective-C, or vice-versa. Check out the main RNCryptor project for details and a list of sub-projects.

因此,现在使用RNCryptor PHP加密和使用RNCryptor Objective-C进行解密是很容易的,反之亦然。查看主RNCryptor项目的详细信息和子项目列表。

#3


1  

It is uncommon to return an md5 as a hex-ascii in other than scripting languages, even php provides a binary output option.

除了脚本语言之外,以十六进制ascii形式返回md5是不常见的,甚至php也提供二进制输出选项。

There are a few n on-standard things about the php mcrypt_encrypt:

关于php mcrypt_encrypt,有几个标准的东西:

  1. Padding the key with \0 if it is smaller than the required key size.
  2. 用\0填充键,如果它小于所需的键大小。
  3. The data will be padded with \0 characters to a multiple of block size, generally a padding such as pkcs7 is used.
  4. 数据将用\0字符填充到块大小的倍数中,通常使用pkcs7之类的填充。
  5. Not specified is how an iv that is not a block size is handled, one could guess that is is also padded with trailing \0 characters.
  6. 未指定的是如何处理非块大小的iv,您可以猜测它也是用拖尾\0字符填充的。

Of these the iv will be the correct length due to the hex-ascii out put of the md5 method. But the that leaves the data length and the padding which is non-standard.

由于md5方法的十六进制ascii输出,iv将是正确的长度。但是它留下了数据长度和非标准的填充。

The key will be 32 bytes so will be padded with 32 \0 characters in php. This begs the question of using AES256 with a 128 bit key.

密钥将为32字节,因此将在php中添加32个\0字符。这就引出了使用AES256和128位密钥的问题。

From the underlying CommonCrypto "CommonCryptor.h":

从下面的CommonCrypto“CommonCryptor.h”:

keyLength: Length of key material. Must be appropriate for the selected operation and algorithm.

键长:关键材料的长度。必须适合所选的操作和算法。

The key length is not correct.

键的长度不正确。

Things to do:
1. Handle the data length/padding
2. Handle the key length

事情要做:1。处理数据长度/填充2。处理密钥长度

For further help please provide sample data you are using and the hex-ascii out put of mcrypt_encrypt prior to base64 encoding.

为了进一步的帮助,请提供您正在使用的示例数据和在base64编码之前的mcrypt_encrypt的hex-ascii输出。

For reference see:
mcrypt-encrypt.php and md5.php

看到供参考:mcrypt-encrypt。php和md5.php

#4


0  

Zaph’s note, that

Zaph的注意

The data will be padded with \0 characters to a multiple of block size

数据将用\0字符填充到块大小的倍数中

set me on track and helped me figure out part of the problem.

让我走上正轨,帮我解决问题的一部分。

Essentially, PHP’s mcrypt only uses \0 padding for the data, while Apple’s CommonCryptor lets you choose between PKCS7 padding (kCCOptionPKCS7Padding) or no padding whatsoever. This was one of the reasons I could never get the data to match: it was always padded differently before it was about to get encrypted.

从本质上讲,PHP的mcrypt只对数据使用\0填充,而苹果的CommonCryptor允许您在PKCS7填充(kCCOptionPKCS7Padding)中选择,或者没有任何填充。这是我无法匹配数据的原因之一:在加密之前,数据总是以不同的方式填充。

The solution for this is to either make PHP perform PKCS7 padding of the data before running mcrypt (example solution) or to make Objective-C perform PHP-style \0 padding and make sure to remove kCCOptionPKCS7Padding (pass NULL to CCCrypt options):

解决方法是,让PHP在运行mcrypt(示例解决方案)之前执行PKCS7数据填充,或者让Objective-C执行PHP风格的\0填充,并确保删除kccoptionpkcs7填充(将NULL传递给CCCrypt选项):

NSMutableData *dataToEncrypt = [sourceData mutableCopy];
NSUInteger dataLength = [dataToEncrypt length];

// See how much padding is required
NSUInteger padding = kCCBlockSizeAES128 - (dataLength % kCCBlockSizeAES128);

// Add that many \0’s (there could be a more efficient way to do this)
for (int i=0; i<padding; i++)
    [dataToEncrypt appendData:[@"\0" dataUsingEncoding:NSASCIIStringEncoding]];

// Recalculate the data length
dataLength = dataToEncrypt.length;

I ended up ditching RNCryptor and working with the native CommonCryptor API directly instead, so the end result looked something like this (the encryptedBase64String method here is a category on NSString in my application, because I only ever need to encrypt strings this way; also note the kHSEncryptionKey constant representing the freeform key string):

我最终选择了抛弃RNCryptor并直接使用本地CommonCryptor API,所以最终的结果看起来是这样的(在我的应用程序中,encryptedBase64String方法是NSString的一个类别,因为我只需要用这种方式加密字符串;还要注意表示*格式密钥字符串的kHSEncryptionKey常量):

- (NSString*)encryptedBase64String {

    // Prepare the data

    NSMutableData *sourceData = [[self dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];

    // Process the key

    NSString *key = [[kHSEncryptionKey MD5String] substringWithRange:NSMakeRange(0, 16)];

    char keyPtr[kCCKeySizeAES128 + 1];
    bzero(keyPtr, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    // Process the iv

    NSString *iv = [[[kHSEncryptionKey MD5String] MD5String] substringWithRange:NSMakeRange(0, 16)];

    char ivPtr[kCCKeySizeAES128 + 1];
    bzero(ivPtr, sizeof(ivPtr));
    [iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];

    // Pad the data, PHP style

    NSUInteger dataLength = [sourceData length];
    NSUInteger padding = kCCBlockSizeAES128 - (dataLength % kCCBlockSizeAES128);

    for (int i=0; i<padding; i++)
        [sourceData appendData:[@"\0" dataUsingEncoding:NSASCIIStringEncoding]];

    dataLength = sourceData.length;

    // Buffer for the resulting data

    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void* buffer = malloc(bufferSize);

    // Run the encryption

    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, NULL,
                                          keyPtr, kCCKeySizeAES128,
                                          ivPtr,
                                          sourceData.bytes, dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesEncrypted);

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

    free(buffer);
    return nil;
}

#1


4  

Although most of the answers focus on the MD5 hash, what's most likely to cause issues is the fact that MCRYPT_RIJNDAEL_256 is not AES. It does not specify a key size of 256 but a block size of 256, while AES always has a block size of 128 bits. As for the other parameters, print out their values in hexadecimal right before the encryption routine in both sides to find out their values.

虽然大多数答案都集中在MD5散列上,但最可能导致问题的是MCRYPT_RIJNDAEL_256不是AES。它没有指定256的键大小,而是指定256的块大小,而AES的块大小总是128位。对于其他参数,在两边的加密例程之前,在十六进制中打印出它们的值,以找出它们的值。

#2


4  

I realize I'm late to the party here and you've probably already moved on. But I want to point out that there's now a full PHP port of RNCryptor that is bytestream compatible with the Objective-C implementation. I myself contributed it last summer, and I am currently maintaining it. :-)

我意识到我迟到了,你可能已经离开了。但是我想指出的是,现在有一个完整的RNCryptor端口,它是与Objective-C实现兼容的bytestream。我自己在去年夏天捐了它,现在我正在维护它。:-)

So it should be quite easy now to encrypt with RNCryptor PHP and decrypt with RNCryptor Objective-C, or vice-versa. Check out the main RNCryptor project for details and a list of sub-projects.

因此,现在使用RNCryptor PHP加密和使用RNCryptor Objective-C进行解密是很容易的,反之亦然。查看主RNCryptor项目的详细信息和子项目列表。

#3


1  

It is uncommon to return an md5 as a hex-ascii in other than scripting languages, even php provides a binary output option.

除了脚本语言之外,以十六进制ascii形式返回md5是不常见的,甚至php也提供二进制输出选项。

There are a few n on-standard things about the php mcrypt_encrypt:

关于php mcrypt_encrypt,有几个标准的东西:

  1. Padding the key with \0 if it is smaller than the required key size.
  2. 用\0填充键,如果它小于所需的键大小。
  3. The data will be padded with \0 characters to a multiple of block size, generally a padding such as pkcs7 is used.
  4. 数据将用\0字符填充到块大小的倍数中,通常使用pkcs7之类的填充。
  5. Not specified is how an iv that is not a block size is handled, one could guess that is is also padded with trailing \0 characters.
  6. 未指定的是如何处理非块大小的iv,您可以猜测它也是用拖尾\0字符填充的。

Of these the iv will be the correct length due to the hex-ascii out put of the md5 method. But the that leaves the data length and the padding which is non-standard.

由于md5方法的十六进制ascii输出,iv将是正确的长度。但是它留下了数据长度和非标准的填充。

The key will be 32 bytes so will be padded with 32 \0 characters in php. This begs the question of using AES256 with a 128 bit key.

密钥将为32字节,因此将在php中添加32个\0字符。这就引出了使用AES256和128位密钥的问题。

From the underlying CommonCrypto "CommonCryptor.h":

从下面的CommonCrypto“CommonCryptor.h”:

keyLength: Length of key material. Must be appropriate for the selected operation and algorithm.

键长:关键材料的长度。必须适合所选的操作和算法。

The key length is not correct.

键的长度不正确。

Things to do:
1. Handle the data length/padding
2. Handle the key length

事情要做:1。处理数据长度/填充2。处理密钥长度

For further help please provide sample data you are using and the hex-ascii out put of mcrypt_encrypt prior to base64 encoding.

为了进一步的帮助,请提供您正在使用的示例数据和在base64编码之前的mcrypt_encrypt的hex-ascii输出。

For reference see:
mcrypt-encrypt.php and md5.php

看到供参考:mcrypt-encrypt。php和md5.php

#4


0  

Zaph’s note, that

Zaph的注意

The data will be padded with \0 characters to a multiple of block size

数据将用\0字符填充到块大小的倍数中

set me on track and helped me figure out part of the problem.

让我走上正轨,帮我解决问题的一部分。

Essentially, PHP’s mcrypt only uses \0 padding for the data, while Apple’s CommonCryptor lets you choose between PKCS7 padding (kCCOptionPKCS7Padding) or no padding whatsoever. This was one of the reasons I could never get the data to match: it was always padded differently before it was about to get encrypted.

从本质上讲,PHP的mcrypt只对数据使用\0填充,而苹果的CommonCryptor允许您在PKCS7填充(kCCOptionPKCS7Padding)中选择,或者没有任何填充。这是我无法匹配数据的原因之一:在加密之前,数据总是以不同的方式填充。

The solution for this is to either make PHP perform PKCS7 padding of the data before running mcrypt (example solution) or to make Objective-C perform PHP-style \0 padding and make sure to remove kCCOptionPKCS7Padding (pass NULL to CCCrypt options):

解决方法是,让PHP在运行mcrypt(示例解决方案)之前执行PKCS7数据填充,或者让Objective-C执行PHP风格的\0填充,并确保删除kccoptionpkcs7填充(将NULL传递给CCCrypt选项):

NSMutableData *dataToEncrypt = [sourceData mutableCopy];
NSUInteger dataLength = [dataToEncrypt length];

// See how much padding is required
NSUInteger padding = kCCBlockSizeAES128 - (dataLength % kCCBlockSizeAES128);

// Add that many \0’s (there could be a more efficient way to do this)
for (int i=0; i<padding; i++)
    [dataToEncrypt appendData:[@"\0" dataUsingEncoding:NSASCIIStringEncoding]];

// Recalculate the data length
dataLength = dataToEncrypt.length;

I ended up ditching RNCryptor and working with the native CommonCryptor API directly instead, so the end result looked something like this (the encryptedBase64String method here is a category on NSString in my application, because I only ever need to encrypt strings this way; also note the kHSEncryptionKey constant representing the freeform key string):

我最终选择了抛弃RNCryptor并直接使用本地CommonCryptor API,所以最终的结果看起来是这样的(在我的应用程序中,encryptedBase64String方法是NSString的一个类别,因为我只需要用这种方式加密字符串;还要注意表示*格式密钥字符串的kHSEncryptionKey常量):

- (NSString*)encryptedBase64String {

    // Prepare the data

    NSMutableData *sourceData = [[self dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];

    // Process the key

    NSString *key = [[kHSEncryptionKey MD5String] substringWithRange:NSMakeRange(0, 16)];

    char keyPtr[kCCKeySizeAES128 + 1];
    bzero(keyPtr, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    // Process the iv

    NSString *iv = [[[kHSEncryptionKey MD5String] MD5String] substringWithRange:NSMakeRange(0, 16)];

    char ivPtr[kCCKeySizeAES128 + 1];
    bzero(ivPtr, sizeof(ivPtr));
    [iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];

    // Pad the data, PHP style

    NSUInteger dataLength = [sourceData length];
    NSUInteger padding = kCCBlockSizeAES128 - (dataLength % kCCBlockSizeAES128);

    for (int i=0; i<padding; i++)
        [sourceData appendData:[@"\0" dataUsingEncoding:NSASCIIStringEncoding]];

    dataLength = sourceData.length;

    // Buffer for the resulting data

    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void* buffer = malloc(bufferSize);

    // Run the encryption

    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, NULL,
                                          keyPtr, kCCKeySizeAES128,
                                          ivPtr,
                                          sourceData.bytes, dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesEncrypted);

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

    free(buffer);
    return nil;
}