mikeshihua

Nodejs 和 iOS 中 RSA 和 AES 加解密的一些问题

Nodejs 和 iOS 中 RSA 和 AES 加解密的一些问题

在使用Nodejs 和 iOS 上系统库代码加密时,遇到了两个平台加密的得到的内容不一致的问题。包括 AES 相同密码加密的内容不一致,相同密钥对 RSA 签名不一致的问题,下面是一些解决方法。

AES 加解密结果不一致

原因:Nodejs 底层使用 OpenSSL,AES 加密时会对密码进行处理,而 iOS 系统库则没有这些处理。Nodejs 的文档中有说明。

The password is used to derive the cipher key and initialization vector (IV). The value must be either a 'latin1' encoded string, a Buffer, aTypedArray, or a DataView.

The implementation of crypto.createCipher() derives keys using the OpenSSL function EVP_BytesToKey with the digest algorithm set to MD5, one iteration, and no salt. The lack of salt allows dictionary attacks as the same password always creates the same key. The low iteration count and non-cryptographically secure hash algorithm allow passwords to be tested very rapidly.

In line with OpenSSL’s recommendation to use pbkdf2 instead of EVP_BytesToKey it is recommended that developers derive a key and IV on their own using crypto.pbkdf2() and to use crypto.createCipheriv() to create the Cipher object.

解决办法:对iOS 的密码进行相同的处理即可。

对于我使用的 aes-128-ecb ,可以在 iOS 端使用MD5 处理密码即可再传入原函数,计算的结果便能吻合。其他的使用 crypto.createCipher() 或者 crypto.createDecipher() 方法加密的,则要根据EVP_BytesToKey 返回的内容进行处理。

对于aes-256-cbc等需要传入 vi 向量的算法,即使用crypto.createCipheriv()crypto.createDecipheriv() 来说,则没有这个问题。

javascript

1
2
3
4
5
6
7
8
function AESEncript(key, content) {
const cipher = crypto.createCipher('aes-128-ecb', key);
let encrypted = cipher.update(content, 'utf8', 'base64');
encrypted += cipher.final('base64');
return encrypted;
}
var result = AESEncript('1234','abc');
console.log(encrypted);

objective-c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#import <CommonCrypto/CommonCryptor.h>
#include <CommonCrypto/CommonDigest.h>
NSData * MD5(NSString *toMD5String){
NSData *data = [toMD5String dataUsingEncoding:NSUTF8StringEncoding];
NSData *retData = nil;
unsigned char *md;
md = (uint8_t *)malloc(CC_MD5_DIGEST_LENGTH);
bzero(md, CC_MD5_DIGEST_LENGTH);
CC_MD5(data.bytes, (CC_LONG)data.length, md);
retData = [NSData dataWithBytes:md length:CC_MD5_DIGEST_LENGTH];
free(md);
md = NULL;
return retData;
}
- (NSData *)AES_EBC_EncriptStringWith:(NSString *)toEncript key:(NSString *)key{
NSData *data = [toEncript dataUsingEncoding:NSUTF8StringEncoding];
// 使用 key 的 MD5 摘要作为密码
NSData *keyData = MD5(key);
NSData *retData = nil;
NSUInteger dataLength = [data length];
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
bzero(buffer, bufferSize);
size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding|kCCOptionECBMode,
keyData.bytes,
keyData.length,
NULL,
data.bytes,
data.length,
buffer,
bufferSize,
&numBytesEncrypted);
if (cryptStatus == kCCSuccess) {
retData = [NSData dataWithBytes:buffer length:numBytesEncrypted];
}
free(buffer);
return retData;
}

验证签名结果不一致

Nodejs 代码私钥签名的内容,在iOS端公钥验证失败

解决办法:使用被签名的数据的摘要传入系统的验证函数

https://stackoverflow.com/questions/33172939/verifying-rsa-signature-ios

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BOOL PKCSVerifyBytesSHA256withRSA(NSData* plainData, NSData* signature, SecKeyRef publicKey)
{
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
if (!CC_SHA256([plainData bytes], (CC_LONG)[plainData length], digest))
return NO;
OSStatus status = SecKeyRawVerify(publicKey,
kSecPaddingPKCS1SHA256,
digest,
CC_SHA256_DIGEST_LENGTH,
[signature bytes],
[signature length]);
return status == errSecSuccess;
}

RSA 证书生成

创建openssl 需要的目录

1
2
3
4
5
6
mkdir demoCA
cd demoCA
mkdir certs newcerts crl private
touch index.txt
echo 01>serial
ls

生成根证书

1
2
3
4
openssl rand -out private/.rand 1000
openssl genrsa -aes256 -out private/cakey.pem 4096
openssl req -new -key private/cakey.pem -out private/ca.csr
openssl x509 -req -days 365 -sha1 -extensions v3_ca -signkey private/cakey.pem -in private/ca.csr -out certs/ca.cer

生成服务器证书

1
2
3
4
openssl genrsa -aes256 -out private/server-key.pem 4096
openssl req -new -key private/server-key.pem -out private/server.csr
openssl x509 -req -days 365 -sha1 -extensions v3_req -CA certs/ca.cer -CAkey private/cakey.pem \
-CAserial ca.srl -CAcreateserial -in private/server.csr -out certs/server.cer

导出证书为 p12、der格式,在 iOS 设备上使用

1
2
3
4
openssl pkcs12 -export -clcerts -name myserver -inkey \
private/server-key.pem -in certs/server.cer -out certs/server.p12
openssl x509 -outform der -in certs/server.cer -out server.der

导出 pem格式的公钥,在node服务器上使用

1
openssl rsa -pubout -in private/server-key.pem -out public.pem

参考

https://nodejs.org/api/crypto.html#crypto_crypto_createcipheriv_algorithm_key_iv
https://my.oschina.net/itblog/blog/651434
https://github.com/LittoCats/Crypto/blob/master/openssl_rsa_certificate_%E7%94%9F%E6%88%90%E4%B8%8E%E8%BD%AC%E6%8D%A2.md
http://www.cnblogs.com/littleatp/p/5878763.html
http://tool.chacuo.net/cryptaes
https://nodejs.org/api/crypto.html#crypto_sign_sign_privatekey_outputformat
http://www.jianshu.com/p/a8b87e436ac7