Skip to content

Commit 5f1e9e2

Browse files
tniessentargos
authored andcommitted
crypto: make authTagLength optional for CC20P1305
PR-URL: #42427 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Filip Skokan <[email protected]>
1 parent 10e9868 commit 5f1e9e2

File tree

3 files changed

+80
-15
lines changed

3 files changed

+80
-15
lines changed

doc/api/crypto.md

+24-8
Original file line numberDiff line numberDiff line change
@@ -2944,6 +2944,10 @@ Checks the primality of the `candidate`.
29442944
added: v0.1.94
29452945
deprecated: v10.0.0
29462946
changes:
2947+
- version: REPLACEME
2948+
pr-url: https://github.com/nodejs/node/pull/42427
2949+
description: The `authTagLength` option is now optional when using the
2950+
`chacha20-poly1305` cipher and defaults to 16 bytes.
29472951
- version: v15.0.0
29482952
pr-url: https://github.com/nodejs/node/pull/35093
29492953
description: The password argument can be an ArrayBuffer and is limited to
@@ -2968,12 +2972,12 @@ Creates and returns a `Cipher` object that uses the given `algorithm` and
29682972
`password`.
29692973

29702974
The `options` argument controls stream behavior and is optional except when a
2971-
cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) or `chacha20-poly1305` is used.
2972-
In that case, the
2975+
cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) is used. In that case, the
29732976
`authTagLength` option is required and specifies the length of the
29742977
authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength`
29752978
option is not required but can be used to set the length of the authentication
29762979
tag that will be returned by `getAuthTag()` and defaults to 16 bytes.
2980+
For `chacha20-poly1305`, the `authTagLength` option defaults to 16 bytes.
29772981

29782982
The `algorithm` is dependent on OpenSSL, examples are `'aes192'`, etc. On
29792983
recent OpenSSL releases, `openssl list -cipher-algorithms` will
@@ -3004,6 +3008,10 @@ Adversaries][] for details.
30043008
<!-- YAML
30053009
added: v0.1.94
30063010
changes:
3011+
- version: REPLACEME
3012+
pr-url: https://github.com/nodejs/node/pull/42427
3013+
description: The `authTagLength` option is now optional when using the
3014+
`chacha20-poly1305` cipher and defaults to 16 bytes.
30073015
- version: v15.0.0
30083016
pr-url: https://github.com/nodejs/node/pull/35093
30093017
description: The password and iv arguments can be an ArrayBuffer and are
@@ -3040,12 +3048,12 @@ Creates and returns a `Cipher` object, with the given `algorithm`, `key` and
30403048
initialization vector (`iv`).
30413049

30423050
The `options` argument controls stream behavior and is optional except when a
3043-
cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) or `chacha20-poly1305` is used.
3044-
In that case, the
3051+
cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) is used. In that case, the
30453052
`authTagLength` option is required and specifies the length of the
30463053
authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength`
30473054
option is not required but can be used to set the length of the authentication
30483055
tag that will be returned by `getAuthTag()` and defaults to 16 bytes.
3056+
For `chacha20-poly1305`, the `authTagLength` option defaults to 16 bytes.
30493057

30503058
The `algorithm` is dependent on OpenSSL, examples are `'aes192'`, etc. On
30513059
recent OpenSSL releases, `openssl list -cipher-algorithms` will
@@ -3073,6 +3081,10 @@ given IV will be.
30733081
added: v0.1.94
30743082
deprecated: v10.0.0
30753083
changes:
3084+
- version: REPLACEME
3085+
pr-url: https://github.com/nodejs/node/pull/42427
3086+
description: The `authTagLength` option is now optional when using the
3087+
`chacha20-poly1305` cipher and defaults to 16 bytes.
30763088
- version: v10.10.0
30773089
pr-url: https://github.com/nodejs/node/pull/21447
30783090
description: Ciphers in OCB mode are now supported.
@@ -3089,10 +3101,10 @@ Creates and returns a `Decipher` object that uses the given `algorithm` and
30893101
`password` (key).
30903102

30913103
The `options` argument controls stream behavior and is optional except when a
3092-
cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) or `chacha20-poly1305` is used.
3093-
In that case, the
3104+
cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) is used. In that case, the
30943105
`authTagLength` option is required and specifies the length of the
30953106
authentication tag in bytes, see [CCM mode][].
3107+
For `chacha20-poly1305`, the `authTagLength` option defaults to 16 bytes.
30963108

30973109
The implementation of `crypto.createDecipher()` derives keys using the OpenSSL
30983110
function [`EVP_BytesToKey`][] with the digest algorithm set to MD5, one
@@ -3111,6 +3123,10 @@ to create the `Decipher` object.
31113123
<!-- YAML
31123124
added: v0.1.94
31133125
changes:
3126+
- version: REPLACEME
3127+
pr-url: https://github.com/nodejs/node/pull/42427
3128+
description: The `authTagLength` option is now optional when using the
3129+
`chacha20-poly1305` cipher and defaults to 16 bytes.
31143130
- version: v11.6.0
31153131
pr-url: https://github.com/nodejs/node/pull/24234
31163132
description: The `key` argument can now be a `KeyObject`.
@@ -3143,12 +3159,12 @@ Creates and returns a `Decipher` object that uses the given `algorithm`, `key`
31433159
and initialization vector (`iv`).
31443160

31453161
The `options` argument controls stream behavior and is optional except when a
3146-
cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) or `chacha20-poly1305` is used.
3147-
In that case, the
3162+
cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) is used. In that case, the
31483163
`authTagLength` option is required and specifies the length of the
31493164
authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength`
31503165
option is not required but can be used to restrict accepted authentication tags
31513166
to those with the specified length.
3167+
For `chacha20-poly1305`, the `authTagLength` option defaults to 16 bytes.
31523168

31533169
The `algorithm` is dependent on OpenSSL, examples are `'aes192'`, etc. On
31543170
recent OpenSSL releases, `openssl list -cipher-algorithms` will

src/crypto/crypto_cipher.cc

+11-3
Original file line numberDiff line numberDiff line change
@@ -574,9 +574,17 @@ bool CipherBase::InitAuthenticated(
574574
}
575575
} else {
576576
if (auth_tag_len == kNoAuthTagLength) {
577-
THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
578-
env(), "authTagLength required for %s", cipher_type);
579-
return false;
577+
// We treat ChaCha20-Poly1305 specially. Like GCM, the authentication tag
578+
// length defaults to 16 bytes when encrypting. Unlike GCM, the
579+
// authentication tag length also defaults to 16 bytes when decrypting,
580+
// whereas GCM would accept any valid authentication tag length.
581+
if (EVP_CIPHER_CTX_nid(ctx_.get()) == NID_chacha20_poly1305) {
582+
auth_tag_len = 16;
583+
} else {
584+
THROW_ERR_CRYPTO_INVALID_AUTH_TAG(
585+
env(), "authTagLength required for %s", cipher_type);
586+
return false;
587+
}
580588
}
581589

582590
// TODO(tniessen) Support CCM decryption in FIPS mode

test/parallel/test-crypto-authenticated.js

+45-4
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,9 @@ for (const test of TEST_CASES) {
9696

9797
const isCCM = /^aes-(128|192|256)-ccm$/.test(test.algo);
9898
const isOCB = /^aes-(128|192|256)-ocb$/.test(test.algo);
99-
const isChacha20Poly1305 = test.algo === 'chacha20-poly1305';
10099

101100
let options;
102-
if (isCCM || isOCB || isChacha20Poly1305)
101+
if (isCCM || isOCB)
103102
options = { authTagLength: test.tag.length / 2 };
104103

105104
const inputEncoding = test.plainIsHex ? 'hex' : 'ascii';
@@ -659,8 +658,7 @@ for (const test of TEST_CASES) {
659658
assert.throws(() => crypto.createCipheriv(
660659
valid.algo,
661660
Buffer.from(valid.key, 'hex'),
662-
Buffer.from(H(prefix) + valid.iv, 'hex'),
663-
{ authTagLength: valid.tag.length / 2 }
661+
Buffer.from(H(prefix) + valid.iv, 'hex')
664662
), errMessages.length, `iv length ${ivLength} was not rejected`);
665663

666664
function H(length) { return '00'.repeat(length); }
@@ -745,3 +743,46 @@ for (const test of TEST_CASES) {
745743
}
746744
}
747745
}
746+
747+
// ChaCha20-Poly1305 should default to an authTagLength of 16. When encrypting,
748+
// this matches the behavior of GCM ciphers. When decrypting, however, it is
749+
// stricter than GCM in that it only allows authentication tags that are exactly
750+
// 16 bytes long, whereas, when no authTagLength was specified, GCM would accept
751+
// shorter tags as long as their length was valid according to NIST SP 800-38D.
752+
// For ChaCha20-Poly1305, we intentionally deviate from that because there are
753+
// no recommended or approved authentication tag lengths below 16 bytes.
754+
{
755+
const rfcTestCases = TEST_CASES.filter(({ algo, tampered }) => {
756+
return algo === 'chacha20-poly1305' && tampered === false;
757+
});
758+
assert.strictEqual(rfcTestCases.length, 1);
759+
760+
const [testCase] = rfcTestCases;
761+
const key = Buffer.from(testCase.key, 'hex');
762+
const iv = Buffer.from(testCase.iv, 'hex');
763+
const aad = Buffer.from(testCase.aad, 'hex');
764+
765+
for (const opt of [
766+
undefined,
767+
{ authTagLength: undefined },
768+
{ authTagLength: 16 },
769+
]) {
770+
const cipher = crypto.createCipheriv('chacha20-poly1305', key, iv, opt);
771+
const ciphertext = Buffer.concat([
772+
cipher.setAAD(aad).update(testCase.plain, 'hex'),
773+
cipher.final(),
774+
]);
775+
const authTag = cipher.getAuthTag();
776+
777+
assert.strictEqual(ciphertext.toString('hex'), testCase.ct);
778+
assert.strictEqual(authTag.toString('hex'), testCase.tag);
779+
780+
const decipher = crypto.createDecipheriv('chacha20-poly1305', key, iv, opt);
781+
const plaintext = Buffer.concat([
782+
decipher.setAAD(aad).update(ciphertext),
783+
decipher.setAuthTag(authTag).final(),
784+
]);
785+
786+
assert.strictEqual(plaintext.toString('hex'), testCase.plain);
787+
}
788+
}

0 commit comments

Comments
 (0)