数据加密(二)——非对称加密(加签/加密,以RSA为例)

目录
- RSA算法概述
- 加签、验签过程、算法概述(以SHA1WithRSA为例)
- 加密、解密
- iOS中遇到的RSA及代码
- 问题:为什么RSA公钥加密使用PKCS1填充每次生成结果都不一样? 答:因为PKSC1填充方式...
- 代码整理Demo

RSA是一种算法,但是,在相关应用的时候,还是需要有一些标准的。这就是pkcs。现在的各种程序中,基本都是遵循这个标准来使用RSA的。最近陆续读取RSA相关的内容进行学习。

RSA官网:https://www.rsa.com
标准的查看:https://www.rfc-editor.org/search/rfc_search_detail.php?title=pkcs&pubstatus%5B%5D=Any&pub_date_type=any
The Public-Key Cryptography Standards (公钥加密标准PKCS)是由美国RSA数据安全公司及其合作伙伴制定的一组公钥密码学标准,其中包括证书申请、证书更新、证书作废表发布、扩展证书内容以及数字签名、数字信封的格式等方面的一系列相关协议。

RSA根据密钥长度可以分为rsa 2048、rsa 1024(单位是bit,换算成字节分别是256、128)。注意不要把密钥长度跟证书长度混淆,在rsa1024中,证书的长度应该是216。

# 加/验签、加/解密


## 加签、验签

接收方可以通过签名来确认发送方的身份,并可进行数据完整性检查。由RSA加密算法的规则可知,一个安全个体的私钥只有自己才知道,公钥则是可以被多方知道,所以要起到签名的效果,需要私钥签名,公钥验签

步骤:将原始数据哈希运算, 得出标记, 用 A 的私钥进行一次非对称加密算法处理。B用A的公钥进行解密,如果能解出来,表示:1. 确实是 A 发的。如果解出来的值与收到的原始文本算出的哈希值相同。2. 数据传输途中未被修改。

过程中出现的算法
哈希算法:将任意长度的消息M映射成一个固定长度的散列值h(也称为消息摘要),常见的比如MD4、MD5、SHA-1、SHA-256、SHA-384、SHA-512
签名算法:RSA、DSA。其中RSA既能当做加密算法,也能当做签名算法来用,正反逆运算都是通的。DSA只能用作签名
本文代码签名算法为SHA1+RSA,Java中称 SHA1WithRSA

## 加密、解密:

与加签的目的不同,加密的目的是实现只有指定个体才能打开发送方发出的数据,所以公钥加密,私钥解密,常用的加密算法如RSA

# iOS中的RSA


在iOS中使用RSA加/验签、加/解密,首先需要拿到我们想要的公钥、私钥,在 上篇博客 中已经介绍过证书文件常见的两种编码方式:DER编码PEM编码,以及在iOS中经常接触到的证书格式标准:PKCS#1PKCS#8(java中经常使用)PKCS#12,PKCS#12文件扩展名为.p12或者.pfx(可存储公钥+私钥),此外常见的还有.cer/.crt/.der(存储的是公钥)。
注意:

  • 加载.p12文件代码转换成私钥
  • 加载.cer .crt .der文件代码转换成公钥
  • 直接将PEM编码格式的、PKCS#1/PKCS#8标准的公私钥硬编码,写在代码里使用

以上都是可以的,但是首先需要先确定到底使用哪种方式,因为不同的数据加载方式、不同的证书格式,所要处理的过程是不一样的。详见下面代码

# 代码处理过程


这里使用的是iOS SDK中的Security.framework库,非openssl库,多年以前苹果就弃用了 OpenSSL,转而推荐自有框架 Security 和 CommonCrypto。当然你仍然可以使用 OpenSSL,比如说在 iOS 上使用开源库 OpenSSL for iPhone。

分为两步(其实很简单):

  1. 将公私钥文件或者字符串转换成 SecKeyRef 对象, SecKeyRef 对象是一个密码学角度的抽象的密钥对象(也就是说它可以代表一个公钥、私钥或者某种对称加密的密钥)。无论是加解密还是签名,都会需要这个对象。
##pragma mark - '.der'公钥文件生成SecKeyRef对象(公钥)

##pragma mark - PKCS#1、PKCS#8 PEM编码公钥生成SecKeyRef对象(公钥)
  //PKCS#8格式的证书如果在代码的处理上,比PKCS#1多了一步对header的处理,也就是demo中的stripPublicKeyHeader函数,如果是PKCS#1的证书,跳过这个函数即可
##pragma mark - '.12'私钥文件生成SecKeyRef对象(私钥)

##pragma mark - PKCS#1、PKCS#8 PEM编码公钥生成SecKeyRef对象(私钥)
  //生成代码与公钥过程大致相同,有一些细微差别

有兴趣可以看下这篇文章 — iOS 生成 SecKeyRef 的正规方式,文章有提到直接处理PEM编码格式的头时,由于对应的代码解析力不够强,经常会返回一个空的密钥对象,但是在我们APP内频繁测试没有发现这个问题(如果读到这里能为我解答这个疑问,麻烦评论留言一下吧,多谢)

  1. 调用相应的函数,实现功能
//加签函数
OSStatus SecKeyRawSign(SecKeyRef key, SecPadding padding, const uint8_t *dataToSign, size_t dataToSignLen, uint8_t *sig, size_t *sigLen);
//验签函数
OSStatus SecKeyRawVerify(SecKeyRef key, SecPadding padding, const uint8_t *signedData, size_t signedDataLen, const uint8_t *sig, size_t sigLen);
//加密
OSStatus SecKeyEncrypt(SecKeyRef key, SecPadding padding, const uint8_t *plainText, size_t plainTextLen, uint8_t *cipherText, size_t *cipherTextLen);
//加密
OSStatus SecKeyDecrypt(SecKeyRef key, SecPadding padding, const uint8_t *cipherText, size_t cipherTextLen, uint8_t *plainText, size_t *plainTextLen)

从上面的函数可以看到,函数参数并不复杂,将1中生成SecKeyRef对象传入,数据传输两端确定padding填充方式即可。要确认两边使用的签名算法设置参数一致;,详细代码看demo即可

// digest message with sha1
+ (NSData *)sha1:(NSString *)str
{
    const void *data = [str cStringUsingEncoding:NSUTF8StringEncoding];
    CC_LONG len = (CC_LONG)strlen(data);
    uint8_t * md = malloc( CC_SHA1_DIGEST_LENGTH * sizeof(uint8_t) );;
    CC_SHA1(data, len, md);
    return [NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH];
}

# 问题:为什么RSA公钥加密使用PKCS1填充每次生成结果都不一样?


在上一篇博客 — 常见的PKI标准(X.509、PKCS)中已经介绍过PKCS1填充方式的过程,不再赘述,总结一下:PKCS1填充格式:加密块EB = 00 + 块类型BT + 填充字符PS + 00 + 数据D。如果使用公钥操作,BT永远为02,而对于BT为02的,PS对应的填充字节的值随机产生但不能是0字节(非00)
填充后,进行加密运算之前的数据不一致,得出的结果当然就不一样。
(这篇博客的作者一步步验证了这个现象,感兴趣的可以看下)

# 代码整理Demo


在Objective-C-RSA项目代码的基础上,根据自己项目的使用场景,整理了一下代码,放在了 GitHub - RSAHandle 上,希望能有所帮助,有什么问题可以留言讨论。

你可能感兴趣的:(数据加密(二)——非对称加密(加签/加密,以RSA为例))