密码学(二)

Base64编码

  • Base64编码简介

    一般情况下,如果用记事本直接打开 .exe、.jpg、.pdf、… 等格式的文件时,会看到一大堆乱码,因为二进制文件包含很多无法显示和打印的字符。所以,如果要让像记事本这样的文本处理软件能处理和显示二进制数据,就需要一个二进制数据到字符串的转换方法。Base64 就是一种最常见的二进制编码方法。

    Base64 是一种以 64 个可见字符集对二进制数据进行编码的编码算法(即,Base64 是一种用 64 个字符来表示任意二进制数据的方法)。

  • Base64编码原理

    Base64编码的原理如下:

    1. 准备一个包含 64 个可见字符的数组构成一张编码表。
    2. 将二进制数据每 3 Byte(共 24 bit) 分为一组,然后将每组均分为 4 段,每段 6 bit。依次将得到的每个段的值,作为索引进行查表,得到对应的编码字符。
    3. 因为二进制数据每 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编码 中 = 号只会出现在最后面。

    一些注意点:

    1. 用于 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文本 会造成一定程度的存储空间浪费 ,但是编码后的文本数据可以在邮件正文、网页等直接显示,易于阅读与传输。
    2. 为什么对二进制数据进行 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)个字符
      … …
    3. 可以自定义 Base64编码,即自定义 64 个编码字符的排列顺序。但是即使使用自定义的编码表,也不能将 Base64编码 用于数据加密。因为 Base64编码 是一种通过查表的编码方法,很容易通过枚举,反算出自定义的编码表。
    4. Base64 适用于小段内容的编码,比如数字证书签名、Cookie的内容等。
    5. Base64 适用于少量二进制数据的显式和传输。
    6. 大多数编码都是由字符串转换成二进制,而 Base64编码 则是从二进制转换为字符串。
    7. Base64 讲到底只是一种编码规则,其底层的操作的对象都是二进制数据,即 Base64 底层是将 源二进制数据 通过Base64编码表 映射成 目标二进制数据。源二进制数据可以 是任意格式的文件,而目标二进制数据,由于 Base64 编码表的约束,恰好都可以转成相应的编码字符。
  • Base64编码举例

    标准的 Base64编码表 包含的可打印字符依次为:A ~ Z、a ~ z、0 ~ 9、+、\
    如下图所示:
    标准的 Base64编码表
    对字符串 Man 进行 Base64 编码:

    1. 将字符串 Man 转换为二进制数据,在这里将 Man 转换为单字节的 ASCII 码字符:
      M、a、n 对应的 ASCII 码值分别为 77,97,110
      对应的二进制值分别为 01001101、01100001、01101110,共 24 bit
    2. 将 24 bit 每 6 bit 分为一段,总共分为 4 段,如图红框所示
    3. 算出每段的值:19、 22 、 5 、 46 作为索引进行查表,获取对应的 Base64 编码字符:T、W、F、u,即 字符串 Man,在 ASCII 编码下,对应的 Base64编码 为 TWFu
      将字符串 Man 进行 Base64 编码

    对字符串 ABCD 进行 Base64 编码:

    1. 将字符串 ABCD 转换为二进制数据,在这里将 ABCD 转换为单字节的 ASCII 码字符:
      A、B、C、D 对应的 ASCII 码值分别为 0x41,0x42,0x43,0x44
      对应的二进制值分别为 01000001、01000010、01000011、01000100,共 32 bit
    2. 因为字符串 ABCD 的 ASCII 码占 4 Byte,所以在末尾补 2 Byte 的 00000000,凑成 6 Byte,共 48 bit。将 48 bit 每 6 bit 分为一段,总共分为 8 段,如下图所示
    3. 算出每段的值:16、 20 、 9 、 3、 17、 0 作为索引进行查表,获取对应的 Base64 编码字符:Q、U、J、D、R、A,因为最后两段的索引值:0 、0,是为了凑齐 6 Byte 补全的,所以不查表,直接输出 = 号。即 字符串 ABCD,在 ASCII 编码下,对应的 Base64编码 为 QUJDRA==
      对字符串 ABCD 进行 Base64 编码
  • 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编码 拓展

  1. 标准的 Base64编码 可能出现字符 + 和 /,在 URL 中就不能直接作为参数,所以有一种 URL Safe 的 base64编码:把字符 + 和 / 分别替换成 - 和 _
  2. 字符 = 也可能出现在 Base64编码 中,但 = 用在URL、Cookie 里面会造成歧义,所以很多 Base64编码 后会把 = 去掉。那去掉 = 后怎么解码呢?因为 Base64编码 是把 3 Byte 的二进制数据 变为4 Byte 的编码字符,所以,Base64编码 的长度永远是 4 的倍数,因此,解码时,根据需要加上 = 把 Base64字符串 的长度变为 4 的倍数,就可以正常解码了。
  3. 密码学中经常使用 Base64,使用流程一般为:
    1. 发送方对数据进行加密,但是加密后的数据不便于查看和传递,因此对加密后的数据进行 Base64 编码后,再发送数据给接收方
    2. 接收方接收到数据后,先对数据进行 Base64 解码,将数据还原成加密后的形态,然后再对解码后的数据,进行解密。

SSL 与 OpenSSL

  • SSL 简介

    SSL(Secure Socket Layer):安全套接字层,由网景公司(Netscape)所研发的一种建立在 TCP 之上的安全通信协议。主要是防止信息在互联网上传输的时,被窃听或者篡改。
    SSL 的体系结构中包含两个协议子层:

    1. SSL 记录协议(SSL Record Protocol):位于网络层,建立在可靠的传输协议(如 TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。
    2. 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 主要包括以下三个组件:

    1. openssl:多用途的命令行工具
    2. libcrypto:加密算法库
    3. libssl:加密模块应用库,实现了 SSL 及 TLS

    OpenSSL 可以实现:秘钥证书管理、对称加密 和 非对称加密

    OpenSSL 一共实现了 8 种对称加密算法,其中有 7 种分组加密算法:AES、DES、Blowfish、CAST、IDEA、RC2、RC5;1 种流加密算法:RC4

    OpenSSL 一共实现了 4 种非对称加密算法,包括:

    1. DH 算法:一般用于密钥交换,即 迪菲-赫尔曼秘钥交换算法
    2. RSA 算法:既可以用于密钥交换,也可以用于数字签名
    3. DSA 算法:DSA算法一般只用于数字签名
    4. EC算法(椭圆曲线算法)

macOS 下 RSA 证书生成过程

  • 常用文件格式介绍

    .pemPrivacy Enhanced Mail):OpenSSL 使用 pem 文件格式存储:证书(Certificate)、公钥(PublicKey)、私钥(PrivateKey)。

    pem 文件一般为文本格式:
    以 -----BEGIN… 开头
    以 -----END… 结尾
    中间的内容是 Base64 编码

    -----BEGIN... 
    //  Base64编码
    -----END... 
    

    .csrCertificate Signing Request):证书签名请求文件,csr 文件是向证书颁发机构(Certificate Authority)申请证书时所需要的一个数据文件,csr 文件包含 用户公钥 和 用户信息。

    csr 文件一般为文本格式:
    以 -----BEGIN CERTIFICATE REQUEST----- 开头
    以 -----END CERTIFICATE REQUEST----- 结尾
    中间的内容是 Base64 编码

    -----BEGIN CERTIFICATE REQUEST-----
    //  Base64编码
    -----END CERTIFICATE REQUEST-----
    

    .crtCertificate):用于存储数字证(Digital Certificate)。书当证书颁发机构(CA)对用户提交的 csr 文件(证书签名请求文件,包含用户公钥和用户信息)审核通过后,会使用自己的私钥对用户提交的公钥和用户信息进行签名,证明用户提交的 csr 文件真实有效。此时证书颁发机构(CA)会返回一个 crt 文件给用户。因此,crt 文件包含:用户的公钥、用户的信息、证书颁发机构对 用户公钥 和 用户信息 的签名。

    crt 文件一般为文本格式:
    以 -----BEGIN CERTIFICATE----- 开头
    以 -----END CERTIFICATE----- 结尾
    中间的内容是 Base64 编码

    -----BEGIN CERTIFICATE-----
    //  Base64编码
    -----END CERTIFICATE-----
    

    .cerCer tificate):用于存储数字证(Digital Certificate)。crt 文件的替代形式。同样地,cer 文件包含:用户的公钥、用户的信息、证书颁发机构对 用户公钥 和 用户信息 的签名。

    .derDistinguished Encoding Rules):通过 crt 文件生成,里面以二进制形式存储了 用户公钥 和 用户信息。

    .p12:(Predecessor of PKCS #12):里面存储的是 用户私钥 和 用户信息,受用户输入的密码保护。用户输入的密码用于加密私钥,以确保私钥的安全:其他人即使拿到了证书备份(p12),在不知道加密私钥的密码的情况下,是无法导入证书的。


  • macOS 下使用 RSA 进行加密和解密

    macOS 内置了 OpenSSL,可以直接通过终端 使用命令行 来进行 RSA 的加密解密。OpenSSL 中关于 RSA 加密解密,常用的指令主要有以下 3 个:

    1. genrsa:生成并输出一个 RSA 私钥
    2. rsautl:使用 RSA 秘钥进行 加密、解密、签名、验证 等运算
    3. rsa:处理 RSA 秘钥的格式转换等问题
  1. 进入 RSA_Test 目录,之后所有的操作,包括证书的生成与输出,都在此目录进行

    $ cd /Users/Airths/Desktop/RSA_Test 
    
  2. 生成用户的 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)
    
  3. 从用户的 RSA 私钥 usr_private.pem 中提取用户的 RSA 公钥 usr_public.pem

    $ openssl rsa -in usr_private.pem -pubout -out usr_public.pem
    writing RSA key
    
  4. 通过 vi 编辑器生成一个名为 message.txt 的测试文本

    // 退出 vi 编辑器
    // 1.按 Esc 键 跳到命令模式
    // 2.在命令模式下输入 :wq 保存文件并退出 vi 编辑器
    $ vi message.txt
    $ cat message.txt 
    I am hcg, hello world!
    
  5. 使用用户的 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?
    
  6. 使用用户的 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!
    
  7. 使用用户的 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?<
    
  8. 使用用户的 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!
    
  9. 查看用户的 RSA 公钥 usr_public.pem

    $ cat usr_public.pem 
    -----BEGIN PUBLIC KEY-----
    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLSZN0cSKor2xr8SaVDFkU5rka
    3/A3upvNGp2mksGXAxsaVug+8jk+6Dw1n3QmuzMcrvizN1Ar8Vfd7ghYEvcrZVTR
    0+c/XAGt7RVFx6j3s6usYtU+FQR67edSn5mQ7ZgbFzDHsl83tXNBWm915CWNMktT
    6pycHiK0TQOjtTGUyQIDAQAB
    -----END PUBLIC KEY-----
    
  10. 查看用户的 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-----
    
  11. 可以看到,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-----
    
  12. 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证书生成流程
    该流程简述如下:

    1. 用户通过 OpenSSL 生成原始的私钥文件 usr_private.pem
    2. 用户通过 OpenSSL 从原始的私钥文件 usr_private.pem 中提取原始的公钥文件 usr_public.pem
    3. 用户通过 OpenSSL 从原始的私钥文件 usr_private.pem 中导出 证书签名请求文件 usr_rsaCert.csr,并提交给 证书颁发机构CA 进行审核。在导出 usr_rsaCert.csr 的过程中,需要用户填入一些审核时需要用到的信息。
    4. 证书颁发机构CA 对 用户提交的 usr_rsaCert.csr 文件审核通过后,使用自己的私钥 ca_private.pem 对用户提交的公钥和用户信息进行签名,证明用户提交的 usr_rsaCert.csr 文件真实有效,并返回 证书文件 usr_rsaCert.crt 给用户。
    5. 用户拿到 证书文件 usr_rsaCert.crt 后,通过 OpenSSL 生成项目中需要用到的公钥和私钥。
      用户通过 OpenSSL 和 usr_rsaCert.crt 生成 经 CA 认证过的公钥文件 usr_rsaCert.der
      用户通过 OpenSSL 和 usr_private.pem 生成 自己独有的私钥文件 usr_rsaCert.p12

    使用 macOS 终端实操该流程:

    1. 如果 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
      
    2. 用户通过 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
      
    3. 用户通过 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
      
    4. 证书颁发机构 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-----
      
    5. 用户通过 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 文件描述如下:
    derFileDescription
    rsaCert.p12 文件描述如下:
    p12FileDescription

  • 工程中添加 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
    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值