Skip to content

AES-GCM nonce is reused for up to 32000 messages under the same key #3

Description

@NiKrause

encrypt() reuses one AES-GCM nonce for up to ivInterval (32 000) messages under the same key.

Measured on 0.0.2, four consecutive encrypt() calls:

iv 1: 7ae680c83d569711275044e8
iv 2: 7ae680c83d569711275044e8
iv 3: 7ae680c83d569711275044e8
iv 4: 7ae680c83d569711275044e8

src/aes-gcm-pbkdf2.js derives the nonce inside deriveEncryptionKey, which runs only when there is no key yet or when count % ivInterval === 0. Every message in between is encrypted under the same (key, nonce) pair.

Impact

A repeated nonce in AES-GCM is not a tunable cost. Two messages under one (key, nonce) disclose the XOR of their plaintexts, and the repeat allows the GHASH authentication subkey to be recovered — so ciphertexts become forgeable as well as readable. For an OrbitDB database this means an attacker holding the log can both read entries and mint entries that authenticate correctly.

The 32 000-message rotation does not bound this: one repeat is already sufficient.

Second defect, in the same path

decrypt() caches the derived key against the nonce:

if (!decryptionKey || nonce.toString() !== decryptionNonce?.toString()) {

The derived key depends on the salt, not the nonce. This is currently harmless only because the nonce never changes; with a per-message nonce it would re-run PBKDF2 (32 767 iterations) for every entry.

Fix

A fresh nonce per encrypt(), with the PBKDF2 key and salt still cached — the derivation is the expensive part, the nonce is not — and the decrypt cache keyed on the salt. PR follows.

test/iv.test.js asserts rotation at the interval; it needs to assert nonce uniqueness per message instead.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions