sun博客

记录点滴!

搞了一周时间,想弄清楚tls是怎么加密的,结果到现在还是半懂不懂!

通过wireshark 设置调试文件和pre-master-secret文件,理解了部分概念。

本质其实就是这两篇文章:

https://www.rfc-editor.org/rfc/rfc5246#section-7.4.7 //rfc 官方对tls的规则说明

https://github.com/voipmonitor/sniffer/blob/master/ssl.cpp //wireshark 的debug源码,包含了debug文件是如何生成出来的。自然算法也在其中。

https://tls12.xargs.org/ 一个学习网站,好像没多大用处。

https://www.rfc-editor.org/rfc/rfc7627#section-6.1 攻击说明

https://halfrost.com/https-key-cipher/ 这里面说的很细!

在 TLS 1.2 中,有 3 种密钥:预备主密钥、主密钥和会话密钥(密钥块),这几个密钥都是有联系的 。 密钥交换,交换的是预备主密钥。如果用RSA是协商算法,预备主密钥就用服务器公钥加密返给服务器。如果是ECDH,客户端通过服务器公钥生成自己的公钥和私钥,将自己公钥当做是 预备主密钥发给服务器。

对于 RSA 握手协商算法来说,Client 会生成的一个 48 字节的预备主密钥,其中前 2 个字节是 ProtocolVersion,后 46 字节是随机数,用 Server 的公钥加密之后通过 Client Key Exchange 子消息发给 Server,Server 用私钥来解密。对于 (EC)DHE 来说,预备主密钥是双方通过椭圆曲线算法生成的,双方各自生成临时公私钥对,保留私钥,将公钥发给对方,然后就可以用自己的私钥以及对方的公钥通过椭圆曲线算法来生成预备主密钥,预备主密钥长度取决于 DH/ECDH 算法公钥。预备主密钥长度是 48 字节或者 X 字节

主密钥是由预备主密钥、ClientHello random 和 ServerHello random 通过 PRF 函数生成的。主密钥长度是 48 字节。可以看出,只要我们知道预备主密钥或者主密钥便可以解密抓包数据,所以 TLS 1.2 中抓包解密调试只需要一个主密钥即可,SSLKEYLOG 就是将主密钥导出来,在 Wireshark 里面导入就可以解密相应的抓包数据。

会话密钥(密钥块)是由主密钥、SecurityParameters.server_random 和 SecurityParameters.client_random 数通过 PRF 函数来生成,会话密钥里面包含对称加密密钥、消息认证和 CBC 模式的初始化向量,对于非 CBC 模式的加密算法来说,就没有用到这个初始化向量。

Session ID 缓存和 Session Ticket 里面保存的也是主密钥,而不是会话密钥,这样每次会话复用的时候再用双方的随机数和主密钥导出会话密钥,从而实现每次加密通信的会话密钥不一样,即使一个会话的主密钥泄露了或者被破解了也不会影响到另一个会话。

TLS 记录层使用一个有密钥的信息验证码(MAC)来保护信息的完整性。密码算法族使用了一个被称为HMAC(在[HMAC]中描述)的 MAC 算法,它基于一个 hash 函数。如果必要的话其它密码算法族可以定义它们自己的 MAC 算法。

此外,为了进行密钥生成或验证,需要一个 MAC 算法对数据块进行扩展以增加机密性。这个伪随机函数(PRF)将机密信息(secret),种子和身份标签作为输入,并产生任意长度的输出。

PRF 使用的 Hash 算法取决于密码套件和 TLS 版本,对应关系如下:

PRF 算法Hash 算法
prf_tls10TLS 1.0 和 TLS 1.1 协议,PRF 算法是结合 MD5 和 SHA_1 算法
prf_tls12_sha256TLS 1.2 协议,默认是 SHA_256 算法(这是能满足最低安全的算法)
prf_tls12_sha384TLS 1.2 协议,如果加密套件指定的 HMAC 算法安全级别高于 SHA_256,则采用加密基元 SHA_384 算法

三. TLS 1.2 中的密钥计算

TLS 1.2 中的密钥算法主要是上一章谈到的 PRF。PRF 主要用于导出主密钥和会话密钥(密钥块)的。

1. 计算主密钥

对于所有的密钥交换算法,相同的算法都会被用来将 pre_master_secret 转化为 master_secret。一旦 master_secret 计算完毕,pre_master_secret就应当从内存中删除。避免攻击者获取预备主密钥,如果攻击者获取到了预备主密钥,加上 ClientHello.random 和 ServerHello.random 传输过程中是不加密的,也容易获取,那么攻击者就可以合成主密钥并进一步导出会话密钥,这样整个加密过程就被完全破解了。C

        master_secret = PRF(pre_master_secret, "master secret",
                            ClientHello.random + ServerHello.random)
                            [0..47];

主密钥的长度一直是 48 字节。预密钥的长度根据密钥交换算法而变

RSA

当RSA被用于身份认证和密钥交换时,Client 会产生一个 48 字节的 pre_master_secret,用 Server 的公钥加密,然后发送给 Server。Server 用它自己的私钥解密 pre_master_secret。然后双方按照前述方法将 pre_master_secret转换为 master_secret。C

        struct {
             ProtocolVersion client_version;
             opaque random[46];
         } PreMasterSecret; 

Diffie-Hellman

一个传统的 Diffie-Hellman 计算需要被执行。协商出来的密钥(Z)会被用做pre_master_secret,并按照前述方法将其转换为 master_secret。在被用做pre_master_secret之前,Z 开头所有的 0 位都会被压缩。

注:Diffie-Hellman 参数由 Server 指定,可能是临时的也可能包含在 Server 的证书中。

keylogfile说明:

NSS Key Log Format
Key logs can be written by NSS so that external programs can decrypt TLS connections. Wireshark 1.6.0 and above can use these log files to decrypt packets. You can tell Wireshark where to find the key file via Edit→Preferences→Protocols→TLS→(Pre)-Master-Secret log filename.

Key logging is enabled by setting the environment variable SSLKEYLOGFILE to point to a file. Note: starting with NSS 3.24 release notes (used by Firefox 48 and 49 only), the SSLKEYLOGFILE approach is disabled by default for optimized builds using the Makefile (those using gyp via build.sh are not affected). Distributors can re-enable it at compile time though (using the NSS_ALLOW_SSLKEYLOGFILE=1 make variable) which is done for the official Firefox binaries. (See bug 1188657.) Notably, Debian does not have this option enabled, see Debian bug 842292.

This key log file is a series of lines. Comment lines begin with a sharp character (‘#’) and are ignored. Secrets follow the format <Label> <space> <ClientRandom> <space> <Secret> where:

<Label> describes the following secret.

<ClientRandom> is 32 bytes Random value from the Client Hello message, encoded as 64 hexadecimal characters.

<Secret> depends on the Label (see below).

The following labels are defined, followed by a description of the secret:

RSA: 48 bytes for the premaster secret, encoded as 96 hexadecimal characters (removed in NSS 3.34)

CLIENT_RANDOM: 48 bytes for the master secret, encoded as 96 hexadecimal characters (for SSL 3.0, TLS 1.0, 1.1 and 1.2)

CLIENT_EARLY_TRAFFIC_SECRET: the hex-encoded early traffic secret for the client side (for TLS 1.3)

CLIENT_HANDSHAKE_TRAFFIC_SECRET: the hex-encoded handshake traffic secret for the client side (for TLS 1.3)

SERVER_HANDSHAKE_TRAFFIC_SECRET: the hex-encoded handshake traffic secret for the server side (for TLS 1.3)

CLIENT_TRAFFIC_SECRET_0: the first hex-encoded application traffic secret for the client side (for TLS 1.3)

SERVER_TRAFFIC_SECRET_0: the first hex-encoded application traffic secret for the server side (for TLS 1.3)

EARLY_EXPORTER_SECRET: the hex-encoded early exporter secret (for TLS 1.3).

EXPORTER_SECRET: the hex-encoded exporter secret (for TLS 1.3)

The RSA form allows ciphersuites using RSA key-agreement to be logged and was the first form supported by Wireshark 1.6.0. It has been superseded by CLIENT_RANDOM which also works with other key-agreement algorithms (such as those based on Diffie-Hellman) and is supported since Wireshark 1.8.0.

The TLS 1.3 lines are supported since NSS 3.34 (bug 1287711) and Wireshark 2.4 (EARLY_EXPORTER_SECRET exists since NSS 3.35, bug 1417331). The size of the hex-encoded secret depends on the selected cipher suite. It is 64, 96 or 128 characters for SHA256, SHA384 or SHA512 respectively.

For Wireshark usage, see TLS - Wireshark Wiki.

所以keylogfile文件中对于TLS v1.2来说,真正有用的就是CLIENT_RANDOM: 48 bytes for the master secret, encoded as 96 hexadecimal characters (for SSL 3.0, TLS 1.0, 1.1 and 1.2)。通过搜索客户端随机数即可找到,空格后面的48个字节(96个字符)即为主密钥。(衰,搞不清楚这个文件中到底是主密钥还是预主密钥)

//debug.txt
dissect_ssl enter frame #9 (first time)
packet_from_server: is from server - FALSE
  conversation = 000002387BBC9610, ssl_session = 000002387BBCA190
  record: offset = 0, reported_length_remaining = 93
dissect_ssl3_record: content_type 22 Handshake
decrypt_ssl3_record: app_data len 37, ssl state 0x197
packet_from_server: is from server - FALSE
decrypt_ssl3_record: using client decoder
decrypt_ssl3_record: no decoder available
dissect_ssl3_handshake iteration 1 type 16 offset 5 length 33 bytes
Calculating hash with offset 5 37
trying to use TLS keylog in C:\Users\86153\Desktop\keylogfile.txt
ssl_generate_pre_master_secret: found SSL_HND_CLIENT_KEY_EXCHG, state 197
ssl_restore_master_key can't find pre-master secret by Unencrypted pre-master secret
ssl_decrypt_pre_master_secret: session uses Diffie-Hellman key exchange (cipher suite 0xC02F TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) and cannot be decrypted using a RSA private key file.
ssl_generate_pre_master_secret: can't decrypt pre-master secret
ssl_restore_master_key can't find pre-master secret by Encrypted pre-master secret
dissect_ssl3_handshake can't generate pre master secret
  record: offset = 42, reported_length_remaining = 51
dissect_ssl3_record: content_type 20 Change Cipher Spec
decrypt_ssl3_record: app_data len 1, ssl state 0x197
packet_from_server: is from server - FALSE
decrypt_ssl3_record: using client decoder
decrypt_ssl3_record: no decoder available
trying to use TLS keylog in C:\Users\86153\Desktop\keylogfile.txt
ssl_finalize_decryption state = 0x197
ssl_restore_master_key can't restore master secret using an empty Session ID
ssl_restore_master_key master secret retrieved using Client Random
Client Random[32]:
| 9a 8b 99 06 0a 80 f1 30 d0 0c 5a d0 28 59 ad 53 |.......0..Z.(Y.S|
| 1c f2 ad 8c 68 a3 f6 6e 1f a8 c0 29 0e f1 31 42 |....h..n...)..1B|
(pre-)master secret[48]:
| d4 5b 65 b0 97 b5 c5 04 00 2e 22 e7 7a ab 15 cd |.[e.......".z...|
| 08 ab 93 a6 6e c1 48 bd 70 c1 bb 15 4c 39 fc d3 |....n.H.p...L9..|
| 74 5a a5 40 58 21 a7 cc 22 c0 15 ae 58 9f 95 e7 |tZ.@X!.."...X...|
ssl_generate_keyring_material CIPHER: AES
ssl_generate_keyring_material sess key generation
tls12_prf: tls_hash(hash_alg SHA256 secret_len 48 seed_len 77 )
tls_hash: hash secret[48]:
| d4 5b 65 b0 97 b5 c5 04 00 2e 22 e7 7a ab 15 cd |.[e.......".z...|
| 08 ab 93 a6 6e c1 48 bd 70 c1 bb 15 4c 39 fc d3 |....n.H.p...L9..|
| 74 5a a5 40 58 21 a7 cc 22 c0 15 ae 58 9f 95 e7 |tZ.@X!.."...X...|
tls_hash: hash seed[77]:
| 6b 65 79 20 65 78 70 61 6e 73 69 6f 6e f6 ca 1c |key expansion...|
| 5a a5 15 ae 0b a5 a9 df 05 9f 72 d0 48 d5 e5 27 |Z.........r.H..'|
| dc 2d c0 d6 ff d4 e7 4b 4e e7 7c cb 57 9a 8b 99 |.-.....KN.|.W...|
| 06 0a 80 f1 30 d0 0c 5a d0 28 59 ad 53 1c f2 ad |....0..Z.(Y.S...|
| 8c 68 a3 f6 6e 1f a8 c0 29 0e f1 31 42          |.h..n...)..1B   |
hash out[104]:
| 28 93 6b 89 63 b9 7a a3 f8 fc 6b ba b0 10 59 29 |(.k.c.z...k...Y)|
| 3c 22 20 f4 cd 8a 49 28 15 cd bb 28 ad bf 1e c4 |<" ...I(...(....|
| 53 22 94 77 32 97 34 65 25 50 09 0d ac 4c 52 aa |S".w2.4e%P...LR.|
| 97 40 2a 4d 9d 05 89 a1 44 4f 7f 71 67 1d ed 14 |.@*M....DO.qg...|
| fc 8a fe 88 29 0f 38 79 d1 05 b3 9a ff c6 7a 72 |....).8y......zr|
| 10 79 6b d8 e5 a5 09 3c 35 cb 04 ee a2 9e 67 39 |.yk....<5.....g9|
| 97 9b f9 11 ee 1d 1d 80                         |........        |
PRF out[104]:
| 28 93 6b 89 63 b9 7a a3 f8 fc 6b ba b0 10 59 29 |(.k.c.z...k...Y)|
| 3c 22 20 f4 cd 8a 49 28 15 cd bb 28 ad bf 1e c4 |<" ...I(...(....|
| 53 22 94 77 32 97 34 65 25 50 09 0d ac 4c 52 aa |S".w2.4e%P...LR.|
| 97 40 2a 4d 9d 05 89 a1 44 4f 7f 71 67 1d ed 14 |.@*M....DO.qg...|
| fc 8a fe 88 29 0f 38 79 d1 05 b3 9a ff c6 7a 72 |....).8y......zr|
| 10 79 6b d8 e5 a5 09 3c 35 cb 04 ee a2 9e 67 39 |.yk....<5.....g9|
| 97 9b f9 11 ee 1d 1d 80                         |........        |
key expansion[104]:
| 28 93 6b 89 63 b9 7a a3 f8 fc 6b ba b0 10 59 29 |(.k.c.z...k...Y)|
| 3c 22 20 f4 cd 8a 49 28 15 cd bb 28 ad bf 1e c4 |<" ...I(...(....|
| 53 22 94 77 32 97 34 65 25 50 09 0d ac 4c 52 aa |S".w2.4e%P...LR.|
| 97 40 2a 4d 9d 05 89 a1 44 4f 7f 71 67 1d ed 14 |.@*M....DO.qg...|
| fc 8a fe 88 29 0f 38 79 d1 05 b3 9a ff c6 7a 72 |....).8y......zr|
| 10 79 6b d8 e5 a5 09 3c 35 cb 04 ee a2 9e 67 39 |.yk....<5.....g9|
| 97 9b f9 11 ee 1d 1d 80                         |........        |
Client Write key[16]:
| 28 93 6b 89 63 b9 7a a3 f8 fc 6b ba b0 10 59 29 |(.k.c.z...k...Y)|
Server Write key[16]:
| 3c 22 20 f4 cd 8a 49 28 15 cd bb 28 ad bf 1e c4 |<" ...I(...(....|
Client Write IV[4]:
| 53 22 94 77                                     |S".w            |
Server Write IV[4]:
| 32 97 34 65                                     |2.4e            |
ssl_generate_keyring_material ssl_create_decoder(client)
decoder initialized (digest len 32)
ssl_generate_keyring_material ssl_create_decoder(server)
decoder initialized (digest len 32)
ssl_generate_keyring_material: client seq 0, server seq 0
ssl_save_master_key inserted (pre-)master secret for Client Random
stored key[32]:
| 9a 8b 99 06 0a 80 f1 30 d0 0c 5a d0 28 59 ad 53 |.......0..Z.(Y.S|
| 1c f2 ad 8c 68 a3 f6 6e 1f a8 c0 29 0e f1 31 42 |....h..n...)..1B|
stored (pre-)master secret[48]:
| d4 5b 65 b0 97 b5 c5 04 00 2e 22 e7 7a ab 15 cd |.[e.......".z...|
| 08 ab 93 a6 6e c1 48 bd 70 c1 bb 15 4c 39 fc d3 |....n.H.p...L9..|
| 74 5a a5 40 58 21 a7 cc 22 c0 15 ae 58 9f 95 e7 |tZ.@X!.."...X...|
ssl_save_master_key: not saving empty Session ID!
packet_from_server: is from server - FALSE
ssl_change_cipher CLIENT
  record: offset = 48, reported_length_remaining = 45
dissect_ssl3_record: content_type 22 Handshake
decrypt_ssl3_record: app_data len 40, ssl state 0x1BF
packet_from_server: is from server - FALSE
decrypt_ssl3_record: using client decoder
ssl_decrypt_record ciphertext len 40
Ciphertext[40]:
| 00 00 00 00 00 00 00 00 cd 8c 83 36 35 b4 24 f8 |...........65.$.|
| b3 45 ae 46 57 fd 51 f1 84 47 83 e3 02 ae f0 ee |.E.FW.Q..G......|
| 89 0e 3a d7 6d 7a bb 83                         |..:.mz..        |
ssl_decrypt_record: allocating 72 bytes for decrypt data (old len 32)
nonce[12]:
| 53 22 94 77 00 00 00 00 00 00 00 00             |S".w........    |
AAD[13]:
| 00 00 00 00 00 00 00 00 16 03 03 00 10          |.............   |
auth_tag(OK)[16]:
| 84 47 83 e3 02 ae f0 ee 89 0e 3a d7 6d 7a bb 83 |.G........:.mz..|
Plaintext[16]:
| 14 00 00 0c 4e 13 73 f1 55 bd 47 63 d0 13 dd ea |....N.s.U.Gc....|
dissect_ssl3_handshake iteration 1 type 20 offset 0 length 12 bytes
trying to use TLS keylog in C:\Users\86153\Desktop\keylogfile.txt
tls13_load_secret TLS version 0x303 is not 1.3

从debug.txt中可以看到,Client Random是客户端握手期间传给客户的随机数。

9a8b99060a80f130d00c5ad02859ad531cf2ad8c68a3f66e1fa8c0290ef13142

(pre)master secret,其值也为48字节。密钥交换交换的就是这个东西。如果用的rsa做的密钥交换,则使用服务器公钥加密传给服务端,服务器私钥解密拿到pre-master secret,通过pre-master secret和客户端和服务器端发送的随机数生成固定48个字节的长度即为master secret。

d45b65b097b5c504002e22e77aab15cd08ab93a66ec148bd70c1bb154c39fcd3745aa5405821a7cc22c015ae589f95e7
struct{
    ProtocolVersion client_version;
    opaque random[46]; // 46字节的随机数
}PreMasterSecret // 总计大小为48字节

sha256,256表示位,256/8=32个字节,1个字节=2的8次方,大小为256=16*16,2个16进制字符=1个字节。32个字节等于64个字符。

客户端发送加密套件,服务器选出一个,我的电脑中访问的目标服务器选的是TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)。

表示密钥交换使用 ECDHE , RSA 则用于证书签名验证。 AES_128_GCM 表示以后实际通讯发送的消息使用这个算法加密。 SHA256 用于验证内容完整性。

ECDHE,表示利用椭圆曲线算法实现的Diffie-Hellman进行密钥协商,E为Ephemeral(短暂的)。 它表示每次进行TLS握手时都进行密钥协商,它比ECDH拥有更好的前向安全保证,  因为 ECDHE 每次握手都会协商一次密钥, 因此的效率也会差一些. 他们的TLS握手方式也有区别, 这里不做赘述.

ECDH相比DH的生成效率来说高很多(椭圆曲线参数的生成), 相对来说DHE成本很高, 为了更好的安全性, ECDHE也被TLS协议所应用。

TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,

TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,

TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
TLS_RSA_WITH_AES_256_GCM_SHA384,
TLS_RSA_WITH_AES_128_GCM_SHA256,
TLS_RSA_WITH_AES_256_CBC_SHA256,
TLS_RSA_WITH_AES_128_CBC_SHA256,
TLS_RSA_WITH_AES_256_CBC_SHA,
TLS_RSA_WITH_AES_128_CBC_SHA,
TLS_RSA_WITH_3DES_EDE_CBC_SHA,
TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
TLS_RSA_WITH_RC4_128_SHA,
TLS_RSA_WITH_RC4_128_MD5,
TLS_RSA_WITH_NULL_SHA256,
TLS_RSA_WITH_NULL_SHA,
TLS_PSK_WITH_AES_256_GCM_SHA384,
TLS_PSK_WITH_AES_128_GCM_SHA256,
TLS_PSK_WITH_AES_256_CBC_SHA384,
TLS_PSK_WITH_AES_128_CBC_SHA256,
TLS_PSK_WITH_NULL_SHA384,
TLS_PSK_WITH_NULL_SHA256

//https://www.rfc-editor.org/rfc/rfc5246#section-7.4.7
8.1.  Computing the Master Secret

   For all key exchange methods, the same algorithm is used to convert
   the pre_master_secret into the master_secret.  The pre_master_secret
   should be deleted from memory once the master_secret has been
   computed.

      master_secret = PRF(pre_master_secret, "master secret",
                          ClientHello.random + ServerHello.random)
                          [0..47];

   The master secret is always exactly 48 bytes in length.  The length
   of the premaster secret will vary depending on key exchange method.

8.1.1.  RSA

   When RSA is used for server authentication and key exchange, a 48-
   byte pre_master_secret is generated by the client, encrypted under
   the server's public key, and sent to the server.  The server uses its
   private key to decrypt the pre_master_secret.  Both parties then
   convert the pre_master_secret into the master_secret, as specified
   above.

8.1.2.  Diffie-Hellman

   A conventional Diffie-Hellman computation is performed.  The
   negotiated key (Z) is used as the pre_master_secret, and is converted
   into the master_secret, as specified above.  Leading bytes of Z that
   contain all zero bits are stripped before it is used as the
   pre_master_secret.

   Note: Diffie-Hellman parameters are specified by the server and may
   be either ephemeral or contained within the server's certificate.

客户端证书校验,即用父级的公钥(证书pem文件的公钥)去验证当前的证书中签名。返回true,就为有效。pem证书公钥其实是16进制的字节数组,base64加密后就是我们常看到的这种。没有—–BEGIN PUBLIC KEY—–和—–END PUBLIC KEY—–。

//这是pkcs8的格式,pkcs1的格式为 -----BEGIN RSA PUBLIC KEY-----
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvqo1sKYWMLi9ohiMlc3B
RA53nI3e4aY9UVLIl58Fe8K5xFnJU5vMUUBucOZUYPSnyB+xD4v+WseSILVhS4KB
rojh1RtqWJfHUxpsMy/jlq10lZKPp3DG9u6QkegZPlOoqFeQhc4ckZheohUDYjhc
TzrpcZN+O+hirF+oD32bc39j1uB0XemAkF+MaEr8bNqidlAjesYOpAKXy9pm2PQO
6S29ODyGuN/MIEVWIemjDRaN+a5HQ/gZTKU0LQGgOKgsiIrFHul+GAulUvK3r0fx
yHCCJ9pnOnOqKSg9C+uWh7rBTGPiuNr5psINKSrk2IqOZ9b58ocUGXT5cKU3sv+s
kQIDAQAB
-----END PUBLIC KEY-----
//从证书中拿到公钥
public PublicKey getRootPublicKey() throws Exception {
        byte[] key = Files.readAllBytes(Paths.get("C:\\Users\\86153\\Desktop\\cert\\csdn2pub.pem"));

        Security.addProvider(new BouncyCastleProvider());

        final PemObject pemObject;

        try (PemReader pemReader = new PemReader(new InputStreamReader(
                new ByteArrayInputStream(key)))) {
            pemObject = pemReader.readPemObject();
        }

        X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(pemObject.getContent());

        KeyFactory factory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = factory.generatePublic(pubKeySpec);
        System.out.println(Base64.getEncoder().encodeToString(publicKey.getEncoded()));
        return publicKey;

        // return PublicKey.class.cast(readKey("C:\\Users\\86153\\Desktop\\cert\\csdn3pub.pubkey"));
    }

//验证签名是否一致
public void VerifyCert() throws Exception {
        //本地证书导入成java可识别对象
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        FileInputStream inStream = new FileInputStream("C:\\Users\\86153\\Desktop\\cert\\csdn1.crt");
        X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(inStream);

        System.out.println(certificate.getSigAlgName());
        Signature signature = Signature.getInstance(certificate.getSigAlgName());

        //证书验证需要父证书中的公钥,当前证书中的签名,及当前证书的摘要信息(TBS)
        //拿到上一个证书的公钥
        signature.initVerify(getRootPublicKey());

        //拿到摘要信息
        signature.update(certificate.getTBSCertificate());
        //这个证书的签名
        boolean legal = signature.verify(certificate.getSignature());
        System.out.println(legal);
    }
//DH算法,DHUtil.java
package utils;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: huangyibo
 * @Date: 2022/5/6 17:33
 * @Description:
 */

public class DHUtil {

    /**
     * 使用DH方式加解密的前提条件:否则报错 java.security.NoSuchAlgorithmException: Unsupported secret key algorithm: AES
     *  1.当前开发环境中的运行的java程序, 在jre中配置缺省VM变量 -Djdk.crypto.KeyAgreement.legacyKDF=true
     *      A.点击窗口,选择首选项  B.点击installed JREs, 选择JRE配置,然后编辑  C.在缺省VM参数:-Djdk.crypto.KeyAgreement.legacyKDF=true
     *  2.可运行jar包,则需要在运行时采用命令提示符运行,在运行时添加VM参数,运行命令为:java -jar -Djdk.crypto.KeyAgreement.legacyKDF=true jarPackName.jar
     *      A.编辑可执行文件,配置VM参数:-Djdk.crypto.KeyAgreement.legacyKDF=true
     */

    /** 本地密钥算法,即对称加密密钥算法  可选DES、DESede或者AES*/
    private static final String SELECT_ALGORITHM = "AES";
    /** 默认的加密算法 */
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
    /** 密钥长度 */
    private static final int KEY_SIZE = 512;
    //公钥
    private static final String PUBLIC_KEY = "DHPublicKey";
    //私钥
    private static final String PRIVATE_KEY = "DHPrivateKey";

    /**  初始化甲方密钥
     * @return Map 甲方密钥Map
     * @throws Exception
     */
    public static Map<String, Object> initKey() {
        try {
            //实例化密钥对生成器
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DH");
            //初始化密钥对生成器
            keyPairGenerator.initialize(KEY_SIZE);
            //生成密钥对
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            //甲方公钥
            DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic();
            //甲方私钥
            DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate();
            //将密钥对存储在Map中
            Map<String, Object> keyMap = new HashMap<String, Object>(2);
            keyMap.put(PUBLIC_KEY, publicKey);
            keyMap.put(PRIVATE_KEY, privateKey);
            return keyMap;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    /** 初始化乙方密钥
     * @param key 甲方公钥
     * @return Map 乙方密钥Map
     * @throws Exception
     */
    public static Map<String, Object> initKey(byte[] key) {
        try {
            //解析甲方公钥,甲方是服务器
            //转换公钥材料
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
            //实例化密钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance("DH");
            //产生公钥
            PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
            //由甲方公钥构建乙方密钥
            DHParameterSpec dhParameterSpec = ((DHPublicKey) pubKey).getParams();
            //实例化密钥对生成器
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DH");
            //初始化密钥对生成器
            keyPairGenerator.initialize(dhParameterSpec);
            //产生密钥对
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            //乙方公钥
            DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic();
            //乙方私约
            DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate();
            //将密钥对存储在Map中
            Map<String, Object> keyMap = new HashMap<String, Object>(2);
            keyMap.put(PUBLIC_KEY, publicKey);
            keyMap.put(PRIVATE_KEY, privateKey);
            return keyMap;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /** 生成加密秘钥
     * @return
     */
    private static SecretKeySpec getSecretKey(final byte[] key) {
        //返回生成指定算法密钥生成器的 KeyGenerator 对象
        KeyGenerator kg = null;
        try {
            kg = KeyGenerator.getInstance(SELECT_ALGORITHM);
            //AES 要求密钥长度为 128
            kg.init(128, new SecureRandom(key));
            //生成一个密钥
            SecretKey secretKey = kg.generateKey();
            return new SecretKeySpec(secretKey.getEncoded(), SELECT_ALGORITHM);// 转换为AES专用密钥
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
        }

        return null;
    }



    /** 加密
     * @param data 待加密数据
     * @param key  密钥
     * @return byte[] 加密数据
     * @throws Exception
     */
    public static byte[] encrypt(byte[] data, byte[] key) {
        try {
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器
            byte[] byteContent = data;
            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));// 初始化为加密模式的密码器
            byte[] result = cipher.doFinal(byteContent);// 加密
            return result;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    /**  解密
     * @param data 待解密数据
     * @param key  密钥
     * @return byte[] 解密数据
     * @throws Exception
     */
    public static byte[] decrypt(byte[] data, byte[] key) {
        try {
            //实例化
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            //使用密钥初始化,设置为解密模式
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));
            //执行操作
            byte[] result = cipher.doFinal(data);
            return result;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    /** 构建密钥
     * @param publicKey  公钥
     * @param privateKey 私钥
     * @return byte[] 本地密钥
     * @throws Exception
     */
    public static byte[] getSecretKey(byte[] publicKey, byte[] privateKey){
        try {
            //实例化密钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance("DH");
            //初始化公钥
            //密钥材料转换
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey);
            //产生公钥
            PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
            //初始化私钥
            //密钥材料转换
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey);
            //产生私钥
            PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);


            //实例化
            KeyAgreement keyAgreement = KeyAgreement.getInstance(keyFactory.getAlgorithm());
            //初始化
            keyAgreement.init(priKey);
            keyAgreement.doPhase(pubKey, true);


            // 当前推荐的做法
            byte[] keyArray = keyAgreement.generateSecret();
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            byte[] digest = messageDigest.digest(keyArray);
            return new SecretKeySpec(digest, "AES").getEncoded();
        }catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    

    /** 取得私钥
     * @param keyMap 密钥Map
     * @return byte[] 私钥
     * @throws Exception
     */
    public static byte[] getPrivateKey(Map<String, Object> keyMap) {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return key.getEncoded();
    }

    /** 取得公钥
     * @param keyMap 密钥Map
     * @return byte[] 公钥
     * @throws Exception
     */
    public static byte[] getPublicKey(Map<String, Object> keyMap) {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return key.getEncoded();
    }
}
public static void main(String[] args) {
    //甲方公钥
    byte[] publicKeyA;
    //甲方私钥
    byte[] privateKeyA;
    //甲方本地密钥
    byte[] localKeyA;
    //乙方公钥
    byte[] publicKeyB;
    //乙方私钥
    byte[] privateKeyB;
    //乙方本地密钥
    byte[] localKeyB;

    //初始化密钥
    //生成甲方密钥对
    Map<String, Object> keyMapA = DHUtil.initKey();
    publicKeyA = DHUtil.getPublicKey(keyMapA);
    privateKeyA = DHUtil.getPrivateKey(keyMapA);
    System.out.println("甲方公钥-byte:\n" + Common.bytesToHexString(publicKeyA));
    System.out.println("甲方公钥:\n" + Base64.getEncoder().encodeToString(publicKeyA));
    System.out.println("甲方私钥:\n" + Base64.getEncoder().encodeToString(privateKeyA));
    //由甲方公钥产生乙方本地密钥对
    Map<String, Object> keyMapB = DHUtil.initKey(publicKeyA);
    publicKeyB = DHUtil.getPublicKey(keyMapB);
    privateKeyB = DHUtil.getPrivateKey(keyMapB);
    System.out.println("乙方公钥:\n" + Base64.getEncoder().encodeToString(publicKeyB));
    System.out.println("乙方私钥:\n" + Base64.getEncoder().encodeToString(privateKeyB));
    System.out.println("乙方私钥-byte:\n" + Common.bytesToHexString(privateKeyB));

    localKeyA = DHUtil.getSecretKey(publicKeyB, privateKeyA);
    System.out.println("甲方本地密钥:\n" + Base64.getEncoder().encodeToString(localKeyA));

    localKeyB = DHUtil.getSecretKey(publicKeyA, privateKeyB);
    System.out.println("乙方本地密钥:\n" + Base64.getEncoder().encodeToString(localKeyB));
    System.out.println("乙方本地密钥-byte:\n" + Common.bytesToHexString(localKeyB));
    System.out.println();
    System.out.println("===甲方向乙方发送加密数据===");
    String msgA2B = "求知若饥,虚心若愚。";
    System.out.println("原文:\n" + msgA2B);
    System.out.println("---使用甲方本地密钥对数据进行加密---");
    //使用甲方本地密钥对数据加密
    byte[] encodeMsgA2B = DHUtil.encrypt(msgA2B.getBytes(), localKeyA);
    System.out.println("加密:\n" + Base64.getEncoder().encodeToString(encodeMsgA2B));
    System.out.println("---使用乙方本地密钥对数据库进行解密---");
    //使用乙方本地密钥对数据进行解密
    byte[] msgB2A = DHUtil.decrypt(encodeMsgA2B, localKeyB);
    String output1 = new String(msgB2A);
    System.out.println("解密:\n" + output1);

    System.out.println("/~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~..~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~/");
    System.out.println("===乙方向甲方发送加密数据===");
    String input2 = "好好学习,天天向上。";
    System.out.println("原文:\n" + input2);
    System.out.println("---使用乙方本地密钥对数据进行加密---");
    //使用乙方本地密钥对数据进行加密
    byte[] encode2 = DHUtil.encrypt(input2.getBytes(), localKeyB);
    System.out.println("加密:\n" + Base64.getEncoder().encodeToString(encode2));
    System.out.println("---使用甲方本地密钥对数据进行解密---");
    //使用甲方本地密钥对数据进行解密
    byte[] decode2 = DHUtil.decrypt(encode2, localKeyA);
    String output2 = new String(decode2);
    System.out.println("解密:\n" + output2);
}

@Test
public void testCommon(){
    //知道对方公钥和自己私钥,生成的结果无论运行多少次都一致。
    byte[] publicKeyA=Common.hexStringToByteArray("3081df30819706092a864886f70d010301308189024100fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e170240678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4020201800343000240737b81240e3e31dce222ab20e9ba9736d7f526963efd56697b72347be9b60a384657d5fa75901bb34874c00920b5378f08b4eab5c0df330ac1cfab83cec645cf");
    byte[] privateKeyB=Common.hexStringToByteArray("3081d202010030819706092a864886f70d010301308189024100fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e170240678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4020201800433023100801f45b4af1a573dc0ef5b70243b7929d614d7f04cec01a4e459831d071bb10b1f48a718149f953c063487047a6b97e4");

    byte[] localKeyB = DHUtil.getSecretKey(publicKeyA, privateKeyB);
    String ori="b160cc67633645453e22415ed4f3e02d32195394ca34c9d78247e76d391b2818";
    String cur=Common.bytesToHexString(localKeyB);
    System.out.println(cur);
    if(ori.equals(cur)){
        System.out.println("is true"); //返回 is true
    }
}
@Test
public void testPubCommon(){
    //只知道公钥,生成的pre-master secret 每次函数运行时不一致
    byte[] publicKeyA=Common.hexStringToByteArray("3081df30819706092a864886f70d010301308189024100fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e170240678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4020201800343000240737b81240e3e31dce222ab20e9ba9736d7f526963efd56697b72347be9b60a384657d5fa75901bb34874c00920b5378f08b4eab5c0df330ac1cfab83cec645cf");
    Map<String, Object> keyMapB = DHUtil.initKey(publicKeyA);
    byte[] publicKeyB = DHUtil.getPublicKey(keyMapB);
    byte[] privateKeyB = DHUtil.getPrivateKey(keyMapB);
    byte[]localKeyB = DHUtil.getSecretKey(publicKeyA, privateKeyB);
    System.out.println("乙方本地密钥-byte:\n" + Common.bytesToHexString(localKeyB));
    //1,0e96244069d469547e9757bea16f53924d1c29e3711778b07528814af72d176b
    //2,9d1b1dc8012179ba981edcceed475b754e15b361b9527be4fadbb0c8d79f013b
}

Message Authentication Code (MAC)

发表评论

邮箱地址不会被公开。 必填项已用*标注