gcm-nonceless

Crates.iogcm-nonceless
lib.rsgcm-nonceless
version0.1.2
created_at2025-11-18 01:05:02.97529+00
updated_at2025-11-22 20:20:30.117139+00
descriptionDecrypts GCM encrypted data without access to the nonce.
homepage
repositoryhttps://github.com/eternal-flame-AD/gcm-nonceless
max_upload_size
id1937767
size37,530
饺子w (Yumechi) (eternal-flame-AD)

documentation

README

gcm-nonceless

Decrypts GCM encrypted data without access to the nonce.

Similar to situations in the following Stack Exchange question (note as of today the accepted answer is incorrect, you do not need any prior knowledge of the plaintext to decrypt the data with a lost nonce):

⚠️ Security Warning: Hazmat!

This is super sketchy cryptanalysis and should not be used in production but security research, forensics and data salvage applications.

All integrity and authenticity guarantees of AES-GCM are lost.

Theory of Operation

Firstly, reconstruct the GHASH key $H$.

$$ H = E_{K}(0^{128}) $$

Given the relationship:

$$ T = \mathtt{GHASH}{H}(A, C) \oplus E{K}(Y_0) $$

Rearrange. This part is done in recover_counter.

$$ E_{K}(Y_0) = \mathtt{GHASH}_{H}(A, C) \oplus T $$

$$ Y_0 = E_{K}^{-1}(\mathtt{GHASH}_{H}(A, C) \oplus T) $$

For practical purposes using standard 12 byte nonces, initialization vector $\mathtt{IV}$ is simply the first 12 bytes of $Y_0$ by construction. This is done in extract_nonce.

$$ Y_0 = \mathtt{IV} \mathbin| 0^{31}1 $$

For other nonce lengths, the initialization vector cannot be deterministically deduced, but the exact original nonce is not required for any further decryption. The following decryption always holds.

$$ P = C \oplus \mathtt{CTR}{32}(E{K}, \mathtt{INCR}_{32}(Y_0)) $$

Usage

Here's a piece of data encrypted using the first 12 bytes of SHA-512 initial hash value as the nonce:

echo 'ba0c7b18f7a44ade467aaca68ea871f5a7ccee25ee55d8167f062e92555e' \
     'd0cebad09f801a557bc40a5ba6354fec' \
    | xxd -r -p \
    | cargo run --features cli -- -caes-128 \
      -a AEADAEAD \
      --key 'YELLOW SUBMARINE'
Recovered nonce: 6a09e667f3bcc908bb67ae85
Meet me at 35.89278, 137.48028

Library usage:

use gcm_nonceless::{extract_nonce, instantiate_keystream, recover_counter};
use aes::cipher::{StreamCipher, generic_array::GenericArray, KeyInit};
let mut ct = *b"\xba\x0c\x7b\x18\xf7\xa4\x4a\xde\x46\x7a\xac\xa6\x8e\xa8\x71\xf5\xa7\xcc\xee\x25\xee\x55\xd8\x16\x7f\x06\x2e\x92\x55\x5e";
let tag =
    GenericArray::from(*b"\xd0\xce\xba\xd0\x9f\x80\x1a\x55\x7b\xc4\x0a\x5b\xa6\x35\x4f\xec");
let aad = *b"\xAE\xAD\xAE\xAD";
let key = GenericArray::from(*b"YELLOW SUBMARINE");
let cipher = aes::Aes128::new(&key);
let y0 = recover_counter(&cipher, &ct, Some(&tag), &aad);

let recovered_nonce = extract_nonce::<aes::Aes128>(&y0).unwrap();
assert_eq!(
    recovered_nonce.as_slice(),
    b"\x6a\x09\xe6\x67\xf3\xbc\xc9\x08\xbb\x67\xae\x85"
);
let mut recovered_keystream = instantiate_keystream(&cipher, &y0);
recovered_keystream.apply_keystream(&mut ct);
assert_eq!(ct.as_slice(), b"Meet me at 35.89278, 137.48028");
Commit count: 0

cargo fmt