Java实现TLS握手时,Diffie-Hellman生成的数值如何使用?
如何在TLS握手的DH密钥交换中使用Pre-Master-Secret
你拿到的这个大整数确实是Pre-Master-Secret(预主密钥),接下来需要按照TLS规范的流程,将它转换为实际用于加密通信的会话密钥,具体步骤如下:
1. 将大整数转换为标准字节数组
首先要把十进制的预主密钥转换成大端字节序的字节数组。Java中BigInteger.toByteArray()会包含符号位,可能产生前导零,需要处理掉,确保最终字节数组长度为128字节(对应1024位DH密钥):
BigInteger preMasterSecretBigInt = new BigInteger("137788568906486456562313443251433625845738808626213687842428636488613021697893174760087427050476313862114101648826514308630193757158023355782732286841194048890730724983728673832483012519924831013247552765933483415273172062507912219558443488454534677453627247897385118516061200838456501806287350964669621095063"); byte[] preMasterSecret; byte[] temp = preMasterSecretBigInt.toByteArray(); // 移除前导零(如果存在) if (temp[0] == 0 && temp.length > 128) { preMasterSecret = Arrays.copyOfRange(temp, 1, temp.length); } else { preMasterSecret = temp; }
2. 生成Master Secret(主密钥)
主密钥由预主密钥、客户端随机数(Client Random)、服务器随机数(Server Random)通过PRF(伪随机函数)生成,TLS规范公式为:
master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random) [0..47]
最终生成48字节的主密钥,以SHA-1版PRF为例,实现代码如下:
public static byte[] generateMasterSecret(byte[] preMasterSecret, byte[] clientRandom, byte[] serverRandom) throws NoSuchAlgorithmException { byte[] label = "master secret".getBytes(StandardCharsets.US_ASCII); byte[] seed = ByteBuffer.allocate(clientRandom.length + serverRandom.length) .put(clientRandom) .put(serverRandom) .array(); byte[] combinedSeed = concat(label, seed); // PRF拆分密钥并循环生成足够字节 byte[] key1 = Arrays.copyOfRange(preMasterSecret, 0, preMasterSecret.length / 2); byte[] key2 = Arrays.copyOfRange(preMasterSecret, preMasterSecret.length / 2, preMasterSecret.length); byte[] temp = concat(hmacSha1(key1, combinedSeed), hmacSha1(key2, combinedSeed)); while (temp.length < 48) { byte[] nextHmac = concat(hmacSha1(key1, temp), hmacSha1(key2, temp)); temp = concat(temp, nextHmac); } return Arrays.copyOfRange(temp, 0, 48); } // 工具方法:HMAC-SHA1计算 private static byte[] hmacSha1(byte[] key, byte[] data) throws NoSuchAlgorithmException { Mac mac = Mac.getInstance("HmacSHA1"); mac.init(new SecretKeySpec(key, "HmacSHA1")); return mac.doFinal(data); } // 工具方法:字节数组拼接 private static byte[] concat(byte[] a, byte[] b) { byte[] result = new byte[a.length + b.length]; System.arraycopy(a, 0, result, 0, a.length); System.arraycopy(b, 0, result, a.length, b.length); return result; }
3. 从主密钥生成会话密钥
会话密钥包括加密密钥、MAC密钥、初始化向量(IV),同样通过PRF生成,公式为:
key_block = PRF(master_secret, "key expansion", ServerHello.random + ClientHello.random)
以密码套件TLS_DHE_RSA_WITH_AES_128_CBC_SHA为例,提取对应长度的密钥:
public static Map<String, byte[]> generateSessionKeys(byte[] masterSecret, byte[] clientRandom, byte[] serverRandom) throws NoSuchAlgorithmException { byte[] label = "key expansion".getBytes(StandardCharsets.US_ASCII); byte[] seed = ByteBuffer.allocate(serverRandom.length + clientRandom.length) .put(serverRandom) .put(clientRandom) .array(); byte[] combinedSeed = concat(label, seed); // 计算所需总字节数:20+20+16+16+16+16=104 byte[] keyBlock = prfSha1(masterSecret, combinedSeed, 104); Map<String, byte[]> keys = new HashMap<>(); int offset = 0; keys.put("client_mac_key", Arrays.copyOfRange(keyBlock, offset, offset +=20)); keys.put("server_mac_key", Arrays.copyOfRange(keyBlock, offset, offset +=20)); keys.put("client_enc_key", Arrays.copyOfRange(keyBlock, offset, offset +=16)); keys.put("server_enc_key", Arrays.copyOfRange(keyBlock, offset, offset +=16)); keys.put("client_iv", Arrays.copyOfRange(keyBlock, offset, offset +=16)); keys.put("server_iv", Arrays.copyOfRange(keyBlock, offset, offset +=16)); return keys; } // 通用PRF生成指定长度字节数组 private static byte[] prfSha1(byte[] secret, byte[] seed, int length) throws NoSuchAlgorithmException { byte[] key1 = Arrays.copyOfRange(secret, 0, secret.length / 2); byte[] key2 = Arrays.copyOfRange(secret, secret.length / 2, secret.length); byte[] temp = new byte[0]; byte[] currentSeed = seed; while (temp.length < length) { byte[] next = concat(hmacSha1(key1, currentSeed), hmacSha1(key2, currentSeed)); temp = concat(temp, next); currentSeed = concat(next, seed); } return Arrays.copyOfRange(temp, 0, length); }
4. 会话密钥的实际使用
生成的密钥用于TLS记录层的加密通信:
- 客户端发数据:用
client_enc_key+client_iv加密,用client_mac_key计算MAC - 服务器收数据:用
client_enc_key+client_iv解密,用client_mac_key验证MAC - 服务器发数据:用
server_enc_key+server_iv加密,用server_mac_key计算MAC - 客户端收数据:用
server_enc_key+server_iv解密,用server_mac_key验证MAC
注意CBC模式下,每次加密后IV需要更新为最后一个密文块,符合TLS规范要求。
内容的提问来源于stack exchange,提问作者Jinwoo An
相关产品推荐
相关产品推荐

