Everything a key determines: the AES key schedule and the table of
multiples of H. Build it once and encrypt as many messages under it as
the key is good for.
Modulecrypton-2.1.7Haskell2010
Crypto.Cipher.AES.GCM
AES-GCM for callers that send many short messages under one key, which is what a datagram transport does.
The interface in Crypto.Cipher.Types builds a state from the key and
the nonce and then walks it through appending the additional data,
encrypting and finalizing, copying the state at each step. For a stream
that is nothing next to the encryption. For a QUIC packet it is most of
the work: the key schedule and the table of multiples of H depend on the
key alone, and rebuilding them for every nonce costs more than encrypting
1440 bytes.
So here a Context is built from the key once and holds both, and encrypt takes a nonce and a whole message and answers in one call.
ctx <- throwCryptoError <$> pure (newContext key)
let packet = encrypt ctx nonce header plaintext 16This runs on AES-NI and carry-less multiply, or on the ARMv8 cryptographic extension, and makes no branch and no memory access that depends on the key or on the data. Where the processor has neither, AES falls back to a table driven implementation that is not constant time; see the side channels section of the README, and processorOptions for which is in use.
The result is the ciphertext with the tag after it, which is the shape a packet wants. decrypt takes that shape back, compares the tag itself and answers Nothing when it does not match.
This computes the same thing as the general interface; the tests hold it to that on the same vectors.
- 2 types
- 6 values
- Packagecrypton-2.1.7
- Exports8
- LanguageHaskell2010
- LicenceBSD-3-Clause AND MIT AND ISC
- SourceGCM.hs
Take a key of 16, 24 or 32 bytes. Any other length is reported as CryptoError_KeySizeInvalid.
Encrypt one message: the nonce, the additional data that is authenticated but not encrypted, the plaintext, and how many bytes of tag to produce, which GCM allows between 4 and 16. Any other length throws CryptoError_AuthenticationTagSizeInvalid, and so does decryptWithTag.
The answer is the ciphertext followed by the tag.
A nonce must not be used twice with the same Context. Twelve bytes is the size GCM is defined for and the only one that does not cost a further pass. A nonce of no bytes is refused: it would hand out the key GCM authenticates with, so encrypt and decryptWithTag throw CryptoError_IvSizeInvalid for it, decrypt gives Nothing and encryptWithMask gives False.
Decrypt one message, in the shape encrypt produced: the ciphertext with its tag after it. The tag is compared here, every byte of it whatever the answer, and a message whose tag does not match gives Nothing rather than the plaintext.
Nothing also comes back when the nonce has no bytes, the input is shorter than the tag, or the tag length is outside 4 to 16.
Decrypt one message, the tag kept apart, and hand back the tag this end computed.
For a caller whose protocol hands it the tag separately from the ciphertext, so that decrypt -- which wants the two together and compares them itself -- does not fit. Compare the two tags with ==; the Eq instance of AuthTag is a constant-time comparison, and taking them apart to compare the bytes is how this goes wrong.
Nothing here says whether the message is authentic. Until the comparison is made and has come out equal, what this returns is not plaintext, it is what the ciphertext turns into, and a caller must not act on it.
Header protection
3 declarationsThe key schedule for header protection, which QUIC keeps separately from the one it encrypts with. Built once, like a Context.
Take a header protection key of 16, 24 or 32 bytes.
encryptWithMask :: (ByteArrayAccess nonce, ByteArrayAccess aad, ByteArrayAccess ba)=> Context-> HeaderKey-> nonce-> aad-> ba-> Inttag length
-> Intsample offset
-> Ptr Word8where the sealed message goes
-> Ptr Word8where the sixteen bytes of mask go
-> IO Bool
Encrypt one message and, from a sample of the ciphertext it just produced, make the header protection mask -- in one call, into two buffers the caller already has.
QUIC takes its sample from the ciphertext, so the mask cannot be had before the encryption. It can be had before coming back, and with the buffers already there nothing is allocated for either. On an Apple M4 the mask then costs about 0.02 us, where asking for it separately costs 0.11.
The sealed message wants length input + taglen bytes and the mask
sixteen. sampleOffset says where the sixteen bytes of sample begin in
the sealed message, counting the tag as part of it.
False comes back, and nothing is written, when the nonce has no bytes, the sample would not fit, or the tag length is outside 4 to 16.