Base64编码
-
Base64编码简介
一般情况下,如果用记事本直接打开 .exe、.jpg、.pdf、… 等格式的文件时,会看到一大堆乱码,因为二进制文件包含很多无法显示和打印的字符。所以,如果要让像记事本这样的文本处理软件能处理和显示二进制数据,就需要一个二进制数据到字符串的转换方法。Base64 就是一种最常见的二进制编码方法。
Base64 是一种以 64 个可见字符集对二进制数据进行编码的编码算法(即,Base64 是一种用 64 个字符来表示任意二进制数据的方法)。
-
Base64编码原理
Base64编码的原理如下:
- 准备一个包含 64 个可见字符的数组构成一张编码表。
- 将二进制数据每 3 Byte(共 24 bit) 分为一组,然后将每组均分为 4 段,每段 6 bit。依次将得到的每个段的值,作为索引进行查表,得到对应的编码字符。
- 因为二进制数据每 3 Byte 分为一组,如果编码到最后,二进制数据剩余 1 Byte,则在二进制数据末尾补 2 Byte 的 0x00,凑齐 3 Byte 后再进行编码,然后在编码字符的最后,拼接 2 个 = 号,表示二进制数据的末尾被补了 2 Byte 的 0x00;同理,如果编码到最后,二进制数据剩余 2 Byte,则在二进制数据末尾补 1 Byte 的 0x00,凑齐 3 Byte 后再进行编码,然后在编码字符的最后,拼接 1 个 = 号,表示二进制数据的末尾被补了 1 Byte 的 0x00。因此,在 Base64编码 中 = 号只会出现在最后面。
一些注意点:
- 用于 Base64编码 的每个编码字符,占 1 Byte。因为 Base64编码 是将原来二进制数据的 3 Byte 编码为 4 个编码字符,所以数据经过 Base64编码 后,占用的存储空间会增大约 1 3 \frac{1}{3} 31 。即,Base64编码 会把 3 Byte 的二进制数据编码为 4 Byte 的文本数据,编码后数据长度增加约 1 3 \frac{1}{3} 31 。虽然将二进制数据编码为 Base64文本 会造成一定程度的存储空间浪费 ,但是编码后的文本数据可以在邮件正文、网页等直接显示,易于阅读与传输。
- 为什么对二进制数据进行 Bsse64编码 时,需要将二进制数据每 3 Byte 分为一组呢?因为计算机中数据存储的最小单元为 Byte(1 Byte = 8 bit),而 Base64 只需要 6 bit 就可以表示所有的编码字符(
2
6
=
64
2^6=64
26=64),所以取 6 bit 和 8 bit 的最小公倍数 24 bit,即,二进制数据 3 Byte 正好 24 bit,将 24 bit 的二进制数据,每 6 bit 编码为一个 Base64编码字符,恰好能得到 4 个编码字符。由此可得:
Base16( 2 4 = 16 2^4=16 24=16)将每 1 Byte (4 bit 和 8 bit 的最小公倍数为 8 bit = 1 Byte)的二进制数据编码为 2(8 bit / 4 bit = 2)个字符
Base32( 2 5 = 32 2^5=32 25=32)将每 5 Byte (5 bit 和 8 bit 的最小公倍数为 40 bit = 5 Byte)的二进制数据编码为 8(40 bit / 5 bit = 8)个字符
Base64( 2 6 = 64 2^6=64 26=64)将每 3 Byte (6 bit 和 8 bit 的最小公倍数为 24 bit = 3 Byte)的二进制数据编码为 4(24 bit / 6 bit = 4)个字符
Base128( 2 7 = 128 2^7=128 27=128)将每 7 Byte (7 bit 和 8 bit 的最小公倍数为 56 bit = 7 Byte)的二进制数据编码为 8(56 bit / 7 bit = 8)个字符
Base256( 2 8 = 256 2^8=256 28=256)将每 1 Byte (8 bit 和 8 bit 的最小公倍数为 8 bit = 1 Byte)的二进制数据编码为 1(8 bit / 8 bit = 1)个字符
… … - 可以自定义 Base64编码,即自定义 64 个编码字符的排列顺序。但是即使使用自定义的编码表,也不能将 Base64编码 用于数据加密。因为 Base64编码 是一种通过查表的编码方法,很容易通过枚举,反算出自定义的编码表。
- Base64 适用于小段内容的编码,比如数字证书签名、Cookie的内容等。
- Base64 适用于少量二进制数据的显式和传输。
- 大多数编码都是由字符串转换成二进制,而 Base64编码 则是从二进制转换为字符串。
- Base64 讲到底只是一种编码规则,其底层的操作的对象都是二进制数据,即 Base64 底层是将 源二进制数据 通过Base64编码表 映射成 目标二进制数据。源二进制数据可以 是任意格式的文件,而目标二进制数据,由于 Base64 编码表的约束,恰好都可以转成相应的编码字符。
-
Base64编码举例
标准的 Base64编码表 包含的可打印字符依次为:A ~ Z、a ~ z、0 ~ 9、+、\
如下图所示:
对字符串 Man 进行 Base64 编码:- 将字符串 Man 转换为二进制数据,在这里将 Man 转换为单字节的 ASCII 码字符:
M、a、n 对应的 ASCII 码值分别为 77,97,110
对应的二进制值分别为 01001101、01100001、01101110,共 24 bit - 将 24 bit 每 6 bit 分为一段,总共分为 4 段,如图红框所示
- 算出每段的值:19、 22 、 5 、 46 作为索引进行查表,获取对应的 Base64 编码字符:T、W、F、u,即 字符串 Man,在 ASCII 编码下,对应的 Base64编码 为 TWFu
对字符串 ABCD 进行 Base64 编码:
- 将字符串 ABCD 转换为二进制数据,在这里将 ABCD 转换为单字节的 ASCII 码字符:
A、B、C、D 对应的 ASCII 码值分别为 0x41,0x42,0x43,0x44
对应的二进制值分别为 01000001、01000010、01000011、01000100,共 32 bit - 因为字符串 ABCD 的 ASCII 码占 4 Byte,所以在末尾补 2 Byte 的 00000000,凑成 6 Byte,共 48 bit。将 48 bit 每 6 bit 分为一段,总共分为 8 段,如下图所示
- 算出每段的值:16、 20 、 9 、 3、 17、 0 作为索引进行查表,获取对应的 Base64 编码字符:Q、U、J、D、R、A,因为最后两段的索引值:0 、0,是为了凑齐 6 Byte 补全的,所以不查表,直接输出 = 号。即 字符串 ABCD,在 ASCII 编码下,对应的 Base64编码 为 QUJDRA==
- 将字符串 Man 转换为二进制数据,在这里将 Man 转换为单字节的 ASCII 码字符:
-
Base64编解码 代码演示
使用 Objective-C 进行 Base64 编解码的演示:
// iOS 系统在 7.0 之后才内置了 Base64 编解码的功能 // 在 iOS 7.0 之前,如果要使用 Base64 编解码功能,需要第三方框架的支持 -(void)base64Test { NSString* name = @"hcg"; NSString* base64String = [self base64Encode:name]; NSString* originString = [self base64Decode:base64String]; NSLog(@"name = %@", name); NSLog(@"base64String = %@", base64String); NSLog(@"originString = %@", originString); /** 输出 name = hcg base64String = aGNn originString = hcg */ } // 对字符串进行 base64 编码 // param0.原字符串 // return.base64 字符串 -(NSString *)base64Encode:(NSString *)originString { NSData* originData = [originString dataUsingEncoding:NSUTF8StringEncoding]; NSString* base64String = [originData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; return base64String; } // 对字符串进行 base64 解码 // param0.base64 字符串 // return.原字符串 -(NSString *)base64Decode:(NSString *)base64String { NSData* originData = [[NSData alloc] initWithBase64EncodedString:base64String options:NSDataBase64DecodingIgnoreUnknownCharacters]; NSString* originString = [[NSString alloc] initWithData:originData encoding:NSUTF8StringEncoding]; return originString; } // 在 Objective-C 中,关于 Base64 编解码的部分,还提供了输出输出结果是 NSData 的方法 // 这也从侧面说明了: // Base64 讲到底只是一种编码规则,其底层的操作的对象都是二进制数据,即 Base64 底层是将 源二进制数据 通过Base64编码表 映射成 目标二进制数据。 -(void)otherFunc { Byte bytes[3] = {68, 63, 67}; NSData* testData = [[NSData alloc] initWithBytes:bytes length:3]; NSData* base64Data = [testData base64EncodedDataWithOptions:(NSDataBase64EncodingOptions)NSDataBase64Encoding64CharacterLineLength]; NSData* originData = [[NSData alloc] initWithBase64EncodedData:base64Data options:(NSDataBase64DecodingOptions)NSDataBase64DecodingIgnoreUnknownCharacters]; NSLog(@"testData = %@", testData); NSLog(@"base64Data = %@", base64Data); NSLog(@"originData = %@", originData); /** 输出 testData = {length = 3, bytes = 0x443f43} base64Data = {length = 4, bytes = 0x52443944} originData = {length = 3, bytes = 0x443f43} */ }
-
使用 macOS 终端进行 Base64 编解码
// 进入到指定的目录进行操作 cd /Users/Airths/Desktop/Base64_Test // 将图片 Nature.jpeg 进行 Base64 编码,结果输出到 Nature.txt。 -o 是 output 的意思 base64 Nature.jpeg -o Nature.txt // 将文本 Nature.txt 进行 Base64 解码,结果输出到 Result.jpeg。 -d 是 decode 的意思 base64 Nature.txt -o Result.jpeg -d
-
Base64编码 拓展
- 标准的 Base64编码 可能出现字符 + 和 /,在 URL 中就不能直接作为参数,所以有一种 URL Safe 的 base64编码:把字符 + 和 / 分别替换成 - 和 _
- 字符 = 也可能出现在 Base64编码 中,但 = 用在URL、Cookie 里面会造成歧义,所以很多 Base64编码 后会把 = 去掉。那去掉 = 后怎么解码呢?因为 Base64编码 是把 3 Byte 的二进制数据 变为4 Byte 的编码字符,所以,Base64编码 的长度永远是 4 的倍数,因此,解码时,根据需要加上 = 把 Base64字符串 的长度变为 4 的倍数,就可以正常解码了。
- 密码学中经常使用 Base64,使用流程一般为:
- 发送方对数据进行加密,但是加密后的数据不便于查看和传递,因此对加密后的数据进行 Base64 编码后,再发送数据给接收方
- 接收方接收到数据后,先对数据进行 Base64 解码,将数据还原成加密后的形态,然后再对解码后的数据,进行解密。
SSL 与 OpenSSL
-
SSL 简介
SSL(Secure Socket Layer):安全套接字层,由网景公司(Netscape)所研发的一种建立在 TCP 之上的安全通信协议。主要是防止信息在互联网上传输的时,被窃听或者篡改。
SSL 的体系结构中包含两个协议子层:- SSL 记录协议(SSL Record Protocol):位于网络层,建立在可靠的传输协议(如 TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。
- SSL 握手协议(SSL Handshake Protocol):位于传输层,它建立在 SSL记录协议 之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
-
SSL 与 HTTPS
HTTPS (Hyper Text Transfer Protocol over Secure Socket Layer):超文本传输安全协议,是以安全为目标的 HTTP 通道,在 HTTP 的基础上通过传输加密和身份认证保证了传输过程的安全性。HTTPS 是在 HTTP 的基础下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL 证书。在使用浏览器访问网站时,如果该网站受 SSL 证书保护,其相应的 URL 中会显示 HTTPS 。
单击浏览器地址栏的挂锁图标,即可查看证书详细信息,包括颁发机构和网站所有者的公司名称。 -
SSL 与 TLS
TLS(Transport Layer Security):传输层安全协议,由于 HTTPS 的推出受到了很多人的欢迎,在 SSL 更新到 3.0 时,IETF 对 SSL3.0 进行了标准化,并添加了少数机制(但是几乎和 SSL3.0 无差异),标准化后的 SSL 被 IETF 更名为 TLS1.0
TLS 是更为安全的升级版 SSL。由于 SSL 这一术语更为常用,因此仍然将我们的安全证书称作 SSL。但从赛门铁克购买 SSL 时,真正购买的是最新的 TLS 证书,有 ECC、RSA 或 DSA 三种加密方式可以选择。
-
OpenSSL 简介
SSL 交互过程比较复杂,而且还涉及到 对称加密 和 非对称加密,为此 Eric A. Young 和 Tim J. Hudson 使用 C 语言 实现了一个开源的库,用于支持 SSL。这个开源库,就是 OpenSSL。
由于 OpenSSL 采用 C 语言编写,这使得 OpenSSL 具有优秀的跨平台性能。OpenSSL支持 Linux、Windows、BSD、macOS、VMS 等平台,这使得 OpenSSL 具有广泛的适用性。
OpenSSL 主要包括以下三个组件:
- openssl:多用途的命令行工具
- libcrypto:加密算法库
- libssl:加密模块应用库,实现了 SSL 及 TLS
OpenSSL 可以实现:秘钥证书管理、对称加密 和 非对称加密
OpenSSL 一共实现了 8 种对称加密算法,其中有 7 种分组加密算法:AES、DES、Blowfish、CAST、IDEA、RC2、RC5;1 种流加密算法:RC4
OpenSSL 一共实现了 4 种非对称加密算法,包括:
- DH 算法:一般用于密钥交换,即 迪菲-赫尔曼秘钥交换算法
- RSA 算法:既可以用于密钥交换,也可以用于数字签名
- DSA 算法:DSA算法一般只用于数字签名
- EC算法(椭圆曲线算法)
macOS 下 RSA 证书生成过程
-
常用文件格式介绍
.pem(Privacy Enhanced Mail):OpenSSL 使用 pem 文件格式存储:证书(Certificate)、公钥(PublicKey)、私钥(PrivateKey)。
pem 文件一般为文本格式:
以 -----BEGIN… 开头
以 -----END… 结尾
中间的内容是 Base64 编码-----BEGIN... // Base64编码 -----END...
.csr(Certificate Signing Request):证书签名请求文件,csr 文件是向证书颁发机构(Certificate Authority)申请证书时所需要的一个数据文件,csr 文件包含 用户公钥 和 用户信息。
csr 文件一般为文本格式:
以 -----BEGIN CERTIFICATE REQUEST----- 开头
以 -----END CERTIFICATE REQUEST----- 结尾
中间的内容是 Base64 编码-----BEGIN CERTIFICATE REQUEST----- // Base64编码 -----END CERTIFICATE REQUEST-----
.crt(Certificate):用于存储数字证(Digital Certificate)。书当证书颁发机构(CA)对用户提交的 csr 文件(证书签名请求文件,包含用户公钥和用户信息)审核通过后,会使用自己的私钥对用户提交的公钥和用户信息进行签名,证明用户提交的 csr 文件真实有效。此时证书颁发机构(CA)会返回一个 crt 文件给用户。因此,crt 文件包含:用户的公钥、用户的信息、证书颁发机构对 用户公钥 和 用户信息 的签名。
crt 文件一般为文本格式:
以 -----BEGIN CERTIFICATE----- 开头
以 -----END CERTIFICATE----- 结尾
中间的内容是 Base64 编码-----BEGIN CERTIFICATE----- // Base64编码 -----END CERTIFICATE-----
.cer(Cer tificate):用于存储数字证(Digital Certificate)。crt 文件的替代形式。同样地,cer 文件包含:用户的公钥、用户的信息、证书颁发机构对 用户公钥 和 用户信息 的签名。
.der(Distinguished Encoding Rules):通过 crt 文件生成,里面以二进制形式存储了 用户公钥 和 用户信息。
.p12:(Predecessor of PKCS #12):里面存储的是 用户私钥 和 用户信息,受用户输入的密码保护。用户输入的密码用于加密私钥,以确保私钥的安全:其他人即使拿到了证书备份(p12),在不知道加密私钥的密码的情况下,是无法导入证书的。
-
macOS 下使用 RSA 进行加密和解密
macOS 内置了 OpenSSL,可以直接通过终端 使用命令行 来进行 RSA 的加密解密。OpenSSL 中关于 RSA 加密解密,常用的指令主要有以下 3 个:
- genrsa:生成并输出一个 RSA 私钥
- rsautl:使用 RSA 秘钥进行 加密、解密、签名、验证 等运算
- rsa:处理 RSA 秘钥的格式转换等问题
-
进入 RSA_Test 目录,之后所有的操作,包括证书的生成与输出,都在此目录进行
$ cd /Users/Airths/Desktop/RSA_Test
-
生成用户的 RSA 私钥 usr_private.pem
注意:usr_private.pem 里面包含了 RSA 算法生成的所有信息。即,usr_private.pem 里面包含了公钥的信息和私钥的信息。// 私钥长度可选:512 | 1024 | 2048 $ openssl genrsa -out usr_private.pem 1024 Generating RSA private key, 1024 bit long modulus ..........................++++++ .........................................................................................................++++++ e is 65537 (0x10001)
-
从用户的 RSA 私钥 usr_private.pem 中提取用户的 RSA 公钥 usr_public.pem
$ openssl rsa -in usr_private.pem -pubout -out usr_public.pem writing RSA key
-
通过 vi 编辑器生成一个名为 message.txt 的测试文本
// 退出 vi 编辑器 // 1.按 Esc 键 跳到命令模式 // 2.在命令模式下输入 :wq 保存文件并退出 vi 编辑器 $ vi message.txt $ cat message.txt I am hcg, hello world!
-
使用用户的 RSA 公钥 usr_public.pem 对 message.txt 里面的内容进行加密,将加密结果保存到 ciphertxet.txt 并查看
使用公钥加密的过程叫做:encrypt(加密)// 加密后的输出文件格式,可以是 ciphertxet.txt 也可以是 ciphertxet.bin $ openssl rsautl -encrypt -in message.txt -inkey usr_public.pem -pubin -out ciphertxet.txt $ cat ciphertxet.txt ??????P???\?.??Ez?????7q$c?(?$ѝ?:???`c?
-
使用用户的 RSA 私钥 usr_private.pem 对 ciphertxet.txt 里面的内容进行解密,将解密结果保存到 origin.txt 并查看
使用私钥解密的过程叫做:decrypt(解密)$ openssl rsautl -decrypt -in ciphertxet.txt -inkey usr_private.pem -out origin.txt $ cat origin.txt I am hcg, hello world!
-
使用用户的 RSA 私钥 usr_private.pem 对 message.txt 里面的内容进行加密,将加密结果保存到 sign.bin 并查看
使用私钥加密的过程叫做:sign(签名)$ openssl rsautl -sign -in message.txt -inkey usr_private.pem -out sign.bin $ xxd sign.bin 00000000: 44cb 08d2 6b39 d65a d2e3 2896 b63a 4f5c D...k9.Z..(..:O\ 00000010: bfe0 5cb0 9a72 f9b5 74ad 8468 dbfd 54b5 ..\..r..t..h..T. 00000020: f9eb c349 5959 3993 0a52 6fac e5da 47b2 ...IYY9..Ro...G. 00000030: 8bf9 521f 08d8 fa00 3566 e6bd 5e6a 6752 ..R.....5f..^jgR 00000040: e0f4 0b8e 21cc 852a 78a2 d9cc 982d 825a ....!..*x....-.Z 00000050: a262 f7dd c582 9dd2 b429 d384 18fa 3558 .b.......)....5X 00000060: 1f79 a9f7 0589 0dec 7572 65f2 d323 a996 .y......ure..#.. 00000070: 9d89 46e1 3179 21fa a7fe 9f45 e158 e23c ..F.1y!....E.X.< $ cat sign.bin D?k9?Z??(??:O\??\??r??t??h??T????IYY9? Ro???G?????5f?^jgR?? ?ure??#????F?1y!????E?X?<
-
使用用户的 RSA 公钥 usr_public.pem 对 sign.bin 里面的内容进行解密,将解密结果保存到 verify.txt 并查看
使用公钥解密的过程叫做:verify(验证)$ openssl rsautl -verify -in sign.bin -inkey usr_public.pem -pubin -out verify.txt $ cat verify.txt I am hcg, hello world!
-
查看用户的 RSA 公钥 usr_public.pem
$ cat usr_public.pem -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLSZN0cSKor2xr8SaVDFkU5rka 3/A3upvNGp2mksGXAxsaVug+8jk+6Dw1n3QmuzMcrvizN1Ar8Vfd7ghYEvcrZVTR 0+c/XAGt7RVFx6j3s6usYtU+FQR67edSn5mQ7ZgbFzDHsl83tXNBWm915CWNMktT 6pycHiK0TQOjtTGUyQIDAQAB -----END PUBLIC KEY-----
-
查看用户的 RSA 私钥 usr_private.pem
$ cat usr_private.pem -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDLSZN0cSKor2xr8SaVDFkU5rka3/A3upvNGp2mksGXAxsaVug+ 8jk+6Dw1n3QmuzMcrvizN1Ar8Vfd7ghYEvcrZVTR0+c/XAGt7RVFx6j3s6usYtU+ FQR67edSn5mQ7ZgbFzDHsl83tXNBWm915CWNMktT6pycHiK0TQOjtTGUyQIDAQAB AoGBAMi9owp9HciNUfdVbtAIHX9yRp+vOzsM3wUunzb5/Iju3DiqUTS3ZKgmFC6v grcsbFCKx7PRjy2VaTsR7tNqBv/kxkWkYBuvIvjqNWUKjBoJ9XBR4pWpKjQt+NJj b5oxg7CTa8BUAgQOtlI7M/kH2DgiqvW5NAsiJ/nwVGjADqIBAkEA5b6ct4Mm/wgN S8DI5L+rwRLXeEOTmDzyFM/B99hKm6WqXX1fDB1uHlStIVxjaIR2r5a0ZMLUJK15 d+gilZ3J+QJBAOKE715ZnsAwxTVQL9w+zT6xGFtDvVVlhY5fOdVleqh5t/K5vS0/ 10pGDbi5wCQPV7CYhR0YeHB/a2BK/CEdVVECQCTfIJu7KskHa8ral+NOd6w27+nX PprSPS/l4AV86wxr3BnBAz4YgxHPGTIfd7zQdZPcpfr94bo5NSNvWA2XE1ECQFh+ PJFsi6Njqjd4uync8wvx3aUR4q6w+as0MoDWo0OcQau2ulwqG65tjcDD+Hdd8xEP lLlYLP0uGkHFKZi6P/ECQQDR9R0e+DYEVGBq/3BU5fZXtwD6btHnW82yAaumJ6Zk qdC/XOza+g8vcEoMgo6edbYs/JSADBCcIQGZDf2u7+4F -----END RSA PRIVATE KEY-----
-
可以看到,usr_private.pem 中 Base64 编码的长度比 usr_public.pem 中 Base64 编码的长度长很多,因为 usr_private.pem 中包含了更多的信息,通过将 usr_public.pem 转换为明文 usr_public.txt,将 usr_private.pem 转换为明文 usr_private.txt,对比 usr_public.txt 和 usr_private.txt 的差异
// 将 usr_public.pem 转换为明文 usr_public.txt $ openssl rsa -in usr_public.pem -pubin -text -out usr_public.txt writing RSA key $ cat usr_public.txt Public-Key: (1024 bit) Modulus: 00:cb:49:93:74:71:22:a8:af:6c:6b:f1:26:95:0c: 59:14:e6:b9:1a:df:f0:37:ba:9b:cd:1a:9d:a6:92: c1:97:03:1b:1a:56:e8:3e:f2:39:3e:e8:3c:35:9f: 74:26:bb:33:1c:ae:f8:b3:37:50:2b:f1:57:dd:ee: 08:58:12:f7:2b:65:54:d1:d3:e7:3f:5c:01:ad:ed: 15:45:c7:a8:f7:b3:ab:ac:62:d5:3e:15:04:7a:ed: e7:52:9f:99:90:ed:98:1b:17:30:c7:b2:5f:37:b5: 73:41:5a:6f:75:e4:25:8d:32:4b:53:ea:9c:9c:1e: 22:b4:4d:03:a3:b5:31:94:c9 Exponent: 65537 (0x10001) -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLSZN0cSKor2xr8SaVDFkU5rka 3/A3upvNGp2mksGXAxsaVug+8jk+6Dw1n3QmuzMcrvizN1Ar8Vfd7ghYEvcrZVTR 0+c/XAGt7RVFx6j3s6usYtU+FQR67edSn5mQ7ZgbFzDHsl83tXNBWm915CWNMktT 6pycHiK0TQOjtTGUyQIDAQAB -----END PUBLIC KEY-----
// 将 usr_private.pem 转换为明文 usr_private.txt $ openssl rsa -in usr_private.pem -text -out usr_private.txt writing RSA key // 查看 usr_private.txt 里面的内容 $ cat usr_private.txt Private-Key: (1024 bit) modulus: 00:cb:49:93:74:71:22:a8:af:6c:6b:f1:26:95:0c: 59:14:e6:b9:1a:df:f0:37:ba:9b:cd:1a:9d:a6:92: c1:97:03:1b:1a:56:e8:3e:f2:39:3e:e8:3c:35:9f: 74:26:bb:33:1c:ae:f8:b3:37:50:2b:f1:57:dd:ee: 08:58:12:f7:2b:65:54:d1:d3:e7:3f:5c:01:ad:ed: 15:45:c7:a8:f7:b3:ab:ac:62:d5:3e:15:04:7a:ed: e7:52:9f:99:90:ed:98:1b:17:30:c7:b2:5f:37:b5: 73:41:5a:6f:75:e4:25:8d:32:4b:53:ea:9c:9c:1e: 22:b4:4d:03:a3:b5:31:94:c9 publicExponent: 65537 (0x10001) privateExponent: 00:c8:bd:a3:0a:7d:1d:c8:8d:51:f7:55:6e:d0:08: 1d:7f:72:46:9f:af:3b:3b:0c:df:05:2e:9f:36:f9: fc:88:ee:dc:38:aa:51:34:b7:64:a8:26:14:2e:af: 82:b7:2c:6c:50:8a:c7:b3:d1:8f:2d:95:69:3b:11: ee:d3:6a:06:ff:e4:c6:45:a4:60:1b:af:22:f8:ea: 35:65:0a:8c:1a:09:f5:70:51:e2:95:a9:2a:34:2d: f8:d2:63:6f:9a:31:83:b0:93:6b:c0:54:02:04:0e: b6:52:3b:33:f9:07:d8:38:22:aa:f5:b9:34:0b:22: 27:f9:f0:54:68:c0:0e:a2:01 prime1: 00:e5:be:9c:b7:83:26:ff:08:0d:4b:c0:c8:e4:bf: ab:c1:12:d7:78:43:93:98:3c:f2:14:cf:c1:f7:d8: 4a:9b:a5:aa:5d:7d:5f:0c:1d:6e:1e:54:ad:21:5c: 63:68:84:76:af:96:b4:64:c2:d4:24:ad:79:77:e8: 22:95:9d:c9:f9 prime2: 00:e2:84:ef:5e:59:9e:c0:30:c5:35:50:2f:dc:3e: cd:3e:b1:18:5b:43:bd:55:65:85:8e:5f:39:d5:65: 7a:a8:79:b7:f2:b9:bd:2d:3f:d7:4a:46:0d:b8:b9: c0:24:0f:57:b0:98:85:1d:18:78:70:7f:6b:60:4a: fc:21:1d:55:51 exponent1: 24:df:20:9b:bb:2a:c9:07:6b:ca:da:97:e3:4e:77: ac:36:ef:e9:d7:3e:9a:d2:3d:2f:e5:e0:05:7c:eb: 0c:6b:dc:19:c1:03:3e:18:83:11:cf:19:32:1f:77: bc:d0:75:93:dc:a5:fa:fd:e1:ba:39:35:23:6f:58: 0d:97:13:51 exponent2: 58:7e:3c:91:6c:8b:a3:63:aa:37:78:bb:29:dc:f3: 0b:f1:dd:a5:11:e2:ae:b0:f9:ab:34:32:80:d6:a3: 43:9c:41:ab:b6:ba:5c:2a:1b:ae:6d:8d:c0:c3:f8: 77:5d:f3:11:0f:94:b9:58:2c:fd:2e:1a:41:c5:29: 98:ba:3f:f1 coefficient: 00:d1:f5:1d:1e:f8:36:04:54:60:6a:ff:70:54:e5: f6:57:b7:00:fa:6e:d1:e7:5b:cd:b2:01:ab:a6:27: a6:64:a9:d0:bf:5c:ec:da:fa:0f:2f:70:4a:0c:82: 8e:9e:75:b6:2c:fc:94:80:0c:10:9c:21:01:99:0d: fd:ae:ef:ee:05 -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDLSZN0cSKor2xr8SaVDFkU5rka3/A3upvNGp2mksGXAxsaVug+ 8jk+6Dw1n3QmuzMcrvizN1Ar8Vfd7ghYEvcrZVTR0+c/XAGt7RVFx6j3s6usYtU+ FQR67edSn5mQ7ZgbFzDHsl83tXNBWm915CWNMktT6pycHiK0TQOjtTGUyQIDAQAB AoGBAMi9owp9HciNUfdVbtAIHX9yRp+vOzsM3wUunzb5/Iju3DiqUTS3ZKgmFC6v grcsbFCKx7PRjy2VaTsR7tNqBv/kxkWkYBuvIvjqNWUKjBoJ9XBR4pWpKjQt+NJj b5oxg7CTa8BUAgQOtlI7M/kH2DgiqvW5NAsiJ/nwVGjADqIBAkEA5b6ct4Mm/wgN S8DI5L+rwRLXeEOTmDzyFM/B99hKm6WqXX1fDB1uHlStIVxjaIR2r5a0ZMLUJK15 d+gilZ3J+QJBAOKE715ZnsAwxTVQL9w+zT6xGFtDvVVlhY5fOdVleqh5t/K5vS0/ 10pGDbi5wCQPV7CYhR0YeHB/a2BK/CEdVVECQCTfIJu7KskHa8ral+NOd6w27+nX PprSPS/l4AV86wxr3BnBAz4YgxHPGTIfd7zQdZPcpfr94bo5NSNvWA2XE1ECQFh+ PJFsi6Njqjd4uync8wvx3aUR4q6w+as0MoDWo0OcQau2ulwqG65tjcDD+Hdd8xEP lLlYLP0uGkHFKZi6P/ECQQDR9R0e+DYEVGBq/3BU5fZXtwD6btHnW82yAaumJ6Zk qdC/XOza+g8vcEoMgo6edbYs/JSADBCcIQGZDf2u7+4F -----END RSA PRIVATE KEY-----
-
usr_public.txt 里面各参数的意义如下:
RSAPublicKey ::= SEQUENCE { version Version, modulus INTEGER, -- n publicExponent INTEGER, -- e otherPrimeInfos OtherPrimeInfos OPTIONAL }
usr_private.txt 里面各参数的意义如下:
RSAPrivateKey ::= SEQUENCE { version Version, modulus INTEGER, -- n publicExponent INTEGER, -- e privateExponent INTEGER, -- d prime1 INTEGER, -- p1 prime2 INTEGER, -- p2 exponent1 INTEGER, -- d mod (p1-1) exponent2 INTEGER, -- d mod (p2-1) coefficient INTEGER, -- (inverse of p2) mod p1 otherPrimeInfos OtherPrimeInfos OPTIONAL }
-
macOS 下 RSA 证书生成过程
上述例子中,虽然可以通过终端直接使用 usr_private.pem 和 usr_public.pem 对数据进行加密和解密。
但是,在实际开发过程中,一般是使用认证过的 usr_rasCert.der 和 usr_rsaCert.p12 对数据进行加密和解密。
通过 usr_private.pem 获取 usr_rasCert.der 和 usr_rsaCert.p12 的流程如下图所示:
该流程简述如下:- 用户通过 OpenSSL 生成原始的私钥文件 usr_private.pem
- 用户通过 OpenSSL 从原始的私钥文件 usr_private.pem 中提取原始的公钥文件 usr_public.pem
- 用户通过 OpenSSL 从原始的私钥文件 usr_private.pem 中导出 证书签名请求文件 usr_rsaCert.csr,并提交给 证书颁发机构CA 进行审核。在导出 usr_rsaCert.csr 的过程中,需要用户填入一些审核时需要用到的信息。
- 证书颁发机构CA 对 用户提交的 usr_rsaCert.csr 文件审核通过后,使用自己的私钥 ca_private.pem 对用户提交的公钥和用户信息进行签名,证明用户提交的 usr_rsaCert.csr 文件真实有效,并返回 证书文件 usr_rsaCert.crt 给用户。
- 用户拿到 证书文件 usr_rsaCert.crt 后,通过 OpenSSL 生成项目中需要用到的公钥和私钥。
用户通过 OpenSSL 和 usr_rsaCert.crt 生成 经 CA 认证过的公钥文件 usr_rsaCert.der
用户通过 OpenSSL 和 usr_private.pem 生成 自己独有的私钥文件 usr_rsaCert.p12
使用 macOS 终端实操该流程:
-
如果 SSL 证书经过专业的 证书颁发机构 签名,会收取昂贵的费用。所以我们采取自签名:自己即当请求证书的用户,也扮演颁发证书的 CA。为此,预先生成 CA 的 原始私钥 ca_private.pem 和 原始公钥 ca_public.pem
// 生成 ca_private.pem $ openssl genrsa -out ca_private.pem 1024 Generating RSA private key, 1024 bit long modulus ..........................++++++ ..............................++++++ e is 65537 (0x10001) // 生成 ca_public.pem $ openssl rsa -in ca_private.pem -pubout -out ca_public.pem writing RSA key
-
用户通过 OpenSSL 生成 RSA 原始私钥 usr_private.pem 和 RSA 原始公钥 usr_public.pem
// 生成 usr_private.pem $ openssl genrsa -out usr_private.pem 1024 Generating RSA private key, 1024 bit long modulus ..........++++++ ...++++++ e is 65537 (0x10001) // 生成 usr_public.pem $ openssl rsa -in usr_private.pem -pubout -out usr_public.pem writing RSA key
-
用户通过 OpenSSL 从 RSA 原始私钥 usr_private.pem 中导出证书签名请求文件 usr_rsaCert.csr
注意:通过 macOS 的 钥匙串 同样能生成 csr 文件$ openssl req -new -key usr_private.pem -out rsaCert.csr You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) []:cn State or Province Name (full name) []:FuJian Locality Name (eg, city) []:XiaMen Organization Name (eg, company) []:XMUT Organizational Unit Name (eg, section) []:CS Common Name (eg, fully qualified host name) []:www.xmut.com Email Address []:123456@xmut.com Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []:123456
-
证书颁发机构 CA 使用自己的 RSA 原始私钥 ca_private.pem 对用户提交的 证书签名请求文件 usr_rsaCert.csr 进行签名 生成 证书文件 usr_rsaCert.crt
// 生成 usr_rsaCert.crt // 使用 x509 标准格式,签名有效期 3650 天,对 rsacert.csr 文件 使用(证书颁发机构的原始私钥) ca_private.pem 进行签名,签名后输出文件 rsacert.crt $ openssl x509 -req -days 3650 -in rsaCert.csr -signkey ca_private.pem -out rsaCert.crt Signature ok subject=/C=cn/ST=Fujian/L=Xiamen/O=XMUT/OU=CS/CN=www.xmut.com/emailAddress=123456@xmut.com Getting Private key // 查看 usr_rsaCert.crt $ cat rsaCert.crt -----BEGIN CERTIFICATE----- MIICfTCCAeYCCQDVzMx+mGrv/jANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMC Y24xDzANBgNVBAgMBkZ1amlhbjEPMA0GA1UEBwwGWGlhbWVuMQ0wCwYDVQQKDARY TVVUMQswCQYDVQQLDAJDUzEVMBMGA1UEAwwMd3d3LnhtdXQuY29tMR4wHAYJKoZI hvcNAQkBFg8xMjM0NTZAeG11dC5jb20wHhcNMjAwNjE2MTExMDAxWhcNMzAwNjE0 MTExMDAxWjCBgjELMAkGA1UEBhMCY24xDzANBgNVBAgMBkZ1amlhbjEPMA0GA1UE BwwGWGlhbWVuMQ0wCwYDVQQKDARYTVVUMQswCQYDVQQLDAJDUzEVMBMGA1UEAwwM d3d3LnhtdXQuY29tMR4wHAYJKoZIhvcNAQkBFg8xMjM0NTZAeG11dC5jb20wgZ8w DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOAGXA7kFeCgZyLL59636Gj+q51Bz5aq DqmkEQPFdxeYt+eB9qJUfu4a26NGCESuUporJXS9XgDe29Mv3Q6Yhg9zpxd28a3L uqr3NKN2smNRtUl6BNJaK2UZ+T02m3l/O7sws743BBQpzAljpsJ3shQqSrsXW+Oa epxfJZrH2oiHAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAfIyQnyyJDumljyjem3W1 aStYzfn27Wvs+7cagMzmqsQ6jm7jrOrqPpNzmM9R8yqGTE0cUCrl3EVdfOqau9CN hITKXxJpV27BGL0O8CdEM6NETRSSRD6wJU0kJk5Qo+oY4PmhWcV4nlR6tGfsA5FT 1qeDoeZyScUHELcC3pbmWX4= -----END CERTIFICATE-----
-
用户通过 OpenSSL 从 证书文件 usr_rsaCert.crt 中导出项目中需要用到的公钥和私钥
// 导出项目中需要使用的公钥 rsaCert.der $ openssl x509 -outform der -in rsaCert.crt -out rsaCert.der // 查看公钥 rsaCert.der 里面的信息 $ openssl x509 -in rsaCert.der -inform der -text -out rsaCert_der.txt Certificate: Data: Version: 1 (0x0) Serial Number: 15405913269422714878 (0xd5cccc7e986aeffe) Signature Algorithm: sha1WithRSAEncryption Issuer: C=cn, ST=Fujian, L=Xiamen, O=XMUT, OU=CS, CN=www.xmut.com/emailAddress=123456@xmut.com Validity Not Before: Jun 16 11:10:01 2020 GMT Not After : Jun 14 11:10:01 2030 GMT Subject: C=cn, ST=Fujian, L=Xiamen, O=XMUT, OU=CS, CN=www.xmut.com/emailAddress=123456@xmut.com Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (1024 bit) Modulus: 00:e0:06:5c:0e:e4:15:e0:a0:67:22:cb:e7:de:b7: e8:68:fe:ab:9d:41:cf:96:aa:0e:a9:a4:11:03:c5: 77:17:98:b7:e7:81:f6:a2:54:7e:ee:1a:db:a3:46: 08:44:ae:52:9a:2b:25:74:bd:5e:00:de:db:d3:2f: dd:0e:98:86:0f:73:a7:17:76:f1:ad:cb:ba:aa:f7: 34:a3:76:b2:63:51:b5:49:7a:04:d2:5a:2b:65:19: f9:3d:36:9b:79:7f:3b:bb:30:b3:be:37:04:14:29: cc:09:63:a6:c2:77:b2:14:2a:4a:bb:17:5b:e3:9a: 7a:9c:5f:25:9a:c7:da:88:87 Exponent: 65537 (0x10001) Signature Algorithm: sha1WithRSAEncryption 7c:8c:90:9f:2c:89:0e:e9:a5:8f:28:de:9b:75:b5:69:2b:58: cd:f9:f6:ed:6b:ec:fb:b7:1a:80:cc:e6:aa:c4:3a:8e:6e:e3: ac:ea:ea:3e:93:73:98:cf:51:f3:2a:86:4c:4d:1c:50:2a:e5: dc:45:5d:7c:ea:9a:bb:d0:8d:84:84:ca:5f:12:69:57:6e:c1: 18:bd:0e:f0:27:44:33:a3:44:4d:14:92:44:3e:b0:25:4d:24: 26:4e:50:a3:ea:18:e0:f9:a1:59:c5:78:9e:54:7a:b4:67:ec: 03:91:53:d6:a7:83:a1:e6:72:49:c5:07:10:b7:02:de:96:e6: 59:7e
// 导出项目中需要使用的私钥 rsaCert.p12 // 因为 p12文件里面包含了私钥等重要信息,所以导出 p12 文件 默认需要输入密码 // 输入的密码用于对 p12 文件中的私钥进行加密 $ openssl pkcs12 -export -inkey usr_private.pem -in rsaCert.crt -out rsaCert.p12 Enter Export Password: Verifying - Enter Export Password: // 查看私钥 rsaCert.12 里面的信息 $ openssl pkcs12 -in rsaCert.p12 -info Enter Import Password: MAC Iteration 2048 MAC verified OK PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 2048 Certificate bag Bag Attributes localKeyID: 52 7B C2 5D C9 30 72 C7 08 D9 33 E9 B0 12 FF D1 7A E3 EE 69 subject=/C=cn/ST=Fujian/L=Xiamen/O=XMUT/OU=CS/CN=www.xmut.com/emailAddress=123456@xmut.com issuer=/C=cn/ST=Fujian/L=Xiamen/O=XMUT/OU=CS/CN=www.xmut.com/emailAddress=123456@xmut.com -----BEGIN CERTIFICATE----- MIICfTCCAeYCCQDVzMx+mGrv/jANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMC Y24xDzANBgNVBAgMBkZ1amlhbjEPMA0GA1UEBwwGWGlhbWVuMQ0wCwYDVQQKDARY TVVUMQswCQYDVQQLDAJDUzEVMBMGA1UEAwwMd3d3LnhtdXQuY29tMR4wHAYJKoZI hvcNAQkBFg8xMjM0NTZAeG11dC5jb20wHhcNMjAwNjE2MTExMDAxWhcNMzAwNjE0 MTExMDAxWjCBgjELMAkGA1UEBhMCY24xDzANBgNVBAgMBkZ1amlhbjEPMA0GA1UE BwwGWGlhbWVuMQ0wCwYDVQQKDARYTVVUMQswCQYDVQQLDAJDUzEVMBMGA1UEAwwM d3d3LnhtdXQuY29tMR4wHAYJKoZIhvcNAQkBFg8xMjM0NTZAeG11dC5jb20wgZ8w DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOAGXA7kFeCgZyLL59636Gj+q51Bz5aq DqmkEQPFdxeYt+eB9qJUfu4a26NGCESuUporJXS9XgDe29Mv3Q6Yhg9zpxd28a3L uqr3NKN2smNRtUl6BNJaK2UZ+T02m3l/O7sws743BBQpzAljpsJ3shQqSrsXW+Oa epxfJZrH2oiHAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAfIyQnyyJDumljyjem3W1 aStYzfn27Wvs+7cagMzmqsQ6jm7jrOrqPpNzmM9R8yqGTE0cUCrl3EVdfOqau9CN hITKXxJpV27BGL0O8CdEM6NETRSSRD6wJU0kJk5Qo+oY4PmhWcV4nlR6tGfsA5FT 1qeDoeZyScUHELcC3pbmWX4= -----END CERTIFICATE----- PKCS7 Data Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2048 Bag Attributes localKeyID: 52 7B C2 5D C9 30 72 C7 08 D9 33 E9 B0 12 FF D1 7A E3 EE 69 Key Attributes: <No Attributes>
Objective-C 使用 RSA
-
工程中添加 rsaCert.der 文件和 rsaCert.p12 文件
rsaCert.der 文件描述如下:
rsaCert.p12 文件描述如下:
-
工程中添加 RSA 加密和解密第三方库:RSACryptor.h 和 RSACryptor.m
#import <Foundation/Foundation.h> @interface RSACryptor : NSObject // 获取单例 +(instancetype)sharedRSACryptor; // 生成密钥对 // @param keySize 密钥尺寸,可选数值(512 / 1024 / 2048) -(void)generateKeyPair:(NSUInteger)keySize; // 加载公钥 // @param publicKeyPath 公钥路径 // @code // # 生成原始私钥文件 // $ openssl genrsa -out ca_private.key 1024 // # 创建证书签名请求文件 // $ openssl req -new -key ca_private.key -out rsaCert.csr // # 生成证书文件并签名 // $ openssl x509 -req -days 3650 -in rsaCert.csr -signkey ca_private.key -out rsaCert.crt // # 对证书文件进行转换格式,生成工程中使用的公钥文件 // $ openssl x509 -outform der -in rsaCert.crt -out rsaCert.der // @endcode -(void)loadPublicKey:(NSString *)publicKeyPath; // 加载私钥 // @param privateKeyPath p12文件路径 // @param password p12文件密码 // @code // # 对证书文件进行转换格式,生成工程中使用的私钥文件 // openssl pkcs12 -export -out rsaCert.p12 -inkey ca_private.key -in rsaCert.crt // @endcode -(void)loadPrivateKey:(NSString *)privateKeyPath password:(NSString *)password; // 加密数据 // @param plainData 明文数据 // @return 密文数据 -(NSData *)encryptData:(NSData *)plainData; // 解密数据 // @param cipherData 密文数据 // @return 明文数据 -(NSData *)decryptData:(NSData *)cipherData; @end
#import "RSACryptor.h" // 填充模式 #define kTypeOfWrapPadding kSecPaddingPKCS1 // 公钥/私钥 标签 #define kPublicKeyTag "com.xmut.RSACryptorDemo.publickey" #define kPrivateKeyTag "com.xmut.RSACryptorDemo.privatekey" static const uint8_t publicKeyIdentifier[] = kPublicKeyTag; static const uint8_t privateKeyIdentifier[] = kPrivateKeyTag; @interface RSACryptor() { SecKeyRef publicKeyRef; // 公钥引用 SecKeyRef privateKeyRef; // 私钥引用 } @property (nonatomic, retain) NSData *publicTag; // 公钥标签 @property (nonatomic, retain) NSData *privateTag; // 私钥标签 @end @implementation RSACryptor #pragma mark - 构造单例 +(instancetype)sharedRSACryptor { static id instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; } -(instancetype)init { self = [super init]; if (self) { // 查询密钥的标签 _privateTag = [[NSData alloc] initWithBytes:privateKeyIdentifier length:sizeof(privateKeyIdentifier)]; _publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)]; } return self; } #pragma mark - 加密 & 解密数据 -(NSData *)encryptData:(NSData *)plainData { OSStatus sanityCheck = noErr; size_t cipherBufferSize = 0; size_t keyBufferSize = 0; NSAssert(plainData != nil, @"明文数据为空"); NSAssert(publicKeyRef != nil, @"公钥为空"); NSData *cipher = nil; uint8_t *cipherBuffer = NULL; // 计算缓冲区大小 cipherBufferSize = SecKeyGetBlockSize(publicKeyRef); keyBufferSize = [plainData length]; if (kTypeOfWrapPadding == kSecPaddingNone) { NSAssert(keyBufferSize <= cipherBufferSize, @"加密内容太大"); } else { NSAssert(keyBufferSize <= (cipherBufferSize - 11), @"加密内容太大"); } // 分配缓冲区 cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t)); memset((void *)cipherBuffer, 0x0, cipherBufferSize); // 使用公钥加密 sanityCheck = SecKeyEncrypt(publicKeyRef, kTypeOfWrapPadding, (const uint8_t *)[plainData bytes], keyBufferSize, cipherBuffer, &cipherBufferSize ); NSAssert(sanityCheck == noErr, @"加密错误,OSStatus == %d", sanityCheck); // 生成密文数据 cipher = [NSData dataWithBytes:(const void *)cipherBuffer length:(NSUInteger)cipherBufferSize]; if (cipherBuffer) free(cipherBuffer); return cipher; } -(NSData *)decryptData:(NSData *)cipherData { OSStatus sanityCheck = noErr; size_t cipherBufferSize = 0; size_t keyBufferSize = 0; NSData *key = nil; uint8_t *keyBuffer = NULL; SecKeyRef privateKey = NULL; privateKey = [self getPrivateKeyRef]; NSAssert(privateKey != NULL, @"私钥不存在"); // 计算缓冲区大小 cipherBufferSize = SecKeyGetBlockSize(privateKey); keyBufferSize = [cipherData length]; NSAssert(keyBufferSize <= cipherBufferSize, @"解密内容太大"); // 分配缓冲区 keyBuffer = malloc(keyBufferSize * sizeof(uint8_t)); memset((void *)keyBuffer, 0x0, keyBufferSize); // 使用私钥解密 sanityCheck = SecKeyDecrypt(privateKey, kTypeOfWrapPadding, (const uint8_t *)[cipherData bytes], cipherBufferSize, keyBuffer, &keyBufferSize ); NSAssert1(sanityCheck == noErr, @"解密错误,OSStatus == %d", sanityCheck); // 生成明文数据 key = [NSData dataWithBytes:(const void *)keyBuffer length:(NSUInteger)keyBufferSize]; if (keyBuffer) free(keyBuffer); return key; } #pragma mark - 密钥处理 // 生成密钥对 -(void)generateKeyPair:(NSUInteger)keySize { OSStatus sanityCheck = noErr; publicKeyRef = NULL; privateKeyRef = NULL; NSAssert1((keySize == 512 || keySize == 1024 || keySize == 2048), @"密钥尺寸无效 %tu", keySize); // 删除当前密钥对 [self deleteAsymmetricKeys]; // 容器字典 NSMutableDictionary *privateKeyAttr = [[NSMutableDictionary alloc] init]; NSMutableDictionary *publicKeyAttr = [[NSMutableDictionary alloc] init]; NSMutableDictionary *keyPairAttr = [[NSMutableDictionary alloc] init]; // 设置密钥对的顶级字典 [keyPairAttr setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType]; [keyPairAttr setObject:[NSNumber numberWithUnsignedInteger:keySize] forKey:(__bridge id)kSecAttrKeySizeInBits]; // 设置私钥字典 [privateKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecAttrIsPermanent]; [privateKeyAttr setObject:_privateTag forKey:(__bridge id)kSecAttrApplicationTag]; // 设置公钥字典 [publicKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecAttrIsPermanent]; [publicKeyAttr setObject:_publicTag forKey:(__bridge id)kSecAttrApplicationTag]; // 设置顶级字典属性 [keyPairAttr setObject:privateKeyAttr forKey:(__bridge id)kSecPrivateKeyAttrs]; [keyPairAttr setObject:publicKeyAttr forKey:(__bridge id)kSecPublicKeyAttrs]; // SecKeyGeneratePair 返回密钥对引用 sanityCheck = SecKeyGeneratePair((__bridge CFDictionaryRef)keyPairAttr, &publicKeyRef, &privateKeyRef); NSAssert((sanityCheck == noErr && publicKeyRef != NULL && privateKeyRef != NULL), @"生成密钥对失败"); } // 加载公钥 -(void)loadPublicKey:(NSString *)publicKeyPath { NSAssert(publicKeyPath.length != 0, @"公钥路径为空"); // 删除当前公钥 if (publicKeyRef) CFRelease(publicKeyRef); // 从一个 DER 表示的证书创建一个证书对象 NSData *certificateData = [NSData dataWithContentsOfFile:publicKeyPath]; SecCertificateRef certificateRef = SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)certificateData); NSAssert(certificateRef != NULL, @"公钥文件错误"); // 返回一个默认 X509 策略的公钥对象,使用之后需要调用 CFRelease 释放 SecPolicyRef policyRef = SecPolicyCreateBasicX509(); // 包含信任管理信息的结构体 SecTrustRef trustRef; // 基于证书和策略创建一个信任管理对象 OSStatus status = SecTrustCreateWithCertificates(certificateRef, policyRef, &trustRef); NSAssert(status == errSecSuccess, @"创建信任管理对象失败"); // 信任结果 SecTrustResultType trustResult; // 评估指定证书和策略的信任管理是否有效 status = SecTrustEvaluate(trustRef, &trustResult); NSAssert(status == errSecSuccess, @"信任评估失败"); // 评估之后返回公钥子证书 publicKeyRef = SecTrustCopyPublicKey(trustRef); NSAssert(publicKeyRef != NULL, @"公钥创建失败"); if (certificateRef) CFRelease(certificateRef); if (policyRef) CFRelease(policyRef); if (trustRef) CFRelease(trustRef); } // 加载私钥 -(void)loadPrivateKey:(NSString *)privateKeyPath password:(NSString *)password { NSAssert(privateKeyPath.length != 0, @"私钥路径为空"); // 删除当前私钥 if (privateKeyRef) CFRelease(privateKeyRef); NSData *PKCS12Data = [NSData dataWithContentsOfFile:privateKeyPath]; CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data; CFStringRef passwordRef = (__bridge CFStringRef)password; // 从 PKCS #12 证书中提取标示和证书 SecIdentityRef myIdentity; SecTrustRef myTrust; const void *keys[] = {kSecImportExportPassphrase}; const void *values[] = {passwordRef}; CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); // 返回 PKCS #12 格式数据中的标示和证书 OSStatus status = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items); if (status == noErr) { CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0); myIdentity = (SecIdentityRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity); myTrust = (SecTrustRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemTrust); } if (optionsDictionary) CFRelease(optionsDictionary); NSAssert(status == noErr, @"提取身份和信任失败"); SecTrustResultType trustResult; // 评估指定证书和策略的信任管理是否有效 status = SecTrustEvaluate(myTrust, &trustResult); NSAssert(status == errSecSuccess, @"信任评估失败"); // 提取私钥 status = SecIdentityCopyPrivateKey(myIdentity, &privateKeyRef); NSAssert(status == errSecSuccess, @"私钥创建失败"); } // 删除非对称密钥 -(void)deleteAsymmetricKeys { OSStatus sanityCheck = noErr; NSMutableDictionary *queryPublicKey = [[NSMutableDictionary alloc] init]; NSMutableDictionary *queryPrivateKey = [[NSMutableDictionary alloc] init]; // 设置公钥查询字典 [queryPublicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass]; [queryPublicKey setObject:_publicTag forKey:(__bridge id)kSecAttrApplicationTag]; [queryPublicKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType]; // 设置私钥查询字典 [queryPrivateKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass]; [queryPrivateKey setObject:_privateTag forKey:(__bridge id)kSecAttrApplicationTag]; [queryPrivateKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType]; // 删除私钥 sanityCheck = SecItemDelete((__bridge CFDictionaryRef)queryPrivateKey); NSAssert1((sanityCheck == noErr || sanityCheck == errSecItemNotFound), @"删除私钥错误,OSStatus == %d", sanityCheck); // 删除公钥 sanityCheck = SecItemDelete((__bridge CFDictionaryRef)queryPublicKey); NSAssert1((sanityCheck == noErr || sanityCheck == errSecItemNotFound), @"删除公钥错误,OSStatus == %d", sanityCheck); if (publicKeyRef) CFRelease(publicKeyRef); if (privateKeyRef) CFRelease(privateKeyRef); } // 获得私钥引用 -(SecKeyRef)getPrivateKeyRef { OSStatus sanityCheck = noErr; SecKeyRef privateKeyReference = NULL; if (privateKeyRef == NULL) { NSMutableDictionary * queryPrivateKey = [[NSMutableDictionary alloc] init]; // 设置私钥查询字典 [queryPrivateKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass]; [queryPrivateKey setObject:_privateTag forKey:(__bridge id)kSecAttrApplicationTag]; [queryPrivateKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType]; [queryPrivateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef]; // 获得密钥 sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef)queryPrivateKey, (CFTypeRef *)&privateKeyReference); if (sanityCheck != noErr) { privateKeyReference = NULL; } } else { privateKeyReference = privateKeyRef; } return privateKeyReference; } @end
-
使用 RSACryptor 对数据进行加密和解密
-(void)RSACryptorDemo { // 1.创建要加密的二进制数据 NSString* originString = @"I am hcg, hello world!"; NSData* originData = [originString dataUsingEncoding:NSUTF8StringEncoding]; // 2.创建RSACryptor单例 RSACryptor* rsaCryptor = [RSACryptor sharedRSACryptor]; // 3.加载公钥对二进制数据进行加密 NSString* publicKeyPath = [[NSBundle mainBundle] pathForResource:@"rsaCert.der" ofType:nil]; [rsaCryptor loadPublicKey:publicKeyPath]; NSData* encryptData0 = [rsaCryptor encryptData:originData]; // 4.将加密后的二进制数据转换为 Base64 编码 NSString* base64String = [encryptData0 base64EncodedStringWithOptions:0]; NSLog(@"base64String = %@", base64String); /* 可以通过设置加密时的填充模式,让同一个字符串每次 RSA 加密生成的结果,都不一样,即可以对数据进行加盐 输出结果 1 : base64String = D+Ll011zKy+nzzcI92p29mS6fjoCV/I656MXZTegl/dtj5x9zBjyklk9YRKyi8MrZPAtylB1c/+STe/peDaLcKU18eN40HDewJpwxetlTt1Wjt5tVDMvIwlWsROYh7kV0JywZrBjvLe5gQMfVvRp4inuql9Yz+5XKPYkQUTkywc= 输出结果 2 : base64String = BL6JRzjsx5S7cRPpOnFri+16Ypcz6b28QynP8uWAe8bEmnkyY3ELVcjfHAnHEvqaAhuOapfmv3GD/z+5HdDLsVWQqdH9z7chrLSDmccbHzF38Jrk+LiaKM976O4TQtXzIJwrPBJw8rbceQYzafnxIrz/R8wK+cbBfOMMeljryRI= */ // 5.将 Base64 字符串解码为加密后的二进制数据 NSData* encryptData1 = [[NSData alloc] initWithBase64EncodedString:base64String options:0]; // 6.加载私钥对解码后的二进制数据进行解密 NSString* privateKeyPath = [[NSBundle mainBundle] pathForResource:@"rsaCert.p12" ofType:nil]; [rsaCryptor loadPrivateKey:privateKeyPath password:@"123456"]; NSData* decryptData = [rsaCryptor decryptData:encryptData1]; // 7.还原解密后的数据格式 NSString* resultString = [[NSString alloc] initWithData:decryptData encoding:NSUTF8StringEncoding]; NSLog(@"resultString = %@", resultString); /* 输出结果 : resultString = I am hcg, hello world! */ }
注意
- p12 文件里面包含了:公钥、私钥、用户信息。所以既可以通过 Certificate.crt + PrivateKey.pem 生成 p12,也可以通过 p12 生成 PublicKey.pem 和 PrivateKey.pem
openssl pkcs12 -in certificate.p12 -nocerts -out private.pem openssl pkcs12 -in certificate.p12 -clcerts -nokeys -out public.pem