Skip to content

Commit f40af6d

Browse files
committed
aesgcm padding disclaimer
1 parent 0b96416 commit f40af6d

File tree

3 files changed

+181
-14
lines changed

3 files changed

+181
-14
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. The format
55
## Table of Contents
66

77
- [Unreleased](#unreleased)
8+
- [1.9.15 - 2025-12-09](#1925---2025-12-09)
89
- [1.9.24 - 2025-12-09](#1924---2025-12-09)
910
- [1.9.23 - 2025-12-08](#1923---2025-12-08)
1011
- [1.9.22 - 2025-12-05](#1922---2025-12-04)
@@ -197,6 +198,13 @@ All notable changes to this project will be documented in this file. The format
197198

198199
---
199200

201+
## [1.9.25] - 2025-12-09
202+
203+
### Added
204+
- Documentation disclaimer for our specific AESGCM implementation of padding for additional authenticated data (AAD) and ciphertext.
205+
206+
---
207+
200208
## [1.9.24] - 2025-12-09
201209

202210
### Fixed

docs/reference/primitives.md

Lines changed: 131 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4958,20 +4958,16 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
49584958
---
49594959
## Functions
49604960
4961-
| |
4962-
| --- |
4963-
| [AES](#function-aes) |
4964-
| [AESGCM](#function-aesgcm) |
4965-
| [AESGCMDecrypt](#function-aesgcmdecrypt) |
4966-
| [assertValidHex](#function-assertvalidhex) |
4967-
| [base64ToArray](#function-base64toarray) |
4968-
| [ghash](#function-ghash) |
4969-
| [normalizeHex](#function-normalizehex) |
4970-
| [pbkdf2](#function-pbkdf2) |
4971-
| [red](#function-red) |
4972-
| [toArray](#function-toarray) |
4973-
| [toBase64](#function-tobase64) |
4974-
| [verifyNotNull](#function-verifynotnull) |
4961+
| | |
4962+
| --- | --- |
4963+
| [AES](#function-aes) | [pbkdf2](#function-pbkdf2) |
4964+
| [AESGCM](#function-aesgcm) | [realHtonl](#function-realhtonl) |
4965+
| [AESGCMDecrypt](#function-aesgcmdecrypt) | [red](#function-red) |
4966+
| [assertValidHex](#function-assertvalidhex) | [swapBytes32](#function-swapbytes32) |
4967+
| [base64ToArray](#function-base64toarray) | [toArray](#function-toarray) |
4968+
| [ghash](#function-ghash) | [toBase64](#function-tobase64) |
4969+
| [htonl](#function-htonl) | [verifyNotNull](#function-verifynotnull) |
4970+
| [normalizeHex](#function-normalizehex) | |
49754971
49764972
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
49774973
@@ -4988,6 +4984,47 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
49884984
---
49894985
### Function: AESGCM
49904986

4987+
SECURITY NOTE – NON-STANDARD AES-GCM PADDING
4988+
4989+
This implementation intentionally deviates from NIST SP 800-38D’s AES-GCM
4990+
specification in how the GHASH input is formed when the additional
4991+
authenticated data (AAD) or ciphertext length is zero.
4992+
4993+
In the standard, AAD and ciphertext are each padded with the minimum number
4994+
of zero bytes required to reach a multiple of 16 bytes; when the length is
4995+
already a multiple of 16 (including the case length = 0), no padding block
4996+
is added. In this implementation, when AAD.length === 0 or ciphertext.length
4997+
=== 0, an extra 16-byte block of zeros is appended before the length fields
4998+
are processed. The same formatting logic is used symmetrically in both
4999+
AESGCM (encryption) and AESGCMDecrypt (decryption).
5000+
5001+
As a result:
5002+
- Authentication tags produced here are NOT compatible with tags produced
5003+
by standards-compliant AES-GCM implementations in the cases where AAD
5004+
or ciphertext are empty.
5005+
- Ciphertexts generated by this code must be decrypted by this exact
5006+
implementation (or one that reproduces the same GHASH formatting), and
5007+
must not be mixed with ciphertexts produced by a strictly standard
5008+
AES-GCM library.
5009+
5010+
Cryptographic impact: this change alters only the encoding of the message
5011+
that is input to GHASH; it does not change the block cipher, key derivation,
5012+
IV handling, or the basicencrypt-then-MAC over (AAD, ciphertext, lengths)”
5013+
structure of AES-GCM. Under the usual assumptions that AES is a secure block
5014+
cipher and GHASH with a secret subkey is a secure polynomial MAC, this
5015+
variant continues to provide confidentiality and integrity for data encrypted
5016+
and decrypted consistently with this implementation. We are not aware of any
5017+
attack that exploits the presence of this extra zero block when AAD or
5018+
ciphertext are empty.
5019+
5020+
However, this padding behavior is non-compliant with NIST SP 800-38D and has
5021+
not been analyzed as extensively as standard AES-GCM. Code that requires
5022+
strict standards compliance or interoperability with external AES-GCM
5023+
implementations SHOULD NOT use this module as-is. Any future migration to a
5024+
fully compliant AES-GCM encoding will require a compatibility strategy, as
5025+
existing ciphertexts produced by this implementation will otherwise become
5026+
undecryptable.
5027+
49915028
```ts
49925029
export function AESGCM(plainText: number[], additionalAuthenticatedData: number[], initializationVector: number[], key: number[]): {
49935030
result: number[];
@@ -5033,6 +5070,15 @@ export function ghash(input: number[], hashSubKey: number[]): number[]
50335070

50345071
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
50355072

5073+
---
5074+
### Function: htonl
5075+
5076+
```ts
5077+
export function htonl(w: number): number
5078+
```
5079+
5080+
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
5081+
50365082
---
50375083
### Function: normalizeHex
50385084

@@ -5070,6 +5116,42 @@ Argument Details
50705116

50715117
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
50725118

5119+
---
5120+
### Function: realHtonl
5121+
5122+
Converts a 32-bit unsigned integer from host byte order to network byte order.
5123+
5124+
Unlike the legacy `htonl()` implementation (which always swapped bytes),
5125+
this function behaves like the traditional C `htonl()`:
5126+
5127+
- On **little-endian** machines → performs a byte swap.
5128+
- On **big-endian** machines → returns the value unchanged.
5129+
5130+
This function is provided to resolve TOB-20, which identified that the
5131+
previous `htonl()` implementation had a misleading name and did not match
5132+
platform-dependent semantics.
5133+
5134+
Example
5135+
5136+
```ts
5137+
realHtonl(0x11223344) // → 0x44332211 on little-endian systems
5138+
```
5139+
5140+
```ts
5141+
export function realHtonl(w: number): number
5142+
```
5143+
5144+
Returns
5145+
5146+
The value converted to network byte order.
5147+
5148+
Argument Details
5149+
5150+
+ **w**
5151+
+ A 32-bit unsigned integer.
5152+
5153+
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
5154+
50735155
---
50745156
### Function: red
50755157

@@ -5079,6 +5161,41 @@ export function red(x: bigint): bigint
50795161

50805162
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
50815163

5164+
---
5165+
### Function: swapBytes32
5166+
5167+
Unconditionally swaps the byte order of a 32-bit unsigned integer.
5168+
5169+
This function performs a strict 32-bit byte swap regardless of host
5170+
endianness. It is equivalent to the behavior commonly referred to as
5171+
`bswap32` in low-level libraries.
5172+
5173+
This function is introduced as part of TOB-20 to provide a clearly-named
5174+
alternative to `htonl()`, which was previously implemented as an
5175+
unconditional byte swap and did not match the semantics of the traditional
5176+
C `htonl()` function.
5177+
5178+
Example
5179+
5180+
```ts
5181+
swapBytes32(0x11223344) // → 0x44332211
5182+
```
5183+
5184+
```ts
5185+
export function swapBytes32(w: number): number
5186+
```
5187+
5188+
Returns
5189+
5190+
The value with its byte order reversed.
5191+
5192+
Argument Details
5193+
5194+
+ **w**
5195+
+ A 32-bit unsigned integer.
5196+
5197+
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
5198+
50825199
---
50835200
### Function: toArray
50845201

src/primitives/AESGCM.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,48 @@ function gctr (
323323
return output
324324
}
325325

326+
/**
327+
* SECURITY NOTE – NON-STANDARD AES-GCM PADDING
328+
*
329+
* This implementation intentionally deviates from NIST SP 800-38D’s AES-GCM
330+
* specification in how the GHASH input is formed when the additional
331+
* authenticated data (AAD) or ciphertext length is zero.
332+
*
333+
* In the standard, AAD and ciphertext are each padded with the minimum number
334+
* of zero bytes required to reach a multiple of 16 bytes; when the length is
335+
* already a multiple of 16 (including the case length = 0), no padding block
336+
* is added. In this implementation, when AAD.length === 0 or ciphertext.length
337+
* === 0, an extra 16-byte block of zeros is appended before the length fields
338+
* are processed. The same formatting logic is used symmetrically in both
339+
* AESGCM (encryption) and AESGCMDecrypt (decryption).
340+
*
341+
* As a result:
342+
* - Authentication tags produced here are NOT compatible with tags produced
343+
* by standards-compliant AES-GCM implementations in the cases where AAD
344+
* or ciphertext are empty.
345+
* - Ciphertexts generated by this code must be decrypted by this exact
346+
* implementation (or one that reproduces the same GHASH formatting), and
347+
* must not be mixed with ciphertexts produced by a strictly standard
348+
* AES-GCM library.
349+
*
350+
* Cryptographic impact: this change alters only the encoding of the message
351+
* that is input to GHASH; it does not change the block cipher, key derivation,
352+
* IV handling, or the basic “encrypt-then-MAC over (AAD, ciphertext, lengths)”
353+
* structure of AES-GCM. Under the usual assumptions that AES is a secure block
354+
* cipher and GHASH with a secret subkey is a secure polynomial MAC, this
355+
* variant continues to provide confidentiality and integrity for data encrypted
356+
* and decrypted consistently with this implementation. We are not aware of any
357+
* attack that exploits the presence of this extra zero block when AAD or
358+
* ciphertext are empty.
359+
*
360+
* However, this padding behavior is non-compliant with NIST SP 800-38D and has
361+
* not been analyzed as extensively as standard AES-GCM. Code that requires
362+
* strict standards compliance or interoperability with external AES-GCM
363+
* implementations SHOULD NOT use this module as-is. Any future migration to a
364+
* fully compliant AES-GCM encoding will require a compatibility strategy, as
365+
* existing ciphertexts produced by this implementation will otherwise become
366+
* undecryptable.
367+
*/
326368
export function AESGCM (
327369
plainText: number[],
328370
additionalAuthenticatedData: number[],

0 commit comments

Comments
 (0)