一、前言

最近写一个 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 验签的方式。

参考资料:

关于Base64编码中的‘+’ 和‘/’字符处理

CocoaCryptoMac

MAC OSX下的RSA加解密实现

Objective-C-RSA