The XAES-256-GCM extended-nonce AEAD
words.filippo.io
words.filippo.io
In AES-CBC terms, the algorithm can be described as:
1. L = AES-CBC-256ₖ(iv = 0¹²⁸, plaintext = 0¹²⁸)[:16]
2. If MSB₁(L) = 0, then K1 = L << 1;
Else K1 = (L << 1) ⊕ 0¹²⁰10000111
3. M1 = 0x00 || 0x01 || X || 0x00 || N[:12]
4. M2 = 0x00 || 0x02 || X || 0x00 || N[:12]
5. Kₓ = AES-CBC-256ₖ(iv = K1, plaintext = M1)[:16] || AES-CBC-256ₖ(iv = K1, plaintext = M2)[:16]
6. Nₓ = N[12:]
Where AES-CBC-256 returns the first 128-bit block of the ciphertext, discarding the padded block. (Thus, if you can't turn off padding, it costs three additional AES calls with the same key compared to a lower level implementation — not bad). After deriving a key, use it with the standard AES-GCM.Here's my JS implementation based on WebCrypto API, which uses this fact: https://github.com/dchest/xaes
It accepts a proper CryptoKey intended for AES-CBC, supporting all CryptoKey features, e.g. storing it in IndexedDB with "extractable" bit set to false.
Great job, Filippo!
I guess crypto pros are ok with notation. But for hobbyist it requires some reverse engineering every time.
for those of you(like me) wondering where this apparently spooky constant is coming from, it is a bitstring of the coefficients of the lexically first irreducible polynomial of degree b with the minimum possible number of non-zero terms, where b is the block size(in bits) of the underlying block cipher with which CMAC is instantiated. So, nothing up the sleeve here.
Investigating further, general-case CMAC involves generating a K1 and a K2, which afaict just need to be arbitrarily different from each other. So why not something even simpler, like "xor with 1"?
The input here is highly restricted so there's no point to it.
The answer is that we're treating this as a Galois field/finite field of order 2^128 with the reducing polynomial (2^128 + 0b10000111).
Under that framework, the left shift and possible XOR implement multiplication by 2. (An example of general multiplication here: https://en.wikipedia.org/wiki/Finite_field_arithmetic#Progra...)
Standard AES-GCM AEAD will catastrophically fail if you use the same nonce twice[1] for different messages, and the size of the size of the nonce is small enough that one cannot safely use a random nonce in many cases.
This work provides an easy to use way to avoid that. It does so by changing not just the nonce but also the key used per message for the AES-GCM call.
And it uses only "plain" AES which is readily available if you have AES-GCM, and no fancy new constructions which may have unknown weaknesses.
The overhead per message is just two small buffers that needs to be encrypted/decrypted with "plain" AES and a longer 192 bit nonce.
I also don't agree with the specific claim (made or at least implied by NIST) that a single 96-bit deterministic nonce isn't limited to 2³² blocks. The counter block will wrap around regardless of how the nonce was generated, because the GCTR function that is used to compute the ciphertext and authentication tag sets CBᵢ = inc32(CBᵢ₋₁) and CB₁ = ICB = inc₃₂(J₀) with J₀ a function of the nonce and inc₃₂ only incrementing the bottom 32 bits. Modern recommendations do not make this distinction around how the nonce was generated and I see no justification made for it by NIST. Perhaps it was meant to apply to the case where a portion of the nonce was implicit and thus not sent or stored in the clear, but deterministic generation doesn't always mean partially implicit nonces and the implicit part is too small (usually 32 bits) and too easy to obtain (often derived from a hardware identifier) to provide any additional security anyway.
Using any nonce lengths other than 96 bits is not recommended today, regardless of the recommendations in 2007. Shorter lengths are obviously a poor choice, but longer lengths are not always supported by implementations. Moreover, while the published standard supports various lengths (with support for <96 bits marked for removal), the invocation of the GHASH function and its effect on nonce entropy is not well studied AFAIK and all nonces other than 96 bits are fed into GHASH. Thus, one shouldn't use a nonce longer than 96 bits, which means the birthday paradox can become a real problem if the same key is used to encrypt a large number of messages, each with different nonces. A single or relatively small number of CSPRNG-generated nonces for the same key is usually okay; a lot is a problem. This problem is a major reason why AES-GCM-SIV, XChaCha20-Poly1305, and XAES-256-GCM even exist.
NSA raised the issue of "Counter wrapping, or integer overflow, because counter is 32 bits" to which NIST replied that "WITH CURRENT COMPUTING ABILITIES [...] Counter should not overflow". I find that to be a thoroughly inadequate response. Obviously current computing capabilities can overflow a 32-bit counter. That also translates (as NSA also pointed out) to 68GB of data encrypted with the same nonce, which is still "a lot" for some use cases, but easy to exceed for other use cases in the age of terabytes and petabytes.
On the issue of nonce reuse specifically, NIST respond to NSA's concerns with 'Generate a new 96-bit nonce for each message using a cryptographically strong PRNG. Re-key at reasonably regular intervals, where "reasonably regular" is defined by how much data and how many messages are being encrypted'. I think that broadly validates what I said. However, "reasonably regular" is not actionable guidance, and it is not always possible to re-key easily.
https://csrc.nist.gov/csrc/media/projects/crypto-publication...
Nonce collision is a huge concern on large file system deployments. 2^32 seems huge but when you’re writing 100k iops a second on a PB array the chance of collision is almost guaranteed if you’re betting on PRNG randomness.
ciphertext1 XOR ciphertext2 = (cipherstream XOR plaintext1) XOR (cipherstream XOR plaintext2) = plaintext1 XOR plaintext2
In GCM it can also break authentication.
I’ve no experience with golang but it seems like it should drop right in based on the age spec. I might give it a shot if time ever permits. I guess I should call it “cage” as in “compliant actually good encryption”
an oxymoron, perhaps
I get that you want to keep up to date with security, but the entire crypto tree is basically a playground for Filippo Valsorda at this point. Meanwhile stuff that I actually need like CMAC is "won't fix"
Elixir can call Erlang/OTP modules directly, so they also have access to that same module. Erlang/OTP is a hard dependency for Elixir afaik.
Trash other peoples' work as "playground" activity, and demand they work on something else for free.
As far as I can tell, Ed25519 is approved (FIPS 186-5), but X25519 is still not (yet).
This is actually similar to why XChaCha20Poly1305 has 192-bit nonces, and consistency with the other major extended-nonce AEAD is another mild advantage.
Would there be an issue before that due to the fact that the AES block size is only 128 bits?
(it's difficult to give a more detailed answer than that, without more detail on why you think it'd be an issue)
My favorite kind of technology.
But at the same time, it is disappointing that you get locked out of several niceties of NIST KDFs, such as label and context. I get that they are sacrificed to minimize the number of AES calls, but still I would prioritize strong cryptographic separation over just a few saved AES calls, especially for messages longer than a few hundred bytes.
Finally, *random* GCM nonces longer than 96 bits are definitely misunderstood and bring better guarantees than 96 bits nonces [1]. But of course, if you can derive a fresh key for every message, that's definitely to prefer.
[1] https://neilmadden.blog/2024/05/23/galois-counter-mode-and-r...