You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 03:52:46