解密 GenericAEADCipher 失败。我做错了什么?

问题描述 投票:0回答:1

我正在尝试在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);
tls1.2 aes-gcm dtls
1个回答
0
投票

一次握手需要大量的计算,而密码学的令人讨厌的一点是,一个小错误需要付出很大的努力,并且很难追溯。

我尝试检查您的计算,其中一个问题可能是 PRF 使用的种子。为了生成主秘密,使用的种子

Buffer.concat([clientRandom, serverRandom]),

很好,但是对于键扩展来说,是倒序的,

const seed = Buffer.concat([clientRandom, serverRandom]);

一定是

const seed = Buffer.concat([serverRandom, clientRandom]);

参见 RFC5246,6.3 关键计算

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];

请注意,这可能是一个问题,但可能不是唯一的问题!

© www.soinside.com 2019 - 2024. All rights reserved.