我从iOS应用程序中调用的PHP API要求以某种自定义方式对有效负载进行加密。我在使用 RNCryptor 在 Objective-C 中复制这种方法时遇到了麻烦。
以下是用于加密字符串的 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))));
}
这就是我试图在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;
}
但是这两个函数的结果永远不匹配。 例如,对于相同的一个单词字符串,PHP 代码返回 J39gRcuBEaqMIPP1VlizdA8tRjmyAB6za4zG5wcOB/8=
,而在 Objective-C 中(在生成的 NSData 上运行 base64EncodedStringWithOptions:
之后),我得到1FGpZpVm2p4z3BBY6KW2fw==
。
我需要在 RNCryptor 设置中进一步调整以使其正常工作吗?
更新
我直接玩过原生的iOS CommonCrypto框架,完全没有使用第三方RNCryptor lib。不过,我始终得到与RNCryptor相同的结果。我甚至尝试在我的 Objective-C 和 PHP 代码段中实现 AES128,但即使这样也从未使两个环境的结果匹配......
更新 2
我正在使用的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];
}
尽管大多数答案都集中在 MD5 哈希上,但最有可能导致问题的是MCRYPT_RIJNDAEL_256不是 AES。它不指定密钥大小 256,而是指定 256 的块大小,而 AES 始终具有 128 位的块大小。至于其他参数,在两边的加密例程之前以十六进制打印出它们的值,以找出它们的值。
我意识到我来晚了,你可能已经离开了。 但我想指出的是,现在有一个完整的 RNCryptor PHP 端口,它与 Objective-C 实现的字节流兼容。 我自己去年夏天贡献了它,目前正在维护它。:-)
因此,现在使用 RNCryptorPHP 加密并使用 RNCryptor Objective-C 解密应该很容易,反之亦然。 查看主要的RNCryptor项目以获取详细信息和子项目列表。
在脚本语言之外,将md5
作为十六进制 ASCII 返回并不常见,即使php
提供了二进制输出选项也是如此。
关于 php mcrypt_encrypt
有一些标准的东西:
- 如果密钥小于所需的密钥大小,则用 ''0 填充密钥。
- 数据将用 ''0 个字符填充到块的倍数大小,通常使用填充物,例如
pkcs7
。 - 未指定如何处理不是块大小的 iv,可以猜测是还填充了尾随 ''0 个字符。
其中,由于md5
方法的十六进制ASCII输出,iv将是正确的长度。但是留下了数据长度和填充,这是非标准的。
密钥将是 32 个字节,因此将在 php 中填充 32 ''0 个字符。这就引出了将AES256与128位密钥一起使用的问题。
从底层的CommonCrypto"CommonCryptor.h":
密钥长度:密钥材料的长度。必须适合 选择的操作和算法。
密钥长度不正确。
要做的事情:
1. 处理数据长度/填充
2. 处理密钥长度
如需进一步帮助,请提供您正在使用的示例数据以及 base64 编码之前mcrypt_encrypt
的十六进制 ascii 输出。
有关参考,请参阅:
mcrypt-encrypt.php 和 md5.php
Zaph的注释,那
数据将用 ''0 个字符填充到块大小的倍
数
让我走上正轨,并帮助我找出部分问题。
从本质上讲,PHP的mcrypt
只使用'0
填充数据,而Apple的CommonCryptor允许您在PKCS7填充(kCCOptionPKCS7Padding
)或没有任何填充之间进行选择。这是我永远无法匹配数据的原因之一:在即将加密之前,它总是以不同的方式填充。
解决方案是让PHP在运行mcrypt
之前执行PKCS7数据填充(示例解决方案),或者让Objective-C执行PHP风格的'0
填充并确保删除kCCOptionPKCS7Padding
(将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;
我最终放弃了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;
}