如何在没有任何 API 的情况下生成 RSA 公钥仅使用模数和公共指数

问题描述

如何在没有任何 API 的情况下生成 RSA 公钥。仅使用模数和公共指数。

在我们的项目中,我们通过模数和公共指数实现 RSA PKCS#8 公钥。 但是我们不知道 PKCS#8 公钥的 ASN.1 二进制文件的含义。

为了理解 PKCS#8 公钥 ASN.1,我们确实吹了。

 1. openssl genrsa -out hoge.key 2048  
 2. openssl rsa -pubout -in hoge.key  

Here we get below infomation.

> -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7z2oyePt5vNbH7Pbieiw
BOgRnCUyyUvUo6Wi+uqUWvMxrji1vH21ViTZYLjg40RrulSCGFwjzwnI4AMtEdIZ
7uOol12E3xOZYNgwTBaDNCT9p0IYYuFVGfQyxlavr7oSIaaNmlSRy+0os1xi7IiI
PCHE/7nfifDQiqGtb6b6TBOwP3QXg5IdrXiqQJAlk+8S0XPhbnwwzWOhlrR3Wftq
jylBPSGSDJQoF0fJ5h2cA2yJiGqTV37YRTThPWmQEuz8Njx4bTaEaTul5/UNhSel
s7khd/IvHV9oN6T2o4V//fAsyjRZlYKEUHldb3ML/QHxWs7+hqWSa9NCwwXZGhEl
wwIDAQAB
-----END PUBLIC KEY-----

> 0000000 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d 01 01
0000010 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 01 01
0000020 00 ef 3d a8 c9 e3 ed e6 f3 5b 1f b3 db 89 e8 b0
0000030 04 e8 11 9c 25 32 c9 4b d4 a3 a5 a2 fa ea 94 5a
0000040 f3 31 ae 38 b5 bc 7d b5 56 24 d9 60 b8 e0 e3 44
0000050 6b ba 54 82 18 5c 23 cf 09 c8 e0 03 2d 11 d2 19
0000060 ee e3 a8 97 5d 84 df 13 99 60 d8 30 4c 16 83 34
0000070 24 fd a7 42 18 62 e1 55 19 f4 32 c6 56 af af ba
0000080 12 21 a6 8d 9a 54 91 cb ed 28 b3 5c 62 ec 88 88
0000090 3c 21 c4 ff b9 df 89 f0 d0 8a a1 ad 6f a6 fa 4c
00000a0 13 b0 3f 74 17 83 92 1d ad 78 aa 40 90 25 93 ef
00000b0 12 d1 73 e1 6e 7c 30 cd 63 a1 96 b4 77 59 fb 6a
00000c0 8f 29 41 3d 21 92 0c 94 28 17 47 c9 e6 1d 9c 03
00000d0 6c 89 88 6a 93 57 7e d8 45 34 e1 3d 69 90 12 ec
00000e0 fc 36 3c 78 6d 36 84 69 3b a5 e7 f5 0d 85 27 a5
00000f0 b3 b9 21 77 f2 2f 1d 5f 68 37 a4 f6 a3 85 7f fd
0000100 f0 2c ca 34 59 95 82 84 50 79 5d 6f 73 0b fd 01
0000110 f1 5a ce fe 86 a5 92 6b d3 42 c3 05 d9 1a 11 25
0000120 c3 02 03 01 00 01                              
0000126

我们理解上面的二进制如下。但是我们不了解一些二进制文件。所以我们想知道PKCS#8公钥的ASN.1二进制文件的含义。 顺便说一下,我们已经参考了以下信息。然而,这并没有提到公钥。 https://datatracker.ietf.org/doc/html/rfc5208

30: SEQUEENCE
82: next 2byte is sequence length 
01 22: 0x122byte
30: SEWUENCE
0d:?
06:?
09:?
2a 86.. 01 01 01: PKCS#1 rsa Encryption
05: ?
00: ?
03: ?
82: next 2byte is length of bit string
01 0f: length of bit string
00: ?
30 82....: PKCS#1 public key

解决方法

赌一把,你真正问的是如何从 RSA 公钥 PEM 编码中获取模数和公共指数,我邀请你参加这次幽会。如果您希望构建这个编码,只给出模数和公共指数,这个剖析应该向您展示它是如何布局的,这是一个有点反向的路线图。无论哪种方式,请继续阅读。

首先:“下一个 2byte 是位串的长度” - 这是你的假设。 ASN.1 完全是关于类型-长度-值(简称 TLV)并且不会动摇该模型。 “接下来的 2 个字节是位串的长度”的唯一原因是因为 ASN.1 说它是。 (稍后会详细介绍)。

ASN.1 非常灵活。我最近有一位同事开玩笑地告诉我“ASN.1 只是婴儿潮一代的 JSON”。是的,我个人认为。自从 JSON 跟随 ASN.1 几十年以来,我喜欢认为“JSON 只是千禧一代的 ASN.1”。

无论如何,关于 ASN.1(和 JSON,就此而言)的有趣之处:有时 TLV 编码中有 TLV 编码,这里就是这种情况。具体而言,如果设置正确(即 OID 匹配),则 RSAPublicKey 序列将被编码为 ASN.1 位字符串,这似乎是您正在努力解决的部分:如何find

我将留下实际的代码来做到这一点(这将是乏味的,毫无疑问充满黑客,最终会让你希望你刚刚使用了一个加密库)作为一个任务。也就是说,继续撕开你的公钥的行为。首先,您的 PEM:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7z2oyePt5vNbH7Pbieiw
BOgRnCUyyUvUo6Wi+uqUWvMxrji1vH21ViTZYLjg40RrulSCGFwjzwnI4AMtEdIZ
7uOol12E3xOZYNgwTBaDNCT9p0IYYuFVGfQyxlavr7oSIaaNmlSRy+0os1xi7IiI
PCHE/7nfifDQiqGtb6b6TBOwP3QXg5IdrXiqQJAlk+8S0XPhbnwwzWOhlrR3Wftq
jylBPSGSDJQoF0fJ5h2cA2yJiGqTV37YRTThPWmQEuz8Njx4bTaEaTul5/UNhSel
s7khd/IvHV9oN6T2o4V//fAsyjRZlYKEUHldb3ML/QHxWs7+hqWSa9NCwwXZGhEl
wwIDAQAB
-----END PUBLIC KEY-----

将base64解码为DER,然后通过xxd显示,如下所示:

00000000: 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d 01 01  0.."0...*.H.....
00000010: 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 01 01  ........0.......
00000020: 00 ef 3d a8 c9 e3 ed e6 f3 5b 1f b3 db 89 e8 b0  ..=......[......
00000030: 04 e8 11 9c 25 32 c9 4b d4 a3 a5 a2 fa ea 94 5a  ....%2.K.......Z
00000040: f3 31 ae 38 b5 bc 7d b5 56 24 d9 60 b8 e0 e3 44  .1.8..}.V$.`...D
00000050: 6b ba 54 82 18 5c 23 cf 09 c8 e0 03 2d 11 d2 19  k.T..\#.....-...
00000060: ee e3 a8 97 5d 84 df 13 99 60 d8 30 4c 16 83 34  ....]....`.0L..4
00000070: 24 fd a7 42 18 62 e1 55 19 f4 32 c6 56 af af ba  $..B.b.U..2.V...
00000080: 12 21 a6 8d 9a 54 91 cb ed 28 b3 5c 62 ec 88 88  .!...T...(.\b...
00000090: 3c 21 c4 ff b9 df 89 f0 d0 8a a1 ad 6f a6 fa 4c  <!..........o..L
000000a0: 13 b0 3f 74 17 83 92 1d ad 78 aa 40 90 25 93 ef  ..?t.....x.@.%..
000000b0: 12 d1 73 e1 6e 7c 30 cd 63 a1 96 b4 77 59 fb 6a  ..s.n|0.c...wY.j
000000c0: 8f 29 41 3d 21 92 0c 94 28 17 47 c9 e6 1d 9c 03  .)A=!...(.G.....
000000d0: 6c 89 88 6a 93 57 7e d8 45 34 e1 3d 69 90 12 ec  l..j.W~.E4.=i...
000000e0: fc 36 3c 78 6d 36 84 69 3b a5 e7 f5 0d 85 27 a5  .6<xm6.i;.....'.
000000f0: b3 b9 21 77 f2 2f 1d 5f 68 37 a4 f6 a3 85 7f fd  ..!w./._h7......
00000100: f0 2c ca 34 59 95 82 84 50 79 5d 6f 73 0b fd 01  .,.4Y...Py]os...
00000110: f1 5a ce fe 86 a5 92 6b d3 42 c3 05 d9 1a 11 25  .Z.....k.B.....%
00000120: c3 02 03 01 00 01                                ......

正常的 ASN.1 编码,正如预期的那样。对于 PKCS8 编码的密钥,它应该如下所示:

PublicKeyInfo ::= SEQUENCE {
  algorithm       AlgorithmIdentifier,PublicKey       BIT STRING
}

通过ASN.1解码发送我们base64解码的DER数据,我们看到::

openssl asn1parse -in pubkey.der -inform DER

    0:d=0  hl=4 l= 290 cons: SEQUENCE          
    4:d=1  hl=2 l=  13 cons: SEQUENCE          
    6:d=2  hl=2 l=   9 prim: OBJECT            :rsaEncryption
   17:d=2  hl=2 l=   0 prim: NULL              
   19:d=1  hl=4 l= 271 prim: BIT STRING        

看起来差不多。对于 RSA 公钥,OID 是 1.2.840.113549.1.1.1,NULL,那么应该是一个 RSAPublicKey 作为 PublicKey 密钥数据位串,如下所示:

RSAPublicKey ::= SEQUENCE {
    modulus           INTEGER,-- n
    publicExponent    INTEGER   -- e
}

从上述解码中,我们知道偏移量 19 是实际的位串,它应该包含 RSAPublicKey ASN.1 结构。很好,但如何?好吧,原始解析在文件的 offset-19 处具有以下内容:

00000010: 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 01 01  ........0.......
                   ^^^^^^^^^^^^^^

由此我们得到:

  • ASN.1 标记字节 03 表示它是一个位串。好的。我们预料到了。然后……
  • 计算长度。根据 ASN.1 规则,如果长度八位字节的高位亮起,则意味着该八位字节的其余位告诉有多少后续八位字节构成实际长度(即它可以是多字节长度)。 82 表示后面跟着两个八位字节,表示 位串总对象长度:010f,例如271 字节。
  • 紧跟在长度描述之后的将是单个八位字节,说明位串的填充位数。老实说,在盯着这些的四分之一个世纪里,我从未在任何 RSA 编码的公钥中看到过除零 (00) 以外的任何值,但仍然值得一提。立>

此后是实际的 RSAPublicKey ASN.1 结构本身。通过位串类型长度填充描述,我们可以解析实际的 RSAPublicKey(终于!)。类型长度和填充位指示符后面的位串内容应该就是这样。好吧,那是我们 DER 编码的偏移量 24,所以:

openssl asn1parse -in pubkey.der -inform DER -offset 24

    0:d=0  hl=4 l= 266 cons: SEQUENCE          
    4:d=1  hl=4 l= 257 prim: INTEGER           :EF3DA8C9E3EDE6F35B1FB3DB89E8B004E8119C2532C94BD4A3A5A2FAEA945AF331AE38B5BC7DB55624D960B8E0E3446BBA5482185C23CF09C8E0032D11D219EEE3A8975D84DF139960D8304C16833424FDA7421862E15519F432C656AFAFBA1221A68D9A5491CBED28B35C62EC88883C21C4FFB9DF89F0D08AA1AD6FA6FA4C13B03F741783921DAD78AA40902593EF12D173E16E7C30CD63A196B47759FB6A8F29413D21920C94281747C9E61D9C036C89886A93577ED84534E13D699012ECFC363C786D3684693BA5E7F50D8527A5B3B92177F22F1D5F6837A4F6A3857FFDF02CCA345995828450795D6F730BFD01F15ACEFE86A5926BD342C305D91A1125C3
  265:d=1  hl=2 l=   3 prim: INTEGER           :010001

果然,模数和指数如预期的那样。从那里提取和解码只是代码问题,但至少现在是基于计算解码而不是猜测的代码。

公平警告

之所以有效的唯一原因是因为我们知道 RSAPublicKey 在位字符串中,而我们知道这一点的唯一原因是因为 OID 说它是。存储在位串中的形式取决于该 OID,因此在假设位串是 RSAPublicKey 材料之前,请确保首先检查它。

,

您问的是如何生成公钥,但我不明白您为什么要显示一些您正在尝试解码的二进制数据。您无法通过查看我认为已经加密的二进制数据来学习如何生成公钥或私钥。 为什么不使用库来生成密钥对并完成它呢?尝试自己从头开始编写代码很可能(实际上很可能)会出现错误,从而导致非常不安全的情况。 无论如何,如果您想看看可以从中学到什么,您可以将源代码下载到像 openssl 这样的 ssl 库中。