This post was updated 2772 days ago and some of the ideas may be out of date.
一、前言
最近写一个 MAC 小工具,在和服务端验证这一步打算用一些加密手段去加密以防破解。本来打算用 MD5 验签,虽然达到目的了,但是还是会暴露一些明文数据,心里不自在。反正我的传输数据很少,RSA 的损耗性能不计了,直接上 RSA 加密吧。
二、介绍
关于 RSA 加密的具体介绍,传送门 -> 点我
这里我准备了两套密钥,一套密钥是 MAC 端的 A 组密钥,一套秘钥是 PHP 端的 B 组密钥。MAC 端向 PHP 端发送数据的时候,MAC 端使用 A 的公钥加密数据,PHP 端接收到数据后用 A 的私钥解密。MAC 端获取 PHP 端的数据的时候,PHP 端使用 B 的公钥加密数据,MAC 端使用 B 的私钥解密。
三、实践
3.1 MAC 端:
我曾经在 iOS 端使用过 RSA 加解密的开源组件: Objective-C-RSA
这个组件使用很方便,但是在这次在 MAC 程序中,就不好用了。
在 MAC 工程中,如图所示方法始终返回-25303,这个状态码的意思是属性不存在。也就是不兼容 MAC 程序了。
在 MAC 程序下推荐另一个开源组件:CocoaCryptoMac
下面是我 MAC 端的加解密代码:
// RSA 加密
- (NSDictionary *)rsaEncryption:(NSDictionary *)params
{
NSString *pubkey = @"-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCCbQ6dCdhDpN7gDhIfzBMM2+QhYRexKNoQevbFLqhhqHbb28L/LktHlTPMWhGiYJroFyrC8vK+oxws/fE7oMIlN0HMpdciQqYLa8g7ihf7H+LsYdVenU5yFslwJmVfkXFvKf5QI3Onp2dHk2aLQ7Fa3VyhqUNt8ej9j19z8dta1QIDAQAB-----END PUBLIC KEY-----";
NSData *data = [[self dictionaryToJson:params] dataUsingEncoding:NSUTF8StringEncoding];
CCMKeyLoader *keyLoader = [[CCMKeyLoader alloc] init];
CCMPublicKey *publicKey = [keyLoader loadX509PEMPublicKey:pubkey];
NSError *error;
CCMCryptor *cryptor = [[CCMCryptor alloc] init];
NSData *crytorData = [cryptor encryptData:data withPublicKey:publicKey error:&error];
NSString *encWithPubKey = [CCMBase64 base64StringFromData:crytorData];
// base64 编码替换
NSString *encWithPubKey1 = [encWithPubKey stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
NSString *encWithPubKey2 = [encWithPubKey1 stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
NSString *encWithPubKey3 = [encWithPubKey2 stringByReplacingOccurrencesOfString:@"=" withString:@""];
NSDictionary *dataParams = @{@"data" : encWithPubKey3};
return dataParams;
}
// RSA 解密
- (NSDictionary *)rsaDecrypt:(NSDictionary *)params
{
NSString *privkey = @"-----BEGIN RSA PRIVATE KEY-----MIICXQIBAAKBgQCpXOMTRiRKmnjK8sD0I785RH25rltHMLFO3J3SUzZ2rrDeCCRcETMT+KVJEnsVTnN0IHB5hlnkLXfxFp05E8/ESQT8Qt0xqeVbzXkX8jQjnq0GgE3biuUsHOMZNLhzIne9/PbIRhi+E/WM3JVc3VBNzYLVjIi3Iu+eX/avSLwv3QIDAQABAoGAA3DC9CXyoB6vNtWORzy1VRZ9GgPeutLUZ0O4Dl5pDCl+/PkGtBAYDN8k4cJ3BEx0WqGQvLHsADn5kR430hcC8MiQ5dVuR8njE5VpXgDf7AOVQasBUDUXU+nXWf74enE/ukaVfYxYm0ixMcG/ZyJ8JqXxNucBc+lXbwy22HELVQkCQQCsSOO+7iEDA7Jh3WlQiiE3wUn/yeq73fwqALT1G6urbIZ30VjqIJlgKPPFpRFGqhbV4Qu7ACJ4F/DosQMKu1CjAkEA+6iIntUQXQErsrNA+wdKacDOyH+7yZrtk6aOnkFc3IoXJ94BqHhoI1zbLxJ7K1yK6lOOjSOEXMVmKZuYhZQFfwJBAJqolEpB2sCqAOh5qqDyXv9+NL+6s04S6NuL5uZiAKnSsqO8+uSyfv0jxjIXDHszFWzKqY0lgcvtMgaxYNmxbaECQQDa+vze0OGrPDCNEAPUK7TprteAif2a4VAnsb/aH2Axm4uoqjrhINzlIJCtNjStN5q9ajXZxHUR0MckH3upiHL7AkBVmuLakI+vwUdXi773+d4JaxPfcthYl/DMbKVEiMsJJ3REb2m0leF3FDHnCyvilRYIwqEIXIqEDAGvU93DK6px-----END RSA PRIVATE KEY-----";
NSString *responseString = params[@"data"];
if (responseString.length == 0) {
return nil;
}
// base64编码替换
NSString *responseString1 = [responseString stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
NSString *responseString2 = [responseString1 stringByReplacingOccurrencesOfString:@"_" withString:@"/"];
NSUInteger mod4 = responseString2.length % 4;
if (mod4 != 0) {
NSString *mod5 = [@"====" substringFromIndex:mod4];
responseString2 = [responseString2 stringByAppendingString:mod5];
}
NSData *inputData = [CCMBase64 dataFromBase64String:responseString2];
CCMKeyLoader *keyLoader = [[CCMKeyLoader alloc] init];
CCMPrivateKey *privateKey = [keyLoader loadRSAPEMPrivateKey:privkey];
CCMCryptor *cryptor = [[CCMCryptor alloc] init];
NSData *crytorData = [cryptor decryptData:inputData withPrivateKey:privateKey error:nil];
id json = [NSJSONSerialization JSONObjectWithData:crytorData options:0 error:nil];
NSDictionary *responseDic = (NSDictionary *)json;
return responseDic;
}
你们一定会问我,为什么要替换 “+/=” 这几个符号呢?相信老司机一定知道,这几个符号在 URL 中有特殊的含义,会导致无法解码二进制数据。因此我们将他们分别替换一下。当然你也可以用苹果提供的 URL 编码 Base64 字符串方法解决,这里我还是选择使用替换字符的方式。
- (NSData *)base64EncodedDataWithOptions:(NSDataBase64EncodingOptions)options NS_AVAILABLE(10_9, 7_0);
3.2 PHP 端:
PHP使用 openssl 进行 RSA 数据加解密的时候有长度限制,加密长度不能超过117个字符,解密长度不能超过128个字符。这里我们可以对数据进行分割加解密后再拼接起来。
protected function encrypt($originalData){
$crypto = '';
foreach (str_split($originalData, 117) as $chunk) {
openssl_public_encrypt($chunk, $encryptData, $this->rsaPublicKey);
$crypto .= $encryptData;
}
return $this->urlsafe_b64encode($crypto);
}
protected function decrypt($encryptData){
$crypto = '';
foreach (str_split($this->urlsafe_b64decode($encryptData), 128) as $chunk) {
openssl_private_decrypt($chunk, $decryptData, $this->rsaPrivateKey);
$crypto .= $decryptData;
}
return $crypto;
}
我们在 PHP 端也需要对 “+/=” 进行一下替换,和 MAC 端对应起来。
protected function urlsafe_b64encode($string) {
$data = base64_encode($string);
$data = str_replace(array('+','/','='),array('-','_',''),$data);
return $data;
}
protected function urlsafe_b64decode($string) {
$data = str_replace(array('-','_'),array('+','/'),$string);
$mod4 = strlen($data) % 4;
if ($mod4) {
$data .= substr('====', $mod4);
}
return base64_decode($data);
}
四.Demo:
这里我写了一个Demo,包括了 PHP 端和 MAC 端。
当我在发送数据中输入一串文字,点击发送请求,PHP 端接收到请求后,拼接了一串文字“哎呀你填的是:”返回来。
PHP源码已提供,环境请自行搭建。
如图:
传送门 -> 点我下载
五.总结:
RSA 加密虽然安全,但是在面对数据量庞大的时候,还是很影响性能的。这也就是为什么市面上一般的程序都是用 MD5 验签的方式。
参考资料:
参与讨论