如何在Node.js中进行基本AEAD加密

时间:2019-05-22 06:02:14

标签: node.js encryption cryptography

我正在尝试在我的节点应用程序中实现基本的加密功能。问题在于我发现的几乎所有示例和指南(包括Node docs上的示例和指南)都使用未经身份验证的密码。

所以,我希望有人可以看看我拥有的东西,并让我知道我是在做愚蠢的事情还是让自己不受任何明显的旁道攻击。

我运行此代码时,除一种情况外,一切都按预期工作(即应该通过的测试和应该失败的测试)。

似乎我可以将任意数量的数据附加到密文的末尾,并且消息仍会解密而没有错误。我认为,对密文进行任何修改后,经过身份验证的加密系统应该会失败。您可以通过将tweakCharAt变量更改为类似100的值来进行测试。我做错了吗?这是某种预期的行为吗?

const { createCipheriv, createDecipheriv, randomBytes, randomFillSync } = require('crypto');
const { promisify } = require('util');

const randomBytesPromise = promisify(randomBytes);

const algorithm = 'chacha20-poly1305';
const ivLen = 12; // IETF version of ChaCha20-Poly1305 uses a 96 bit (12 byte) nonce
const authTagLength = 16; // Poly1305 generates a 128 bit (16 byte) authentication tag

// Takes a Node Transform stream (like Cipher and Decipher) + some data and returns a Promise
const streamPromise = (stream, data) => new Promise((resolve, reject) => {

    // Create an array to hold the chunks of binary data
    const chunks = [];

    // Setup stream event handlers
    // See: https://nodejs.org/api/crypto.html#crypto_class_cipher
    stream.on('readable', () => {
        let chunk;

        // eslint-disable-next-line no-cond-assign
        while ((chunk = stream.read()) !== null) {

            chunks.push(chunk);
        }
    });

    stream.on('end', () => { resolve(Buffer.concat(chunks)); });

    stream.on('error', (error) => { reject(error); });

    // Write the given data to the stream and then close it up
    stream.write(data);
    stream.end();
});

const encrypt = (plainText, key, authenticatedData = Buffer.alloc(0)) => (

    randomBytesPromise(ivLen)
        .then((rand) => {

            const iv = rand;

            const cipher = createCipheriv(algorithm, key, iv, { authTagLength });

            cipher.setAAD(authenticatedData);

            return streamPromise(cipher, plainText)
                .then(encMessage => [iv, authenticatedData, cipher.getAuthTag(), encMessage]);
        })
        .then(parts => parts.map(buffer => buffer.toString('base64')).join(':'))
);

const decrypt = (cipherText, key) => {

    const [
        iv,
        authenticatedData,
        authTag,
        message
    ] = cipherText.split(':').map(string => Buffer.from(string, 'base64'));

    const decipher = createDecipheriv(algorithm, key, iv, { authTagLength });

    decipher.setAAD(authenticatedData);
    decipher.setAuthTag(authTag);

    return streamPromise(decipher, message);
};

const test = (message, keyBuffer, authenticatedData, testNum, tamperWithCipherText = false) => (

    encrypt(message, keyBuffer, authenticatedData)
        .then((cipherText) => {

            console.log(testNum + ' cipher text:     ', cipherText);

            let newCipherText = cipherText;

            if (tamperWithCipherText) {

                const tweakCharAt = 30; // 100;

                newCipherText = newCipherText.substring(0, tweakCharAt)
                    + '0' // '==/I5QUaGV'
                    + newCipherText.substring(tweakCharAt + 1);

                console.log(testNum + ' new cipher text: ', newCipherText);
            }

            console.log(testNum + ' AAD:             ', Buffer.from(newCipherText.split(':')[1], 'base64').toString('utf8'));

            return decrypt(newCipherText, keyBuffer);
        })
        .then((plainText) => {

            console.log(testNum + ' plain text:      ', plainText.toString('utf8') + '\n');
        })
        .catch((error) => {

            console.error(testNum, error);
        })
);

// Run some tests
const key = Buffer.allocUnsafe(32);
randomFillSync(key);
const testMessage = 'Top secret message';
const authData = Buffer.from('AAD data here', 'utf8');

// Test without authenticated data
test(testMessage, key, undefined, 1); // should work
test(testMessage, key, undefined, 2, true); // should error

// Test with authenticated data
test(testMessage, key, authData, 3); // should work
test(testMessage, key, authData, 4, true); // should error

样本输出

这是在节点12中运行上述代码的一些输出,这些设置具有不同的修改密文设置。

修改中间的字符(检测到)

1 cipher text:      /mFoBKirqH8DZ3Wv::JayX8aiYi77ApLyhI6yYfw==:uBOj318Zf9M2qaBgIkiEX2MC
1 AAD:
1 plain text:       Top secret message

2 cipher text:      QnEbbrFpYhTLPuhk::owdqDGokK9WoXE0Od5DDTQ==:QROQ3/Q1qhKz4/FSQbwFuVoG
2 new cipher text:  QnEbbrFpYhTLPuhk::owdqDGokK9Wo0E0Od5DDTQ==:QROQ3/Q1qhKz4/FSQbwFuVoG
2 AAD:
2 Error: Unsupported state or unable to authenticate data
    at Decipheriv._flush (internal/crypto/cipher.js:144:29)
    at Decipheriv.prefinish (_stream_transform.js:140:10)
    at Decipheriv.emit (events.js:200:13)
    ...
3 cipher text:      36cf2xXpsZnfFn+I:QUFEIGRhdGEgaGVyZQ==:NRRNaY9yAJ2yu7z4Nh0+Tw==:ZdY9xtFh0zcXgDcydG+zEhug
3 AAD:              AAD data here
3 plain text:       Top secret message

4 cipher text:      HWA0BWBPHe/3kFps:QUFEIGRhdGEgaGVyZQ==:w6s+CYMeU0SNoCtgbeRBsw==:2TOwGnbT2T1ZZ8qKe90BU2VJ
4 new cipher text:  HWA0BWBPHe/3kFps:QUFEIGRhdGEga0VyZQ==:w6s+CYMeU0SNoCtgbeRBsw==:2TOwGnbT2T1ZZ8qKe90BU2VJ
4 AAD:              AAD data kEre
4 Error: Unsupported state or unable to authenticate data
    at Decipheriv._flush (internal/crypto/cipher.js:144:29)
    at Decipheriv.prefinish (_stream_transform.js:140:10)
    at Decipheriv.emit (events.js:200:13)
    ...

添加到结尾,base64(未检测到)

1 cipher text:      cwtqwgX2mB5gmPh4::I2yPh0xoorrvVU+7edM1Ww==:V9qnpa25HEPw/v6zBc03tpYz
1 AAD:
1 plain text:       Top secret message

2 cipher text:      fe4spIYdiJDyIaUo::unK0AA6vI9bTDDVN/qm3Gw==:dE9bRpOi99ntKviitH4QdkGR
2 new cipher text:  fe4spIYdiJDyIaUo::unK0AA6vI9bTDDVN/qm3Gw==:dE9bRpOi99ntKviitH4QdkGRA
2 AAD:
2 plain text:       Top secret message

3 cipher text:      zzDP/FB3u4svhDoC:QUFEIGRhdGEgaGVyZQ==:GExIqrogg8Dm8ifQUhVxJg==:VH46trhHXsmcafPdImgArorb
3 AAD:              AAD data here
3 plain text:       Top secret message

4 cipher text:      TQE0QC8wUAElPYIC:QUFEIGRhdGEgaGVyZQ==:TpWkCLskrnRrQOykke+FoA==:YF3WEywHVzPNzuTg7UYKD3Ja
4 new cipher text:  TQE0QC8wUAElPYIC:QUFEIGRhdGEgaGVyZQ==:TpWkCLskrnRrQOykke+FoA==:YF3WEywHVzPNzuTg7UYKD3JaA
4 AAD:              AAD data here
4 plain text:       Top secret message

以十六进制添加到末尾(未检测到)

1 cipher text:      35ddfd0541be72780793894b::dc9ea755fbf00ee1bd9b737727b912af:5cd46cad985c2da058fa51967a675cb9bda6
1 AAD:
1 plain text:       Top secret message

2 cipher text:      0da4c80d45610cd3717e8497::0fcdea1d4ccfb9cb4347491430ccc4b3:3060d55728bc8ab2f75f80ce27ebe06f813e
2 new cipher text:  0da4c80d45610cd3717e8497::0fcdea1d4ccfb9cb4347491430ccc4b3:3060d55728bc8ab2f75f80ce27ebe06f813eA
2 AAD:
2 plain text:       Top secret message

3 cipher text:      f6ae6d9a72139b8611dd3798:41414420646174612068657265:f00d9d987f94038334603165605ce966:c6e635d8d81da0badd0ee98d64e007308853
3 AAD:              AAD data here
3 plain text:       Top secret message

4 cipher text:      a66fab00b07bc963cff3c721:41414420646174612068657265:05ed29d50a426df3b5a3616c7a467703:1b9d88bfdff01f5eacad9ce7cd9a117d6b08
4 new cipher text:  a66fab00b07bc963cff3c721:41414420646174612068657265:05ed29d50a426df3b5a3616c7a467703:1b9d88bfdff01f5eacad9ce7cd9a117d6b08A
4 AAD:              AAD data here
4 plain text:       Top secret message

0 个答案:

没有答案