iOS 端 RSA 加密

RSA算法是最广为使用的“非对称加密算法”,基本上只要有计算机网络的地方,就有RSA算法。 这种算法非常可靠,密钥越长,它就越难破解。根据报道,目前被破解的最长RSA密钥是768个二进制位。也就是说,长度超过768位的密钥,还无法破解(至少没人公开宣布)。 因此可以认为,1024位的RSA密钥基本安全,2048位的密钥极其安全。RSA是由Rivest、Shamir 和 Adleman三位数学家1977年提出的加密算法,所以以他们首字母命名。

理解RSA

理解RSA首先要理解非对称加密:

  • 公私钥: 非对称加密有公钥私钥组成,公钥和私钥是按照一定数学算法生成的。公钥是公开的,任何人都可以获取。私钥是保密的,只有拥有者才能使用。
  • 加密和签名: 他人使用你的公钥加密信息,然后发送给你,你用私钥解密,取出信息。反过来,你也可以用私钥加密信息,别人用你的公钥解开,从而证明这个信息确实是你发出的,且未被篡改,这叫做数字签名。
  • 理解: 至于公钥和私钥的关系,有人形象的把公钥比喻为保险箱,把私钥比喻为保险箱的钥匙,保险箱我可以给任何人,也可以有多个保险箱,任何人都可以往保险箱里面放东西(机密数据),但只有我有私钥(保险箱的钥匙),只有我能打开保险箱。

iOS使用RSA

RSA加密iOS中经常用到,常见场景是加密用户输入的密码发送给后台,当然为了不泄露用户密码,一般都是将密码进行MD5加盐编码后再用RSA加密给后台,这样即使被破解,也不会泄露用户输入的密码。

基本流程

客户端->>服务器: App请求“公钥”
服务器->>客户端: Server返回“公钥”
客户端->>服务器: App用公钥加密“密码”发送
服务器->>客户端: Server解密正确返回数据

注意: 一般是后台从密钥池中选取一对公私钥,把公钥发送给客户端,客户端加密密码等机密信息后发送给后台,后台使用私钥解密获取加密的内容。

iOS使用RSA注意点

  • 防反编译: 密码密钥等信息一般不存在客户端,防止被反编译
  • 耗时操作: 生成公私钥是耗时操作,后台一般会提前生成多对公私钥保存到数据库中,使用的时候随机取出一对,使用并记录使用的那一对即可。
  • 文件格式: 后台生成.der和.p12后缀格式的文件,其中der格式的文件存放的是公钥(Public key)用于加密,p12格式的文件存放的是私钥(Private key)用于解密。后台可以从.der文件中读出公钥,转化为JSON字符串格式发送给客户端,也可以直接发送后缀名为.der格式公钥文件,客户端下载后使用。
  • iOS生成公私钥对: 至于iOS端生成RSA公私钥,需要集成openssl库,具体Google百度吧。

  • 加解密失败: 如果与后台联调的时候加解密失败,注意一下密钥格式(分为pkcs8和pkcs1)。例如Mac下生成的给iOS使用公私钥文件.der和.p12,密钥编码格式为pkcs1;而Java后台使用的.pem文件格式的公私钥,编码格式为pkcs8,使用的时候需要转换格式。

RSA可靠性

Wiki百科: 对极大整数做因数分解的难度决定了RSA算法的可靠性。换言之,对一极大整数做因数分解愈困难,RSA算法愈可靠。

假如有人找到一种快速因数分解的算法,那么RSA的可靠性就会极度下降。但找到这样的算法的可能性是非常小的。今天只有短的RSA密钥才可能被暴力破解。到目前为止,世界上还没有任何可靠的攻击RSA算法的方式。

只要密钥长度足够长,用RSA加密的信息实际上是不能被解破的。

RSA公私钥生成步骤:

  1. 随意选择两个大的质数p和q,p不等于q,计算N=pq,N的二进制位数就是密钥长度。
  2. 根据欧拉函数(对正整数n,欧拉函数是小于n的正整数中与n互质的数的数目),求得r = (p-1)(q-1)
  3. 选择一个小于r的整数e,条件是1< e < r,且e与r互质,求得 e 关于模 r 的模反元素,命名为d。(模反元素存在,当且仅当e与r互质)
  4. 将 p 和 q 的记录销毁。
  5. (N,e)是公钥,(N,d)是私钥。

RSA加解密公式

PS:假设m为要传递的密文,mod为求余,N、e、d都是已知的

  • 加密公式: m^e ≡ c (mod N),m、e、N都为已知,加密就是算出密文c。
  • 解密公式: c^d ≡ m (mod N),c、d、N为已知,解密就是算出m。

RSA加密长度限制:

  • 理论可加密明文长度: RSA算法本身要求加密内容也就是明文长度m必须0<m<N,也就是说内容这个大整数不能超过N,否则就出错。
  • 实际可加密明文长度: 实际可加密明文长度小于理论长度,因为加密需要遵循一定标准,需要用到padding,padding标准有NoPPadding、OAEPPadding、PKCS1Padding,其中PKCS#1建议的padding就占用了11个字节。
  • 实际可加密明文长度计算: 目前主流的密钥长度一般为1024bits2048bits,如果是PKCS格式密钥长度是1024位,则可加密字符长度为1024/8=128个字节,实际可加密明文长度为128-11=117个字节;同理,如果是PKCS格式2048位密钥,可加密长度为256-11=244个字节。

RSA加密长度超限制解决办法:

如果要加密大于N的数据怎么办? 有两种解决方法:

  • 一种是把长信息分割成若干段短消息,每段分别加密,因为RSA加解密效率比较低,所以这种方式效率很低;
  • 另一种是先选择一种”对称性加密算法“(比如DES或AES),先用RSA加密传递DES或AES密钥,然后用AES或DES的密钥加密信息,对称加密的效率很高。HTTPS就是采用这种方式,RSA传递对称加密密钥,对称密钥加密数据传递。

iOS端RSA加解密示例

场景:客户端加密用户密码/交易密码发送给服务器

客户端向服务器请求RSA公钥服务器 → 返给客户端一个NSString格式的RSA公钥 → 客户端用RSA公钥字符串加密密码发送给服务器 → 服务器用RSA私钥解密并核对密码 → 核对密码是否正确,并返回客户数据给客户端

RSA序列图

例如:这是一串服务器生成的RSA公钥

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDTbZ6cNH9 PgdF60aQKveLz3FTalyzHQwbp601y77SzmGHX3F5NoVUZbd K7UMdoCLK4FBziTewYD9DWvAErXZo9BFuI96bAop8wfl1Vk ZyyHTcznxNJFGSQd/B70/ExMgMBpEwkAAdyUqIjIdVGh1FQ K/4acwS39YXwbS+IlHsPSQIDAQAB

但由于含有/+=\n等特殊字符串,网络传输过程中导致转义,进而导致加密解密不成功,解决办法是进行URL特殊符号编码解码(百分号转义);具体示例,在Demo中有示例,文章最下方有Demo链接

RSA用NSString加密解密Demo,非常简单

//----------------------RSA加密示例------------------------
//原始数据,要加密的字符串
NSString *originalString = @"这是一段将要使用'秘钥字符串'进行加密的字符串!";

//使用字符串格式的公钥私钥加密解密, RSAPublickKey为公钥字符串(NSString格式)
NSString *encryptStr = [RSAEncryptor encryptString:originalString publicKey:RSAPublickKey];

NSLog(@"加密前:%@", originalString);
NSLog(@"加密后:%@", encryptStr);
//用私钥解密,RSAPrivateKey为私钥字符串(NSString格式)
NSString *decryptString = [RSAEncryptor decryptString:encryptStr privateKey:RSAPrivateKey];

NSLog(@"解密后:%@",decryptString);

RSA用NSString加解密的示例头文件

// RSA加密封装类
//注意:如果使用,需要打开钥匙串;因为iOS不支持直接使用字符串格式的公钥进行加密,转换为文件后可使用

#import <Foundation/Foundation.h>

@interface RSAEncryptor : NSObject

/**
*  加密方法
*
*  @param str   需要加密的字符串
*  @param path  '.der'格式的公钥文件路径
*/
+ (NSString *)encryptString:(NSString *)str publicKeyWithContentsOfFile:(NSString *)path;

/**
*  解密方法
*
*  @param str       需要解密的字符串
*  @param path      '.p12'格式的私钥文件路径
*  @param password  私钥文件密码
*/
+ (NSString *)decryptString:(NSString *)str privateKeyWithContentsOfFile:(NSString *)path password:(NSString *)password;

/**
*  加密方法
*
*  @param str    需要加密的字符串
*  @param pubKey 公钥字符串
*/
+ (NSString *)encryptString:(NSString *)str publicKey:(NSString *)pubKey;

/**
*  解密方法
*
*  @param str     需要解密的字符串
*  @param privKey 私钥字符串
*/
+ (NSString *)decryptString:(NSString *)str privateKey:(NSString *)privKey;

@end

如果您觉得有所帮助,请在RSADemo上赏个Star ⭐️,您的鼓励是我前进的动力

参考: