我正在尝试在nodejs中实现DTLS服务器。当我尝试从客户端解密
EncryptedHandshakeMessage
时,出现以下错误。
Error: Unsupported state or unable to authenticate data
我做错了什么?任何帮助将不胜感激。
我正在尝试在nodejs中实现DTLS服务器。
我的密码套件是,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
我的服务器发送
ServerKeyExchange
并带有以下参数
EC Diffie-Hellman Server Params
Curve Type: named_curve (0x03)
Named Curve: secp256r1 (0x0017)
Pubkey Length: 65
Pubkey: 04581008311d54c64afad46c931c92911e8df9b0edc6dd1d9703ab678412cf5af53c11d8…
Signature Algorithm: ecdsa_secp256r1_sha256 (0x0403)
Signature Length: 72
Signature: 3046022100a986cc75f373f6400e7223c28e4c770acbe17ff6c85f09476c25948aa151c6…
我使用以下代码生成公钥和私钥。使用
prime256v1
,因为它与 secp256r1
表示相同。 RFC 4492,附录-A
const ecdh = crypto.createECDH('prime256v1');
ecdh.generateKeys();
const publicKey = ecdh.getPublicKey(null, 'uncompressed');
const privateKey = ecdh.getPrivateKey();
现在我的服务器上有以下内容。
clientRandom
- 来自 ClientHello
serverRandom
- 我的服务器生成了它。已发送至ServerHello
。
serverPrivateKey
- 从上面的方块获得
serverPublicKey
- 从上面的街区获得。已发送ServerKeyExchange
。
clientPublicKey
- 来自 ClientKeyExchange
此后,我使用以下代码计算预主密钥
const ecdh = crypto.createECDH('prime256v1');
ecdh.setPrivateKey(serverPrivateKey);
const preMasterSecret = ecdh.computeSecret(clientPublicKey);
然后计算主密钥
const masterSecret = PRF(
preMasterSecret,
Buffer.from("master secret"),
Buffer.concat([clientRandom, serverRandom]),
48
);
然后计算密钥块。不计算 MAC 密钥,因为 RFC 5246, 6.2.3.3 表示 AEAD 密码“不使用 MAC 密钥”。
const seed = Buffer.concat([clientRandom, serverRandom]);
const length =
(2 * 16) + // client_write_key + server_write_key
(2 * 4); // client_write_IV + server_write_IV
const keyBlock = PRF(
masterSecret,
Buffer.from('key expansion'),
seed,
length
);
const keyLength = 16;
const ivLength = 4;
let start = 0, end = start + keyLength;
const clientWriteKey = keyBlock.slice(start, end);
start = end, end = start + keyLength;
const serverWriteKey = keyBlock.slice(start, end);
start = end, end = start + ivLength;
const clientWriteIV = keyBlock.slice(start, end);
start = end, end = start + ivLength;
const serverWriteIV = keyBlock.slice(start, end);
从服务器收到 EncryptedHandshakeMessage 后,我从有效负载中提取前 8 个字节以获取
nonce_explicit
值(如 RFC 5246, 6.2.3.3 中定义)
我按照上述链接中所述将
clientWriteIV
连接为 nonce_implicit
(按该顺序)并生成 nonce
。
const nonce = Buffer.concat([
clientWriteIV,
encryptedHandshakeMessage.slice(0, 8)
]);
然后,我根据 RFC 5246, 6.2.3.3 构建附加验证数据。由于我使用 DTLS,因此 seq_num 是根据 RFC 6347, 4.1.2.1.
生成的const aad = [
// epoch
0x00, 0x01,
//sequence number
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// content type
0x16,
// version
0xfe, 0xfd,
// length (48 bytes)
0x00, 0x30
]
然后我进行了如下解密操作
const decipher = crypto.createDecipheriv('aes-128-gcm', clientWriteKey, nonce);
decipher.setAuthTag(encryptedHandshakeMessage.slice(-16));
decipher.setAAD(Buffer.from(aad));
const decrypted = decipher.update(encryptedHandshakeMessage.slice(0, -16), null, 'utf8');
decipher.final();
行
decipher.final()
失败并出现以下错误。
Error: Unsupported state or unable to authenticate data
我在 DTLS 1.2 模式下使用 OpenSSL s_client 作为客户端。我使用
-keylogfile
参数记录了密钥,输出如下
CLIENT_RANDOM 6e0ed5aec5691fc199d1019ecfc1e4a8629c9ed4208dcad30e8005c82364099c d228852f0c13a0b3122065519c3d4b66b8c2674232686221f77c5cfb49001e99528be5900ea1d88a23c533f8c544c6d9
如果我的知识是正确的,第二部分表示主密钥与我在服务器中计算的主密钥不匹配。
我做错了什么?
完整代码供参考。
const crypto = require('crypto');
const clientRandom = Buffer.from("6e0ed5aec5691fc199d1019ecfc1e4a8629c9ed4208dcad30e8005c82364099c", 'hex')
const serverRandom = Buffer.from("9c27d9ef09af50cf0c9aba9e535714ed07f2f522ec672a4c17ff550604f950e4", 'hex')
const serverPrivateKey = Buffer.from("4ac2c6a823a455c19101b2b61d3fd6b4c923cb6512f7b1488a717a86c4b1e9c0", 'hex')
const serverPublicKey = Buffer.from("04581008311d54c64afad46c931c92911e8df9b0edc6dd1d9703ab678412cf5af53c11d81935b463e3da85f56dabb0aa998f3dd09ea3629a05cff3a0c801f7bdc7", 'hex')
const clientPublicKey = Buffer.from("04ad753ed35f557cf9419d7d03d51510b4c1fa282ddd0c432b43de1256cf561df16c7b355b17328f2f72ec9ce4361d7855d92e643904e501387b46a4edfe1c67d6", 'hex')
const encryptedHandshakeMessage = Buffer.from("713a474b4484511c7de96486fa9bc8d1716ed73717c42c0b56277ab6453d349770db77bb40353fb622c5c3422d79b981", 'hex')
const aad = [
// epoch
0x00, 0x01,
//sequence number
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// content type
0x16,
// version
0xfe, 0xfd,
// length (48 bytes)
0x00, 0x30
]
function PRF(secret, label, seed, length){
const labelSeedConcat = Buffer.concat([label, seed]);
function hmacHash(secret, seed){
const hmac = crypto.createHmac('sha256', Buffer.from(secret));
hmac.update(seed);
return hmac.digest();
}
function A(i){
if(i === 0){
return labelSeedConcat;
}
else{
return hmacHash(secret, A(i-1));
}
}
return Buffer.concat([
hmacHash(secret, Buffer.concat([A(1), labelSeedConcat])),
hmacHash(secret, Buffer.concat([A(2), labelSeedConcat])),
hmacHash(secret, Buffer.concat([A(3), labelSeedConcat])),
hmacHash(secret, Buffer.concat([A(4), labelSeedConcat])),
]).slice(0, length);
}
const ecdh = crypto.createECDH('prime256v1');
ecdh.setPrivateKey(serverPrivateKey);
const preMasterSecret = ecdh.computeSecret(clientPublicKey);
const masterSecret = PRF(
preMasterSecret,
Buffer.from("master secret"),
Buffer.concat([clientRandom, serverRandom]),
48
);
console.log('Master secret:', masterSecret.toString('hex'));
function computeSymmetricKeysFromMasterSecret(masterSecret, clientRandom, serverRandom){
const seed = Buffer.concat([clientRandom, serverRandom]);
const length =
(2 * 16) + // client_write_key + server_write_key
(2 * 4); // client_write_IV + server_write_IV
const keyBlock = PRF(
masterSecret,
Buffer.from('key expansion'),
seed,
length
);
const macKeyLength = 32;
const keyLength = 16;
const ivLength = 4;
let start = 0, end = 0;
start = end, end = start + keyLength;
const clientWriteKey = keyBlock.slice(start, end);
start = end, end = start + keyLength;
const serverWriteKey = keyBlock.slice(start, end);
start = end, end = start + ivLength;
const clientWriteIV = keyBlock.slice(start, end);
start = end, end = start + ivLength;
const serverWriteIV = keyBlock.slice(start, end);
return {clientWriteKey, serverWriteKey, clientWriteIV, serverWriteIV};
}
const {clientWriteKey, serverWriteKey, clientWriteIV, serverWriteIV} = computeSymmetricKeysFromMasterSecret(masterSecret, clientRandom, serverRandom);
/*
nonce = implicit nonce (4 bytes) + explicit nonce (8 bytes)
*/
const nonce = Buffer.concat([
clientWriteIV,
encryptedHandshakeMessage.slice(0, 8)
]);
const decipher = crypto.createDecipheriv('aes-128-gcm', clientWriteKey, nonce);
decipher.setAuthTag(encryptedHandshakeMessage.slice(-16));
decipher.setAAD(Buffer.from(aad));
const decrypted = decipher.update(encryptedHandshakeMessage.slice(0, -16), null, 'utf8');
decipher.final();
console.log('Decrypted:', decrypted);
然后我想,在密钥块生成期间,也许我应该实际计算 MAC 密钥,但不使用它?我也尝试过。也没有用。
const seed = Buffer.concat([clientRandom, serverRandom]);
const length =
(2 * 32) + // client_write_MAC_key + server_write_MAC_key
(2 * 16) + // client_write_key + server_write_key
(2 * 4); // client_write_IV + server_write_IV
const keyBlock = PRF(
masterSecret,
Buffer.from('key expansion'),
seed,
length
);
const macKeyLength = 32;
const keyLength = 16;
const ivLength = 4;
let start = 0, end = 0;
start = end, end = start + macKeyLength;
const clientWriteMACKey = keyBlock.slice(start, end);
start = end, end = start + macKeyLength;
const serverWriteMACKey = keyBlock.slice(start, end);
start = end, end = start + keyLength;
const clientWriteKey = keyBlock.slice(start, end);
start = end, end = start + keyLength;
const serverWriteKey = keyBlock.slice(start, end);
start = end, end = start + ivLength;
const clientWriteIV = keyBlock.slice(start, end);
start = end, end = start + ivLength;
const serverWriteIV = keyBlock.slice(start, end);
一次握手需要大量的计算,而密码学的令人讨厌的一点是,一个小错误需要付出很大的努力,并且很难追溯。
我尝试检查您的计算,其中一个问题可能是 PRF 使用的种子。为了生成主秘密,使用的种子
Buffer.concat([clientRandom, serverRandom]),
很好,但是对于键扩展来说,是倒序的,
const seed = Buffer.concat([clientRandom, serverRandom]);
一定是
const seed = Buffer.concat([serverRandom, clientRandom]);
key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random +
SecurityParameters.client_random);
此处的顺序与计算主密钥的顺序进行了交换,请参阅RFC 5246, 8.1,计算主密钥
master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)
[0..47];
请注意,这可能是一个问题,但可能不是唯一的问题!