RFC 8452

AES-GCM-SIV: Nonce Misuse-Resistant Authenticated Encryption

๐Ÿ“… published April 2019, from ietf

(Part 1 of 9 ๐ŸŽ€, more coming, bestie!)

AES-GCM-SIV: Nonce Misuse-Resistant Authenticated Encryption ๐ŸŽ€

okay so the title block first ๐Ÿ’…

  • Stream: Internet Research Task Force (IRTF) ๐ŸŒธ
  • RFC number: 8452
  • Category: Informational ๐Ÿ“š
  • ISSN: 2070-1721
  • Authors: S. Gueron (University of Haifa and Amazon), A. Langley (Google LLC), and Y. Lindell (Bar-Ilan University and Unbound Tech) ๐Ÿซถ
  • Date: April 2019 ๐Ÿฆ‹
  • Obsoletes / Updates: the title block doesn't list anything this one obsoletes or updates โœจ

Abstract

ok so this memo specs two authenticated encryption algorithms that are nonce misuse resistant, which means they don't fail catastrophically if a nonce gets repeated ๐Ÿ™ˆ this doc came out of the Crypto Forum Research Group ๐Ÿ’œ

Status of This Memo

this one is not an Internet Standards Track spec, it's published for informational purposes ๐Ÿ“š

it's an IRTF product, and the IRTF publishes results of Internet-related research and development activities. those results might not be suitable for deployment, so be careful ๐Ÿ‘€ this RFC represents the consensus of the Crypto Forum Research Group of the IRTF. documents approved for publication by the IRSG are not candidates for any level of Internet Standard, see Section 2 of RFC 7841 ๐Ÿ’…

if you want the current status, any errata, or a way to send feedback, check https://www.rfc-editor.org/info/rfc8452 ๐Ÿฆ‹

Copyright (c) 2019 IETF Trust and the persons identified as the document authors. All rights reserved. ๐ŸŽ€

this doc is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication. please read those carefully, bestie, because they describe your rights and restrictions for this document ๐Ÿซถ

Table of Contents ๐Ÿ“œ

1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   2
2.  Requirements Language . . . . . . . . . . . . . . . . . . . .   3
3.  POLYVAL . . . . . . . . . . . . . . . . . . . . . . . . . . .   3
4.  Encryption  . . . . . . . . . . . . . . . . . . . . . . . . .   4
5.  Decryption  . . . . . . . . . . . . . . . . . . . . . . . . .   7
6.  AEADs . . . . . . . . . . . . . . . . . . . . . . . . . . . .  10
7.  Field Operation Examples  . . . . . . . . . . . . . . . . . .  10
8.  Worked Example  . . . . . . . . . . . . . . . . . . . . . . .  10
9.  Security Considerations . . . . . . . . . . . . . . . . . . .  11
10. IANA Considerations . . . . . . . . . . . . . . . . . . . . .  14
11. References  . . . . . . . . . . . . . . . . . . . . . . . . .  14
  11.1.  Normative References . . . . . . . . . . . . . . . . . .  14
  11.2.  Informative References . . . . . . . . . . . . . . . . .  15
Appendix A.  The Relationship between POLYVAL and GHASH . . . . .  17
Appendix B.  Additional Comparisons with AES-GCM  . . . . . . . .  19
Appendix C.  Test Vectors . . . . . . . . . . . . . . . . . . . .  20
  C.1.  AEAD_AES_128_GCM_SIV  . . . . . . . . . . . . . . . . . .  20
  C.2.  AEAD_AES_256_GCM_SIV  . . . . . . . . . . . . . . . . . .  30
  C.3.  Counter Wrap Tests  . . . . . . . . . . . . . . . . . . .  41
Acknowledgements  . . . . . . . . . . . . . . . . . . . . . . . .  42
Authors' Addresses  . . . . . . . . . . . . . . . . . . . . . . .  42

1. Introduction

okay bestie, so Authenticated Encryption with Additional Data (AEAD) [RFC5116] is the thing that couples confidentiality and integrity into one single operation ๐Ÿ” that's a big deal because it skips the risky old habit of building your own thing out of block-cipher and hash primitives. the most popular AEAD is AES-GCM [GCM], and it's everywhere because it's so fast โšก

but here's the tea โ˜• some AEADs (AES-GCM included) fail catastrophically on confidentiality and/or integrity when two distinct messages get encrypted with the same key and nonce. the AEAD requirements say the (key, nonce) pair shall only ever be used once and thus prohibit reuse, but honestly that's still a real worry in practice ๐Ÿ˜ญ

nonce misuse-resistant AEADs don't have this problem ๐Ÿ’– with this class, encrypting two messages under the same nonce only reveals whether the messages were equal or not. that's the minimum info a deterministic algorithm can leak in that situation, so it's giving very principled energy ๐Ÿงš

this memo specs two nonce misuse-resistant AEADs: AEAD_AES_128_GCM_SIV and AEAD_AES_256_GCM_SIV ๐Ÿ‘‘ they're built to take advantage of existing hardware support for AES-GCM, and they can decrypt within 5% of AES-GCM speed (for multikilobyte messages) ๐Ÿ”ฅ encryption is, perforce, slower than AES-GCM, because two passes are needed to get that nonce misuse-resistance. still, measurements suggest it can run at two-thirds of AES-GCM's speed, which is honestly not bad at all โœจ

we suggest these AEADs be considered in any situation where nonce uniqueness can't be guaranteed. that includes situations with no stateful counter, or where that state can't be guaranteed, like when multiple encryptors share the same key ๐ŸŒˆ as Section 9 explains, it is RECOMMENDED to use this scheme with randomly chosen nonces ๐ŸŽ€

this document represents the consensus of the Crypto Forum Research Group (CFRG) ๐Ÿ’œ

2. Requirements Language

the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this doc are interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, just like shown here ๐Ÿ’… so lowercase versions are just regular words, no cap ๐Ÿ™ˆ

3. POLYVAL

the GCM-SIV construction is basically GCM's cousin ๐Ÿฆ‹ the block cipher runs in counter mode to encrypt the plaintext, and a polynomial authenticator provides integrity. that authenticator is called POLYVAL ๐Ÿ’–

POLYVAL, like GHASH (the authenticator in AES-GCM; see [GCM], Section 6.4), works in a binary field of size 2^128 ๐Ÿงš the field is defined by the irreducible polynomial:

x^128 + x^127 + x^126 + x^121 + 1

the sum of any two field elements is the result of XORing them โœจ the product of any two elements is calculated with standard (binary) polynomial multiplication, followed by reduction modulo that irreducible polynomial ๐ŸŽ€

we define another binary operation on field elements, dot(a, b), where:

dot(a, b) = a * b * x^-128

the value of the field element x^-128 is equal to:

x^127 + x^124 + x^121 + x^114 + 1

the result of this multiplication, dot(a, b), is another field element ๐ŸŒธ

polynomials in this field get converted to and from 128-bit strings by making the least significant bit of the first byte the coefficient of x^0, the most significant bit of the first byte the coefficient of x^7, and so on, until the most significant bit of the last byte is the coefficient of x^127 ๐Ÿ’œ

POLYVAL takes a field element H and a series of field elements X_1, ..., X_s. its result is S_s, where S is defined by this iteration:

S_0 = 0
S_j = dot(S_{j-1} + X_j, H), for j = 1..s

we note that POLYVAL(H, X_1, X_2, ...) is equal to:

ByteReverse(GHASH(ByteReverse(H) * x, ByteReverse(X_1),
ByteReverse(X_2), ...))

where ByteReverse is a function that reverses the order of 16 bytes ๐Ÿ”ฅ Appendix A goes into a more detailed explanation, so stay tuned ๐Ÿ‘€

4. Encryption โœจ

okay so bestie, this is where the actual magic happens ๐Ÿ’… AES-GCM-SIV encryption takes a 16- or 32-byte key-generating key, a 96-bit nonce, and plaintext and additional data byte strings of variable length. It spits out an authenticated ciphertext that is 16 bytes longer than the plaintext. Both encryption and decryption are only defined on inputs that are a whole number of bytes ๐ŸŽ€

If the key-generating key is 16 bytes long, then AES-128 is used throughout. Otherwise, AES-256 is used throughout ๐Ÿฆ‹

The first step of encryption is to generate per-nonce keys: a message-authentication key and a message-encryption key ๐Ÿ”‘ The message-authentication key is 128 bit, and the message-encryption key is either 128 (for AES-128) or 256 bit (for AES-256).

These keys are generated by encrypting a series of plaintext blocks. Each block holds a 32-bit, little-endian counter followed by the nonce, and then you throw away the second half of the resulting ciphertext ๐Ÿงš For AES-128, that's 128 + 128 = 256 bits of key material, and since each encrypted block gives you 64 bits after discarding half, four blocks need to be encrypted. The counter values for these blocks are 0, 1, 2, and 3. For AES-256, six blocks are needed in total, with counter values 0 through 5 (inclusive) ๐Ÿ’–

In pseudocode form, where "++" indicates concatenation and "x[:8]" indicates taking only the first eight bytes from x:

func derive_keys(key_generating_key, nonce) {
  message_authentication_key =
      AES(key = key_generating_key,
          block = little_endian_uint32(0) ++ nonce)[:8] ++
      AES(key = key_generating_key,
          block = little_endian_uint32(1) ++ nonce)[:8]
  message_encryption_key =
      AES(key = key_generating_key,
          block = little_endian_uint32(2) ++ nonce)[:8] ++
      AES(key = key_generating_key,
          block = little_endian_uint32(3) ++ nonce)[:8]

  if bytelen(key_generating_key) == 32 {
    message_encryption_key ++=
        AES(key = key_generating_key,
            block = little_endian_uint32(4) ++ nonce)[:8] ++
        AES(key = key_generating_key,
            block = little_endian_uint32(5) ++ nonce)[:8]
  }

  return message_authentication_key, message_encryption_key
}

Define the "length block" as a 16-byte value that is the concatenation of the 64-bit, little-endian encodings of bytelen(additional_data) * 8 and bytelen(plaintext) * 8 ๐ŸŒธ Pad the plaintext and additional data with zeros until they are each a multiple of 16 bytes, the AES block size. Then X_1, X_2, ... (the series of field elements that are inputs to POLYVAL) are the concatenation of the padded additional data, the padded plaintext, and the length block โœจ

Calculate S_s = POLYVAL(message-authentication-key, X_1, X_2, ...). XOR the first twelve bytes of S_s with the nonce and clear the most significant bit of the last byte. Encrypt the result with AES using the message-encryption key to produce the tag ๐Ÿซถ

(It's worth highlighting a contrast with AES-GCM here: AES-GCM authenticates the encoded additional data and ciphertext, while AES-GCM-SIV authenticates the encoded additional data and plaintext. Main character energy, honestly ๐Ÿ‘‘)

The encrypted plaintext is produced by using AES, with the message-encryption key, in counter mode (see [SP800-38A], Section 6.5) on the unpadded plaintext. The initial counter block is the tag with the most significant bit of the last byte set to one. The counter advances by incrementing the first 32 bits interpreted as an unsigned, little-endian integer, wrapping at 2^32. The result of the encryption is the encrypted plaintext (truncated to the length of the plaintext), followed by the tag ๐ŸŒˆ

In pseudocode form, the encryption process can be expressed as:

func right_pad_to_multiple_of_16_bytes(input) {
  while (bytelen(input) % 16 != 0) {
    input = input ++ "\x00"
  }
  return input
}

func AES_CTR(key, initial_counter_block, in) {
  block = initial_counter_block

  output = ""
  while bytelen(in) > 0 {
    keystream_block = AES(key = key, block = block)
    block[0:4] = little_endian_uint32(
        read_little_endian_uint32(block[0:4]) + 1)

    todo = min(bytelen(in), bytelen(keystream_block)
    for j = 0; j < todo; j++ {
      output = output ++ (keystream_block[j] ^ in[j])
    }

    in = in[todo:]
  }

  return output
}

func encrypt(key_generating_key,
             nonce,
             plaintext,
             additional_data) {
  if bytelen(plaintext) > 2^36 {
    fail()
  }
  if bytelen(additional_data) > 2^36 {
    fail()
  }

  message_encryption_key, message_authentication_key =
      derive_keys(key_generating_key, nonce)

  length_block =
      little_endian_uint64(bytelen(additional_data) * 8) ++
      little_endian_uint64(bytelen(plaintext) * 8)
  padded_plaintext = right_pad_to_multiple_of_16_bytes(plaintext)
  padded_ad = right_pad_to_multiple_of_16_bytes(additional_data)
  S_s = POLYVAL(key = message_authentication_key,
                input = padded_ad ++ padded_plaintext ++
                        length_block)
  for i = 0; i < 12; i++ {
    S_s[i] ^= nonce[i]
  }
  S_s[15] &= 0x7f
  tag = AES(key = message_encryption_key, block = S_s)

  counter_block = tag
  counter_block[15] |= 0x80
  return AES_CTR(key = message_encryption_key,
                 initial_counter_block = counter_block,
                 in = plaintext) ++
         tag
}

5. Decryption ๐Ÿ”“

Decryption takes a 16- or 32-byte key-generating key, a 96-bit nonce, and ciphertext and additional data byte strings of variable length. It either fails or outputs a plaintext that is 16 bytes shorter than the ciphertext ๐Ÿ’œ

To decrypt an AES-GCM-SIV ciphertext, first derive the message-encryption and message-authentication keys in the same manner as when encrypting ๐ŸŽ€

If the ciphertext is less than 16 bytes or more than 2^36 + 16 bytes, then fail. Otherwise, split the input into the encrypted plaintext and a 16-byte tag. Decrypt the encrypted plaintext with the message-encryption key in counter mode, where the initial counter block is the tag with the most significant bit of the last byte set to one. Advance the counter for each block in the same way as when encrypting. At this point, the plaintext is unauthenticated and MUST NOT be output until the following tag confirmation is complete ๐Ÿ™ˆ

Pad the additional data and plaintext with zeros until they are each a multiple of 16 bytes, the AES block size. Calculate the length block and X_1, X_2, ... as above and compute S_s = POLYVAL(message-authentication-key, X_1, X_2, ...). Compute the expected tag by XORing S_s and the nonce, clearing the most significant bit of the last byte and encrypting with the message-encryption key. Compare the provided and expected tag values in constant time. Fail the decryption if they do not match (and do not release the plaintext); otherwise, return the plaintext ๐Ÿ“

In pseudocode form, the decryption process can be expressed as:

func decrypt(key_generating_key,
             nonce,
             ciphertext,
             additional_data) {
  if bytelen(ciphertext) < 16 || bytelen(ciphertext) > 2^36 + 16 {
    fail()
  }
  if bytelen(additional_data) > 2^36 {
    fail()
  }

  message_encryption_key, message_authentication_key =
      derive_keys(key_generating_key, nonce)

  tag = ciphertext[bytelen(ciphertext)-16:]

  counter_block = tag
  counter_block[15] |= 0x80
  plaintext = AES_CTR(key = message_encryption_key,
                      initial_counter_block = counter_block,
                      in = ciphertext[:bytelen(ciphertext)-16])

  length_block =
      little_endian_uint64(bytelen(additional_data) * 8) ++
      little_endian_uint64(bytelen(plaintext) * 8)
  padded_plaintext = right_pad_to_multiple_of_16_bytes(plaintext)
  padded_ad = right_pad_to_multiple_of_16_bytes(additional_data)
  S_s = POLYVAL(key = message_authentication_key,
                input = padded_ad ++ padded_plaintext ++
                        length_block)
  for i = 0; i < 12; i++ {
    S_s[i] ^= nonce[i]
  }
  S_s[15] &= 0x7f
  expected_tag = AES(key = message_encryption_key, block = S_s)

  xor_sum = 0
  for i := 0; i < bytelen(expected_tag); i++ {
    xor_sum |= expected_tag[i] ^ tag[i]
  }

  if xor_sum != 0 {
    fail()
  }

  return plaintext
}

6. AEADs ๐Ÿฆ‹

We define two AEADs, in the format of RFC 5116, that use AES-GCM-SIV: AEAD_AES_128_GCM_SIV and AEAD_AES_256_GCM_SIV. They differ only in the size of the AES key used ๐Ÿ’…

The key input to these AEADs becomes the key-generating key. Thus, AEAD_AES_128_GCM_SIV takes a 16-byte key and AEAD_AES_256_GCM_SIV takes a 32-byte key ๐ŸŒธ

The parameters for AEAD_AES_128_GCM_SIV are then as follows: K_LEN is 16, P_MAX is 2^36, A_MAX is 2^36, N_MIN and N_MAX are 12, and C_MAX is 2^36 + 16 โœจ

The parameters for AEAD_AES_256_GCM_SIV differ only in the key size: K_LEN is 32, P_MAX is 2^36, A_MAX is 2^36, N_MIN and N_MAX are 12, and C_MAX is 2^36 + 16 ๐Ÿซถ


7. Field Operation Examples ๐Ÿงš

Polynomials in this document will be written as 16-byte values. For example, the sixteen bytes 01000000000000000000000000000492 would represent the polynomial x^127 + x^124 + x^121 + x^114 + 1, which is also the value of x^-128 in this field ๐ŸŒˆ

   If a = 66e94bd4ef8a2c3b884cfa59ca342b2e and
      b = ff000000000000000000000000000000,
   then a + b = 99e94bd4ef8a2c3b884cfa59ca342b2e,
        a * b = 37856175e9dc9df26ebc6d6171aa0ae9, and
        dot(a, b) = ebe563401e7e91ea3ad6426b8140c394.

8. Worked Example โœจ

okay so this section is literally a full walkthrough of encrypting the plaintext "Hello world" with the additional data "example" under key ee8e1ed9ff2540ae8f2ba9f50bc2f27c using AEAD_AES_128_GCM_SIV ๐ŸŽ€ the random nonce we're using is 752abad3e0afb5f434dc4310. bestie, let's go step by step, it's so satisfying ๐Ÿฆ‹

To generate the message-authentication and message-encryption keys, a counter gets combined with the nonce to form four blocks ๐ŸŒธ those blocks get encrypted with the key above:

Counter |       Nonce                         Ciphertext
00000000752abad3e0afb5f434dc4310 -> 310728d9911f1f38c40e952ca83d093e
01000000752abad3e0afb5f434dc4310 -> 37b24316c3fab9a046ae90952daa0450
02000000752abad3e0afb5f434dc4310 -> a4c5ae624996327947920b2d2412474b
03000000752abad3e0afb5f434dc4310 -> c100be4d7e2c6edd1efef004305ab1e7

Then the latter halves of the ciphertext blocks get tossed ๐Ÿ—‘๏ธ and the leftover bytes get stitched together into the per-message keys. So the message-authentication key is 310728d9911f1f3837b24316c3fab9a0 and the message-encryption key is a4c5ae6249963279c100be4d7e2c6edd ๐Ÿ’œ

The length block holds the bit lengths of the additional data and the plaintext, in that order ๐Ÿ“ "example" is seven characters, so that's 56 bits (aka 0x38 in hex). "Hello world" is 11 characters, which is 88 = 0x58 bits. So the length block is 38000000000000005800000000000000 โœจ

The input to POLYVAL is the padded additional data, then the padded plaintext, then the length block. Here it is, based on the ASCII encoding of "example" (6578616d706c65) and "Hello world" (48656c6c6f20776f726c64):

6578616d706c650000000000000000004 8656c6c6f20776f726c64000000000038000000000000005800000000000000

Wait, I have to be honest, the original text breaks that hex across two lines, so I'm copying it exactly as the source has it:

6578616d706c6500000000000000000048656c6c6f20776f726c64000000000038000000000000005800000000000000

Calling POLYVAL with the message-authentication key and that input gives S_s = ad7fcf0b5169851662672f3c5f95138f ๐Ÿ”ฅ

Before encrypting, the nonce gets XORed in and the most significant bit of the last byte gets cleared. That gives d85575d8b1c630e256bb6c2c5f95130f, because that bit happened to be one already ๐Ÿ™ˆ Encrypting that with the message-encryption key (using AES-128) gives the tag, which is 4fbcdeb7e4793f4a1d7e4faa70100af1 ๐Ÿ‘‘

To form the initial counter block, the most significant bit of the last byte of the tag is set to one. That doesn't change anything in this example ๐Ÿ’… Encrypting this with the message key (using AES-128) gives the first block of the keystream: 1551f2c1787e81deac9a99f139540ab5 ๐Ÿงš

The final ciphertext is the result of XORing the plaintext with the keystream and appending the tag. That gives:

5d349ead175ef6b1def6fd4fbcdeb7e4793f4a1d7e4faa70100af1

Literally so iconic ๐Ÿ˜ญ๐Ÿซถ


9. Security Considerations ๐Ÿ”

bestie, this section is SERIOUS, so read it carefully ๐Ÿ™

AES-GCM-SIV decryption starts by producing an unauthenticated plaintext. This plaintext can be messed with by an attacker, so if an implementation releases some or all of the plaintext before authenticating it, other parts of a system might process malicious data as if it were legit ๐Ÿ’€ AES-GCM might be less likely to make implementations do this, because there the ciphertext is usually authenticated before, or at the same time as, the plaintext calculation. So this text says that implementations MUST NOT release unauthenticated plaintext. Full stop ๐Ÿ›‘ System designers should also think about memory limits when picking the size of AES-GCM-SIV plaintexts, since large plaintexts may not fit in the available memory of some machines, and that can tempt implementations to release unverified plaintext ๐Ÿ˜ฌ

A detailed cryptographic analysis of AES-GCM-SIV appears in [AES-GCM-SIV], and the rest of this section is a summary of that paper ๐Ÿ“š

The AEADs defined in this document calculate fresh AES keys for each nonce ๐Ÿ”‘ This lets a larger number of plaintexts be encrypted under a given key โœจ Without this step, AES-GCM-SIV encryption would be limited by the birthday bound, like other standard modes (e.g., AES-GCM, AES-CCM [RFC3610], and AES-SIV [RFC5297]). That means when 2^64 blocks have been encrypted overall, a distinguishing adversary trying to break the confidentiality of the scheme has an advantage of 1/2. So, to limit the adversary's advantage to 2^-32, at most 2^48 blocks can be encrypted overall. In contrast, by deriving fresh keys from each nonce, it's possible to encrypt a far larger number of messages and blocks with AES-GCM-SIV ๐Ÿฆ‹

We stress that nonce misuse-resistant schemes guarantee that if a nonce repeats, the only security loss is that identical plaintexts produce identical ciphertexts. Since that can still be a concern (as the fact that the same plaintext was encrypted twice is revealed), we do not recommend using a fixed nonce as a policy ๐Ÿ™…โ€โ™€๏ธ Also, as we show below, better-than-birthday bounds are achieved by AES-GCM-SIV when the nonce repetition rate is low. Finally, as shown in [BHT18], there's a great security benefit in the multiuser/multikey setting when each particular nonce is reused by only a small number of users. We stress that the nonce misuse-resistance property is NOT intended to be paired with intentional nonce reuse; rather, these schemes provide the best possible security in the event of nonce reuse. Due to all of the above, it is RECOMMENDED that AES-GCM-SIV nonces be randomly generated ๐ŸŽฒ

Some example usage bounds for AES-GCM-SIV are below ๐Ÿ“Š The adversary's advantage is the "AdvEnc" from [key-derive], and it's colloquially the ability of an attacker to tell ciphertexts apart from random bit strings. The bounds below limit this advantage to 2^-32. For up to 256 uses of the same nonce and key (i.e., where you can assume nonce misuse is no more than this bound), the following message limits should be respected (this assumes a short additional authenticated data (AAD), i.e., less than 64 bytes):

   2^29 messages, where each plaintext is at most 1 GiB

   2^35 messages, where each plaintext is at most 128 MiB
   2^49 messages, where each plaintext is at most 1 MiB

   2^61 messages, where each plaintext is at most 16 KiB

Suzuki et al. [multi-birthday] show that even if nonces are selected uniformly at random, the probability that one or more values would be repeated 256 or more times is negligible until the number of nonces reaches 2^102. (Specifically, the probability is 1/((2^96)^(255)) * Binomial(q, 256), where q is the number of nonces.) Since 2^102 is vastly greater than the limit on the number of plaintexts per key given above, we don't feel this limit on the number of repeated nonces will be a problem ๐Ÿ™Œ This also means that selecting nonces at random is a safe practice with AES-GCM-SIV. The bounds obtained for random nonces are as follows (as above, for these bounds, the adversary's advantage is at most 2^-32):

   2^32 messages, where each plaintext is at most 8 GiB

   2^48 messages, where each plaintext is at most 32 MiB

   2^64 messages, where each plaintext is at most 128 KiB

For situations where, for some reason, an even higher number of nonce repeats is possible (e.g., in devices with very poor randomness), the message limits need to be reconsidered ๐Ÿค” Theorem 7 in [AES-GCM-SIV] has more details, but for up to 1,024 repeats of each nonce, the limits would be (again assuming a short AAD, i.e., less than 64 bytes):

   2^25 messages, where each plaintext is at most 1 GiB

   2^31 messages, where each plaintext is at most 128 MiB

   2^45 messages, where each plaintext is at most 1 MiB

   2^57 messages, where each plaintext is at most 16 KiB

In addition to calculating fresh AES keys for each nonce, these AEADs also calculate fresh POLYVAL keys. Previous versions of GCM-SIV didn't do this and instead used part of the AEAD's key as the POLYVAL key. Bleichenbacher pointed out [Bleichenbacher16] that this allowed an attacker who controlled the AEAD key to force the POLYVAL key to be zero. If a user of this AEAD authenticated messages with a secret additional-data value, that would be insecure, because the attacker could calculate a valid authenticator without knowing the input ๐Ÿ˜ฑ This doesn't violate the standard properties of an AEAD, since the additional data isn't assumed to be confidential. However, we want these AEADs to be robust against plausible misuse and also to be drop-in replacements for AES-GCM, so they derive nonce-specific POLYVAL keys to avoid this issue ๐Ÿ’–

We also want to note that the probability of successful forgery increases with the number of attempts an attacker is permitted ๐Ÿ•ต๏ธโ€โ™€๏ธ The advantage defined in [key-derive] and used above is specified in terms of the ability of an attacker to distinguish ciphertexts from random bit strings. So it covers both confidentiality and integrity, and Theorem 6.2 in [key-derive] shows the advantage increases with the number of decryption attempts, although much more slowly than with the number of encryptions. The dependence on the number of decryption queries for forgery is actually only linear, not quadratic. The latter is an artifact of the bound in the paper not being tight. If an attacker is permitted extremely large numbers of attempts, then the tiny probability that any given attempt succeeds may add up to a non-trivial chance ๐ŸŽฏ

A security analysis of a similar scheme without nonce-based key derivation appears in [GCM-SIV], and a full analysis of the bounds when applying nonce-based key derivation appears in [key-derive]. A larger table of bounds and other information appears at [aes-gcm-siv-homepage] ๐ŸŒ

The multiuser/multikey security of AES-GCM-SIV was studied by [BHT18], which showed that security is almost the same as in the single-user setting, as long as nonces don't repeat many times across many users. This is the case when nonces are chosen randomly ๐ŸŽ€


10. IANA Considerations ๐Ÿ“

IANA has added two entries to the "AEAD Algorithms" registry: AEAD_AES_128_GCM_SIV (Numeric ID 30) and AEAD_AES_256_GCM_SIV (Numeric ID 31), both referencing this document as their specification ๐ŸŒˆ


11. References ๐Ÿ“š

11.1. Normative References ๐Ÿ’œ

[RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
           Requirement Levels", BCP 14, RFC 2119,
           DOI 10.17487/RFC2119, March 1997,
           <https://www.rfc-editor.org/info/rfc2119>.

[RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
           2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
           May 2017, <https://www.rfc-editor.org/info/rfc8174>.

[SP800-38A]
           Dworkin, M., "Recommendation for Block Cipher Modes of
           Operation: Methods and Techniques", NIST SP 800-38A,
           DOI 10.6028/NIST.SP.800-38A, December 2001,
           <https://csrc.nist.gov/publications/detail/sp/800-38a/
           final>.

11.2. Informative References โœจ

okay bestie, here are the extra reads that back up the spec ๐Ÿ“š they aren't required to implement anything, but they're the receipts for the crypto and the history ๐Ÿ’…

[AES-GCM-SIV] Gueron, S., Langley, A., and Y. Lindell, "AES-GCM-SIV: Specification and Analysis", July 2017, https://eprint.iacr.org/2017/168.

[aes-gcm-siv-homepage] Gueron, S., Langley, A., and Y. Lindell, "Webpage for the AES-GCM-SIV Mode of Operation", https://cyber.biu.ac.il/aes-gcm-siv/.

[BHT18] Bose, P., Hoang, V., and S. Tessaro, "Revisiting AES-GCM-SIV: Multi-user Security, Faster Key Derivation, and Better Bounds", Advances in Cryptology - EUROCRYPT 2018, DOI 10.1007/978-3-319-78381-9_18, May 2018, https://eprint.iacr.org/2018/136.pdf.

[Bleichenbacher16] Bleichenbacher, D., "Subject: AES-GCM-SIV security of the additional data", message to the cfrg mailing list, 24 June 2016, https://mailarchive.ietf.org/arch/msg/cfrg/qgh-Yxmj7CC7cq2YZLpmfGA3x-o.

[GCM] Dworkin, M., "Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM) and GMAC", NIST SP 800-38D, DOI 10.6028/NIST.SP.800-38D, November 2007, https://csrc.nist.gov/publications/detail/sp/800-38d/final.

[GCM-SIV] Gueron, S. and Y. Lindell, "GCM-SIV: Full Nonce Misuse-Resistant Authenticated Encryption at Under One Cycle Per Byte", Proceedings of the 22nd ACM SIGSAC Conference on Computer and Communications Security, DOI 10.1145/2810103.2813613, October 2015, http://doi.acm.org/10.1145/2810103.2813613.

[key-derive] Gueron, S. and Y. Lindell, "Better Bounds for Block Cipher Modes of Operation via Nonce-Based Key Derivation", Proceedings of the 2017 ACM SIGSAC Conference on Computer and Communications Security, DOI 10.1145/3133956.3133992, 2017, https://doi.org/10.1145/3133956.3133992.

[multi-birthday] Suzuki, K., Tonien, D., Kurosawa, K., and K. Toyota, "Birthday Paradox for Multi-collisions", Information Security and Cryptology - ICISC 2006, Lecture Notes in Computer Science, Volume 4296, DOI 10.1007/11927587_5, 2006, http://dx.doi.org/10.1007/11927587_5.

[RFC3610] Whiting, D., Housley, R., and N. Ferguson, "Counter with CBC-MAC (CCM)", RFC 3610, DOI 10.17487/RFC3610, September 2003, https://www.rfc-editor.org/info/rfc3610.

[RFC5116] McGrew, D., "An Interface and Algorithms for Authenticated Encryption", RFC 5116, DOI 10.17487/RFC5116, January 2008, https://www.rfc-editor.org/info/rfc5116.

[RFC5297] Harkins, D., "Synthetic Initialization Vector (SIV) Authenticated Encryption Using the Advanced Encryption Standard (AES)", RFC 5297, DOI 10.17487/RFC5297, October 2008, https://www.rfc-editor.org/info/rfc5297.


Appendix A. The Relationship between POLYVAL and GHASH ๐Ÿ”ฎ

okay so GHASH and POLYVAL are cousins ๐Ÿฆ‹ they both live in GF(2^128), just with different irreducible polynomials. POLYVAL works modulo x^128 + x^127 + x^126 + x^121 + 1 and GHASH works modulo x^128 + x^7 + x^2 + x + 1. ngl the wild part is that these two polynomials are the "reverse" of each other ๐Ÿคฏ

GHASH also maps 128-bit strings to field elements differently. POLYVAL takes the least significant to most significant bits of the first byte as the coefficients of x^0 to x^7, but GHASH flips it and takes them as the coefficients of x^7 to x^0. This keeps going until the last byte, where POLYVAL takes the least significant to most significant bits as the coefficients of x^120 to x^127, while GHASH takes them as the coefficients of x^127 to x^120.

so the combo of these facts means you can "convert" values between the two just by reversing the byte order of a 16-byte string ๐Ÿช„ the different bit-order interpretations already handle reversing the bits inside each byte, and then reversing the bytes does the rest. this could be a real perk for implementations that want to do both GHASH and POLYVAL ๐Ÿ’œ

To keep it clear which field an operation happens in, let mulX_GHASH be a function that takes a 16-byte string, converts it to an element of GHASH's field using GHASH's convention, multiplies it by x, and converts it back to a string. Likewise, mulX_POLYVAL converts a 16-byte string to an element of POLYVAL's field using POLYVAL's convention, multiplies it by x, and converts it back.

Given the 16-byte string 01000000000000000000000000000000, mulX_GHASH of that string is 00800000000000000000000000000000 and mulX_POLYVAL of that string is 02000000000000000000000000000000. As a more general example, given 9c98c04df9387ded828175a92ba652d8, mulX_GHASH of that string is 4e4c6026fc9c3ef6c140bad495d3296c and mulX_POLYVAL of it is 3931819bf271fada0503eb52574ca5f2.

Lastly, let ByteReverse be the function that takes a 16-byte string and returns a copy with the order of the bytes reversed.

Now GHASH and POLYVAL can be defined in terms of one another ๐Ÿซถ

POLYVAL(H, X_1, ..., X_n) =
ByteReverse(GHASH(mulX_GHASH(ByteReverse(H)), ByteReverse(X_1), ...,
ByteReverse(X_n)))

GHASH(H, X_1, ..., X_n) =
ByteReverse(POLYVAL(mulX_POLYVAL(ByteReverse(H)), ByteReverse(X_1),
..., ByteReverse(X_n)))

As a worked example ๐ŸŒธ

let H = 25629347589242761d31f826ba4b757b,
    X_1 = 4f4f95668c83dfb6401762bb2d01a262, and
    X_2 = d1a24ddd2721d006bbe45f20d3c9f362.
POLYVAL(H, X_1, X_2) = f7a3b47b846119fae5b7866cf5e5b77e.

If we wanted to calculate this with only a GHASH implementation, the key for GHASH would be

mulX_GHASH(ByteReverse(H)) = dcbaa5dd137c188ebb21492c23c9b112.

Then

ByteReverse(GHASH(dcba..., ByteReverse(X_1), ByteReverse(X_2)))
     = f7a3b47b846119fae5b7866cf5e5b77e, as required.

going the other direction ๐Ÿ” GHASH(H, X_1, X_2) = bd9b3997046731fb96251b91f9c99d7a. If we wanted to calculate this with only a POLYVAL implementation, we'd first calculate the key for POLYVAL:

mulX_POLYVAL(ByteReverse(H)) = f6ea96744df0633aec8424b18e26c54a.

Then

ByteReverse(POLYVAL(f6ea..., ByteReverse(X_1), ByteReverse(X_2)))
     = bd9b3997046731fb96251b91f9c99d7a.

Appendix B. Additional Comparisons with AES-GCM ๐Ÿ’…

okay here are some more functional differences between AES-GCM and AES-GCM-SIV that are worth knowing ๐Ÿ‘€

AES-GCM lets plaintexts be encrypted in a streaming way, meaning the start of the plaintext can be encrypted and sent before the whole message has been processed ๐ŸŒŠ AES-GCM-SIV needs two passes for encryption, so it cannot do this ๐Ÿ˜ญ

AES-GCM lets a constant additional-data input be precomputed to save per-message work โฉ AES-GCM-SIV varies the authenticator key based on the nonce, so it does not allow this ๐Ÿ™ˆ

The performance of AES-GCM versus AES-GCM-SIV on small machines can be roughly described by the number of AES operations and the number of GF(2^128) multiplications needed to process a message ๐Ÿงฎ

Let a = (bytelen(additional-data) + 15) / 16 and
    p = (bytelen(plaintext) + 15) / 16.

So then AES-GCM needs p + 1 AES operations and p + a + 1 field multiplications. Defined the same way, AES-GCM-SIV with AES-128 needs p + 5 AES operations and p + a + 1 field multiplications. With AES-256, that becomes p + 7 AES operations ๐ŸŽ€

On large machines the available parallelism matters way more, and this simple analysis stops being representative ๐Ÿ–ฅ๏ธ For those machines, decryption with AES-GCM-SIV is only about 5% slower than AES-GCM, as long as the message is at least a couple of kilobytes. Encryption tends to run at about two-thirds the speed because of the extra pass it needs ๐Ÿฆ‹


Appendix C. Test Vectors ๐Ÿงช

C.1. AEAD_AES_128_GCM_SIV

here are the test vectors, copied exactly so you can check your implementation against them ๐Ÿ’–

Plaintext (0 bytes) =
AAD (0 bytes) =
Key =                       01000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = d9b360279694941ac5dbc6987ada7377
Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
POLYVAL input =             00000000000000000000000000000000
POLYVAL result =            00000000000000000000000000000000
POLYVAL result XOR nonce =  03000000000000000000000000000000
... and masked =            03000000000000000000000000000000
Tag =                       dc20e2d83f25705bb49e439eca56de25
Initial counter =           dc20e2d83f25705bb49e439eca56dea5
Result (16 bytes) =         dc20e2d83f25705bb49e439eca56de25
Plaintext (8 bytes) =       0100000000000000
AAD (0 bytes) =
Key =                       01000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = d9b360279694941ac5dbc6987ada7377
Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
POLYVAL input =             01000000000000000000000000000000
                            00000000000000004000000000000000
POLYVAL result =            eb93b7740962c5e49d2a90a7dc5cec74
POLYVAL result XOR nonce =  e893b7740962c5e49d2a90a7dc5cec74
... and masked =            e893b7740962c5e49d2a90a7dc5cec74
Tag =                       578782fff6013b815b287c22493a364c
Initial counter =           578782fff6013b815b287c22493a36cc
Result (24 bytes) =         b5d839330ac7b786578782fff6013b81
                            5b287c22493a364c
Plaintext (12 bytes) =      010000000000000000000000
AAD (0 bytes) =
Key =                       01000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = d9b360279694941ac5dbc6987ada7377
Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
POLYVAL input =             01000000000000000000000000000000
                            00000000000000006000000000000000
POLYVAL result =            48eb6c6c5a2dbe4a1dde508fee06361b
POLYVAL result XOR nonce =  4beb6c6c5a2dbe4a1dde508fee06361b
... and masked =            4beb6c6c5a2dbe4a1dde508fee06361b
Tag =                       a4978db357391a0bc4fdec8b0d106639
Initial counter =           a4978db357391a0bc4fdec8b0d1066b9
Result (28 bytes) =         7323ea61d05932260047d942a4978db3
                            57391a0bc4fdec8b0d106639
Plaintext (16 bytes) =      01000000000000000000000000000000
AAD (0 bytes) =
Key =                       01000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = d9b360279694941ac5dbc6987ada7377
Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
POLYVAL input =             01000000000000000000000000000000
                            00000000000000008000000000000000
POLYVAL result =            20806c26e3c1de019e111255708031d6
POLYVAL result XOR nonce =  23806c26e3c1de019e111255708031d6
... and masked =            23806c26e3c1de019e11125570803156
Tag =                       303aaf90f6fe21199c6068577437a0c4
Initial counter =           303aaf90f6fe21199c6068577437a0c4
Result (32 bytes) =         743f7c8077ab25f8624e2e948579cf77
                            303aaf90f6fe21199c6068577437a0c4

Okay bestie, here come the AEAD_AES_128_GCM_SIV test vectors, and they're giving main character energy ๐ŸŽ€โœจ

Vector 1 ๐Ÿฆ‹

   Plaintext (32 bytes) =      01000000000000000000000000000000
                               02000000000000000000000000000000
   AAD (0 bytes) =
   Key =                       01000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = d9b360279694941ac5dbc6987ada7377
   Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               00000000000000000001000000000000
   POLYVAL result =            ce6edc9a50b36d9a98986bbf6a261c3b
   POLYVAL result XOR nonce =  cd6edc9a50b36d9a98986bbf6a261c3b
   ... and masked =            cd6edc9a50b36d9a98986bbf6a261c3b
   Tag =                       1a8e45dcd4578c667cd86847bf6155ff
   Initial counter =           1a8e45dcd4578c667cd86847bf6155ff
   Result (48 bytes) =         84e07e62ba83a6585417245d7ec413a9
                               fe427d6315c09b57ce45f2e3936a9445
                               1a8e45dcd4578c667cd86847bf6155ff

Vector 2 ๐Ÿ’œ

   Plaintext (48 bytes) =      01000000000000000000000000000000
                               02000000000000000000000000000000
                               03000000000000000000000000000000
   AAD (0 bytes) =
   Key =                       01000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = d9b360279694941ac5dbc6987ada7377
   Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               03000000000000000000000000000000
                               00000000000000008001000000000000
   POLYVAL result =            81388746bc22d26b2abc3dcb15754222
   POLYVAL result XOR nonce =  82388746bc22d26b2abc3dcb15754222
   ... and masked =            82388746bc22d26b2abc3dcb15754222
   Tag =                       5e6e311dbf395d35b0fe39c2714388f8
   Initial counter =           5e6e311dbf395d35b0fe39c2714388f8
   Result (64 bytes) =         3fd24ce1f5a67b75bf2351f181a475c7
                               b800a5b4d3dcf70106b1eea82fa1d64d
                               f42bf7226122fa92e17a40eeaac1201b
                               5e6e311dbf395d35b0fe39c2714388f8

Vector 3 ๐ŸŒธ

   Plaintext (64 bytes) =      01000000000000000000000000000000
                               02000000000000000000000000000000
                               03000000000000000000000000000000
                               04000000000000000000000000000000
   AAD (0 bytes) =
   Key =                       01000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = d9b360279694941ac5dbc6987ada7377
   Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               03000000000000000000000000000000
                               04000000000000000000000000000000
                               00000000000000000002000000000000
   POLYVAL result =            1e39b6d3344d348f6044f89935d1cf78
   POLYVAL result XOR nonce =  1d39b6d3344d348f6044f89935d1cf78
   ... and masked =            1d39b6d3344d348f6044f89935d1cf78
   Tag =                       8a263dd317aa88d56bdf3936dba75bb8
   Initial counter =           8a263dd317aa88d56bdf3936dba75bb8
   Result (80 bytes) =         2433668f1058190f6d43e360f4f35cd8
                               e475127cfca7028ea8ab5c20f7ab2af0
                               2516a2bdcbc08d521be37ff28c152bba
                               36697f25b4cd169c6590d1dd39566d3f
                               8a263dd317aa88d56bdf3936dba75bb8

Vector 4 ๐Ÿงš

   Plaintext (8 bytes) =       0200000000000000
   AAD (1 bytes) =             01
   Key =                       01000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = d9b360279694941ac5dbc6987ada7377
   Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               08000000000000004000000000000000
   POLYVAL result =            b26781e7e2c1376f96bec195f3709b2a
   POLYVAL result XOR nonce =  b16781e7e2c1376f96bec195f3709b2a
   ... and masked =            b16781e7e2c1376f96bec195f3709b2a
   Tag =                       3b0a1a2560969cdf790d99759abd1508
   Initial counter =           3b0a1a2560969cdf790d99759abd1588
   Result (24 bytes) =         1e6daba35669f4273b0a1a2560969cdf
                               790d99759abd1508

Vector 5 ๐Ÿ“

   Plaintext (12 bytes) =      020000000000000000000000
   AAD (1 bytes) =             01
   Key =                       01000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = d9b360279694941ac5dbc6987ada7377
   Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               08000000000000006000000000000000
   POLYVAL result =            111f5affb18e4cc1164a01bdc12a4145
   POLYVAL result XOR nonce =  121f5affb18e4cc1164a01bdc12a4145
   ... and masked =            121f5affb18e4cc1164a01bdc12a4145
   Tag =                       08299c5102745aaa3a0c469fad9e075a
   Initial counter =           08299c5102745aaa3a0c469fad9e07da
   Result (28 bytes) =         296c7889fd99f41917f4462008299c51
                               02745aaa3a0c469fad9e075a

Vector 6 ๐Ÿ’…

   Plaintext (16 bytes) =      02000000000000000000000000000000
   AAD (1 bytes) =             01
   Key =                       01000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = d9b360279694941ac5dbc6987ada7377
   Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               08000000000000008000000000000000
   POLYVAL result =            79745ab508622c8a958543675fac4688
   POLYVAL result XOR nonce =  7a745ab508622c8a958543675fac4688
   ... and masked =            7a745ab508622c8a958543675fac4608
   Tag =                       8f8936ec039e4e4bb97ebd8c4457441f
   Initial counter =           8f8936ec039e4e4bb97ebd8c4457449f
   Result (32 bytes) =         e2b0c5da79a901c1745f700525cb335b
                               8f8936ec039e4e4bb97ebd8c4457441f

Vector 7 ๐Ÿ”ฅ

   Plaintext (32 bytes) =      02000000000000000000000000000000
                               03000000000000000000000000000000
   AAD (1 bytes) =             01
   Key =                       01000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = d9b360279694941ac5dbc6987ada7377
   Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               03000000000000000000000000000000
                               08000000000000000001000000000000
   POLYVAL result =            2ce7daaf7c89490822051255b12eca6b
   POLYVAL result XOR nonce =  2fe7daaf7c89490822051255b12eca6b
   ... and masked =            2fe7daaf7c89490822051255b12eca6b
   Tag =                       e6af6a7f87287da059a71684ed3498e1
   Initial counter =           e6af6a7f87287da059a71684ed3498e1
   Result (48 bytes) =         620048ef3c1e73e57e02bb8562c416a3
                               19e73e4caac8e96a1ecb2933145a1d71
                               e6af6a7f87287da059a71684ed3498e1

Vector 8 ๐Ÿ‘‘

   Plaintext (48 bytes) =      02000000000000000000000000000000
                               03000000000000000000000000000000
                               04000000000000000000000000000000
   AAD (1 bytes) =             01
   Key =                       01000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = d9b360279694941ac5dbc6987ada7377
   Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               03000000000000000000000000000000
                               04000000000000000000000000000000
                               08000000000000008001000000000000
   POLYVAL result =            9ca987715d69c1786711dfcd22f830fc
   POLYVAL result XOR nonce =  9fa987715d69c1786711dfcd22f830fc
   ... and masked =            9fa987715d69c1786711dfcd22f8307c
   Tag =                       6a8cc3865f76897c2e4b245cf31c51f2
   Initial counter =           6a8cc3865f76897c2e4b245cf31c51f2
   Result (64 bytes) =         50c8303ea93925d64090d07bd109dfd9
                               515a5a33431019c17d93465999a8b005
                               3201d723120a8562b838cdff25bf9d1e
                               6a8cc3865f76897c2e4b245cf31c51f2

Vector 9 ๐ŸŒˆ

   Plaintext (64 bytes) =      02000000000000000000000000000000
                               03000000000000000000000000000000
                               04000000000000000000000000000000
                               05000000000000000000000000000000
   AAD (1 bytes) =             01
   Key =                       01000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = d9b360279694941ac5dbc6987ada7377
   Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               03000000000000000000000000000000
                               04000000000000000000000000000000
                               05000000000000000000000000000000
                               08000000000000000002000000000000
   POLYVAL result =            ffcd05d5770f34ad9267f0a59994b15a
   POLYVAL result XOR nonce =  fccd05d5770f34ad9267f0a59994b15a
   ... and masked =            fccd05d5770f34ad9267f0a59994b15a
   Tag =                       cdc46ae475563de037001ef84ae21744
   Initial counter =           cdc46ae475563de037001ef84ae217c4
   Result (80 bytes) =         2f5c64059db55ee0fb847ed513003746
                               aca4e61c711b5de2e7a77ffd02da42fe
                               ec601910d3467bb8b36ebbaebce5fba3
                               0d36c95f48a3e7980f0e7ac299332a80
                               cdc46ae475563de037001ef84ae21744

Vector 10 โœจ

   Plaintext (4 bytes) =       02000000
   AAD (12 bytes) =            010000000000000000000000
   Key =                       01000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = d9b360279694941ac5dbc6987ada7377
   Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               60000000000000002000000000000000
   POLYVAL result =            f6ce9d3dcd68a2fd603c7ecc18fb9918
   POLYVAL result XOR nonce =  f5ce9d3dcd68a2fd603c7ecc18fb9918
   ... and masked =            f5ce9d3dcd68a2fd603c7ecc18fb9918
   Tag =                       07eb1f84fb28f8cb73de8e99e2f48a14
   Initial counter =           07eb1f84fb28f8cb73de8e99e2f48a94
   Result (20 bytes) =         a8fe3e8707eb1f84fb28f8cb73de8e99
                               e2f48a14

Okay, bestie, let's keep going through these test vectors. They're the receipts that prove the math checks out. ๐Ÿงฎโœจ

Here's the first batch of AEAD_AES_128_GCM_SIV vectors. ๐ŸŽ€

Plaintext (20 bytes) =      03000000000000000000000000000000
                            04000000
AAD (18 bytes) =            01000000000000000000000000000000
                            0200
Key =                       01000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = d9b360279694941ac5dbc6987ada7377
Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
POLYVAL input =             01000000000000000000000000000000
                            02000000000000000000000000000000
                            03000000000000000000000000000000
                            04000000000000000000000000000000
                            9000000000000000a000000000000000
POLYVAL result =            4781d492cb8f926c504caa36f61008fe
POLYVAL result XOR nonce =  4481d492cb8f926c504caa36f61008fe
... and masked =            4481d492cb8f926c504caa36f610087e
Tag =                       24afc9805e976f451e6d87f6fe106514
Initial counter =           24afc9805e976f451e6d87f6fe106594
Result (36 bytes) =         6bb0fecf5ded9b77f902c7d5da236a43
                            91dd029724afc9805e976f451e6d87f6
                            fe106514

Next up, a vector where the plaintext and AAD lengths flip. ๐Ÿฆ‹

Plaintext (18 bytes) =      03000000000000000000000000000000
                            0400
AAD (20 bytes) =            01000000000000000000000000000000
                            02000000
Key =                       01000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = d9b360279694941ac5dbc6987ada7377
Record encryption key =     4004a0dcd862f2a57360219d2d44ef6c
POLYVAL input =             01000000000000000000000000000000
                            02000000000000000000000000000000
                            03000000000000000000000000000000
                            04000000000000000000000000000000
                            a0000000000000009000000000000000
POLYVAL result =            75cbc23a1a10e348aeb8e384b5cc79fd
POLYVAL result XOR nonce =  76cbc23a1a10e348aeb8e384b5cc79fd
... and masked =            76cbc23a1a10e348aeb8e384b5cc797d
Tag =                       bff9b2ef00fb47920cc72a0c0f13b9fd
Initial counter =           bff9b2ef00fb47920cc72a0c0f13b9fd
Result (34 bytes) =         44d0aaf6fb2f1f34add5e8064e83e12a
                            2adabff9b2ef00fb47920cc72a0c0f13
                            b9fd

Now the empty-plaintext, empty-AAD case. It's giving minimalism. ๐ŸŒธ

Plaintext (0 bytes) =
AAD (0 bytes) =
Key =                       e66021d5eb8e4f4066d4adb9c33560e4
Nonce =                     f46e44bb3da0015c94f70887
Record authentication key = 036ee1fe2d7926af68898095e54e7b3c
Record encryption key =     5e46482396008223b5c1d25173d87539
POLYVAL input =             00000000000000000000000000000000
POLYVAL result =            00000000000000000000000000000000
POLYVAL result XOR nonce =  f46e44bb3da0015c94f7088700000000
... and masked =            f46e44bb3da0015c94f7088700000000
Tag =                       a4194b79071b01a87d65f706e3949578
Initial counter =           a4194b79071b01a87d65f706e39495f8
Result (16 bytes) =         a4194b79071b01a87d65f706e3949578

Here's a short 3-byte plaintext with 5-byte AAD. ๐Ÿงš

Plaintext (3 bytes) =       7a806c
AAD (5 bytes) =             46bb91c3c5
Key =                       36864200e0eaf5284d884a0e77d31646
Nonce =                     bae8e37fc83441b16034566b
Record authentication key = 3e28de1120b2981a0155795ca2812af6
Record encryption key =     6d4b78b31a4c9c03d8db0f42f7507fae
POLYVAL input =             46bb91c3c50000000000000000000000
                            7a806c00000000000000000000000000
                            28000000000000001800000000000000
POLYVAL result =            43d9a745511dcfa21b96dd606f1d5720
POLYVAL result XOR nonce =  f931443a99298e137ba28b0b6f1d5720
... and masked =            f931443a99298e137ba28b0b6f1d5720
Tag =                       711bd85bc1e4d3e0a462e074eea428a8
Initial counter =           711bd85bc1e4d3e0a462e074eea428a8
Result (19 bytes) =         af60eb711bd85bc1e4d3e0a462e074ee
                            a428a8

A 6-byte plaintext with 10-byte AAD. ๐Ÿ’œ

Plaintext (6 bytes) =       bdc66f146545
AAD (10 bytes) =            fc880c94a95198874296
Key =                       aedb64a6c590bc84d1a5e269e4b47801
Nonce =                     afc0577e34699b9e671fdd4f
Record authentication key = 43b8de9cea62330d15cccfc84a33e8c8
Record encryption key =     8e54631607e431e095b54852868e3a27
POLYVAL input =             fc880c94a95198874296000000000000
                            bdc66f14654500000000000000000000
                            50000000000000003000000000000000
POLYVAL result =            26498e0d2b1ef004e808c458e8f2f515
POLYVAL result XOR nonce =  8989d9731f776b9a8f171917e8f2f515
... and masked =            8989d9731f776b9a8f171917e8f2f515
Tag =                       d6a9c45545cfc11f03ad743dba20f966
Initial counter =           d6a9c45545cfc11f03ad743dba20f9e6
Result (22 bytes) =         bb93a3e34d3cd6a9c45545cfc11f03ad
                            743dba20f966

A 9-byte plaintext with 15-byte AAD. ๐Ÿ“

Plaintext (9 bytes) =       1177441f195495860f
AAD (15 bytes) =            046787f3ea22c127aaf195d1894728
Key =                       d5cc1fd161320b6920ce07787f86743b
Nonce =                     275d1ab32f6d1f0434d8848c
Record authentication key = 8a51df64d93eaf667c2c09bd454ce5c5
Record encryption key =     43ab276c2b4a473918ca73f2dd85109c
POLYVAL input =             046787f3ea22c127aaf195d189472800
                            1177441f195495860f00000000000000
                            78000000000000004800000000000000
POLYVAL result =            63a3451c0b23345ad02bba59956517cf
POLYVAL result XOR nonce =  44fe5faf244e2b5ee4f33ed5956517cf
... and masked =            44fe5faf244e2b5ee4f33ed59565174f
Tag =                       1d02fd0cd174c84fc5dae2f60f52fd2b
Initial counter =           1d02fd0cd174c84fc5dae2f60f52fdab
Result (25 bytes) =         4f37281f7ad12949d01d02fd0cd174c8
                            4fc5dae2f60f52fd2b

A 12-byte plaintext with 20-byte AAD. ๐ŸŒˆ

Plaintext (12 bytes) =      9f572c614b4745914474e7c7
AAD (20 bytes) =            c9882e5386fd9f92ec489c8fde2be2cf
                            97e74e93
Key =                       b3fed1473c528b8426a582995929a149
Nonce =                     9e9ad8780c8d63d0ab4149c0
Record authentication key = 22f50707a95dd416df069d670cb775e8
Record encryption key =     f674a5584ee21fe97b4cebc468ab61e4
POLYVAL input =             c9882e5386fd9f92ec489c8fde2be2cf
                            97e74e93000000000000000000000000
                            9f572c614b4745914474e7c700000000
                            a0000000000000006000000000000000
POLYVAL result =            0cca0423fba9d77fe7e2e6963b08cdd0
POLYVAL result XOR nonce =  9250dc5bf724b4af4ca3af563b08cdd0
... and masked =            9250dc5bf724b4af4ca3af563b08cd50
Tag =                       c1dc2f871fb7561da1286e655e24b7b0
Initial counter =           c1dc2f871fb7561da1286e655e24b7b0
Result (28 bytes) =         f54673c5ddf710c745641c8bc1dc2f87
                            1fb7561da1286e655e24b7b0

A 15-byte plaintext with 25-byte AAD. โœจ

Plaintext (15 bytes) =      0d8c8451178082355c9e940fea2f58
AAD (25 bytes) =            2950a70d5a1db2316fd568378da107b5
                            2b0da55210cc1c1b0a
Key =                       2d4ed87da44102952ef94b02b805249b
Nonce =                     ac80e6f61455bfac8308a2d4
Record authentication key = 0b00a29a83e7e95b92e3a0783b29f140
Record encryption key =     a430c27f285aed913005975c42eed5f3
POLYVAL input =             2950a70d5a1db2316fd568378da107b5
                            2b0da55210cc1c1b0a00000000000000
                            0d8c8451178082355c9e940fea2f5800
                            c8000000000000007800000000000000
POLYVAL result =            1086ef25247aa41009bbc40871d9b350
POLYVAL result XOR nonce =  bc0609d3302f1bbc8ab366dc71d9b350
... and masked =            bc0609d3302f1bbc8ab366dc71d9b350
Tag =                       83b3449b9f39552de99dc214a1190b0b
Initial counter =           83b3449b9f39552de99dc214a1190b8b
Result (31 bytes) =         c9ff545e07b88a015f05b274540aa183
                            b3449b9f39552de99dc214a1190b0b

An 18-byte plaintext with 30-byte AAD. This one is a whole mood. ๐Ÿฅน

Plaintext (18 bytes) =      6b3db4da3d57aa94842b9803a96e07fb
                            6de7
AAD (30 bytes) =            1860f762ebfbd08284e421702de0de18
                            baa9c9596291b08466f37de21c7f
Key =                       bde3b2f204d1e9f8b06bc47f9745b3d1
Nonce =                     ae06556fb6aa7890bebc18fe
Record authentication key = 21c874a8bad3603d1c3e8784df5b3f9f
Record encryption key =     d1c16d72651c3df504eae27129d818e8
POLYVAL input =             1860f762ebfbd08284e421702de0de18
                            baa9c9596291b08466f37de21c7f0000
                            6b3db4da3d57aa94842b9803a96e07fb
                            6de70000000000000000000000000000
                            f0000000000000009000000000000000
POLYVAL result =            55462a5afa0da8d646481e049ef9c764
POLYVAL result XOR nonce =  fb407f354ca7d046f8f406fa9ef9c764
... and masked =            fb407f354ca7d046f8f406fa9ef9c764
Tag =                       3e377094f04709f64d7b985310a4db84
Initial counter =           3e377094f04709f64d7b985310a4db84
Result (34 bytes) =         6298b296e24e8cc35dce0bed484b7f30
                            d5803e377094f04709f64d7b985310a4
                            db84

And the big finale for AEAD_AES_128_GCM_SIV: a 21-byte plaintext with 35-byte AAD. Iconic. ๐Ÿ‘‘

Plaintext (21 bytes) =      e42a3c02c25b64869e146d7b233987bd
                            dfc240871d
AAD (35 bytes) =            7576f7028ec6eb5ea7e298342a94d4b2
                            02b370ef9768ec6561c4fe6b7e7296fa
                            859c21
Key =                       f901cfe8a69615a93fdf7a98cad48179
Nonce =                     6245709fb18853f68d833640
Record authentication key = 3724f55f1d22ac0ab830da0b6a995d74
Record encryption key =     75ac87b70c05db287de779006105a344
POLYVAL input =             7576f7028ec6eb5ea7e298342a94d4b2
                            02b370ef9768ec6561c4fe6b7e7296fa
                            859c2100000000000000000000000000
                            e42a3c02c25b64869e146d7b233987bd
                            dfc240871d0000000000000000000000
                            1801000000000000a800000000000000
POLYVAL result =            4cbba090f03f7d1188ea55749fa6c7bd
POLYVAL result XOR nonce =  2efed00f41b72ee7056963349fa6c7bd
... and masked =            2efed00f41b72ee7056963349fa6c73d
Tag =                       2d15506c84a9edd65e13e9d24a2a6e70
Initial counter =           2d15506c84a9edd65e13e9d24a2a6ef0
Result (37 bytes) =         391cc328d484a4f46406181bcd62efd9

                            b3ee197d052d15506c84a9edd65e13e9
                            d24a2a6e70

C.2. AEAD_AES_256_GCM_SIV

Now we level up to AES-256 with the AEAD_AES_256_GCM_SIV vectors. The key is twice as long, so it's a bigger commitment, bestie. ๐Ÿ’…๐Ÿ”ฅ

Here's the one with empty plaintext and empty AAD, and a 32-byte key. ๐ŸŒธ

Plaintext (0 bytes) =
AAD (0 bytes) =
Key =                       01000000000000000000000000000000
                            00000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = b5d3c529dfafac43136d2d11be284d7f
Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                            456e3c6c05ecc157cdbf0700fedad222
POLYVAL input =             00000000000000000000000000000000
POLYVAL result =            00000000000000000000000000000000
POLYVAL result XOR nonce =  03000000000000000000000000000000
... and masked =            03000000000000000000000000000000
Tag =                       07f5f4169bbf55a8400cd47ea6fd400f
Initial counter =           07f5f4169bbf55a8400cd47ea6fd408f
Result (16 bytes) =         07f5f4169bbf55a8400cd47ea6fd400f

That's the whole test vector set! ๐ŸŒˆ Every number checks out, and the output is exactly what the spec says it should be. โญ

okay so we're still in the AEAD_AES_256_GCM_SIV test vectors ๐ŸŽ€ each block walks through one encryption from start to finish, so you can check your own work against them โœจ

Plaintext is 8 bytes and there's no AAD. The key is 32 bytes, so this is the AES-256 one ๐Ÿฆ‹

Plaintext (8 bytes) =       0100000000000000
AAD (0 bytes) =
Key =                       01000000000000000000000000000000
                            00000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = b5d3c529dfafac43136d2d11be284d7f
Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                            456e3c6c05ecc157cdbf0700fedad222
POLYVAL input =             01000000000000000000000000000000
                            00000000000000004000000000000000
POLYVAL result =            05230f62f0eac8aa14fe4d646b59cd41
POLYVAL result XOR nonce =  06230f62f0eac8aa14fe4d646b59cd41
... and masked =            06230f62f0eac8aa14fe4d646b59cd41
Tag =                       843122130f7364b761e0b97427e3df28
Initial counter =           843122130f7364b761e0b97427e3dfa8
Result (24 bytes) =         c2ef328e5c71c83b843122130f7364b7
                            61e0b97427e3df28

Next up is a 12 byte plaintext, still no AAD, same key and nonce ๐ŸŒธ the POLYVAL input's length field changes here, so the result is totally different even though the setup is the same ๐Ÿ’…

Plaintext (12 bytes) =      010000000000000000000000
AAD (0 bytes) =
Key =                       01000000000000000000000000000000
                            00000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = b5d3c529dfafac43136d2d11be284d7f
Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3

                            456e3c6c05ecc157cdbf0700fedad222
POLYVAL input =             01000000000000000000000000000000
                            00000000000000006000000000000000
POLYVAL result =            6d81a24732fd6d03ae5af544720a1c13
POLYVAL result XOR nonce =  6e81a24732fd6d03ae5af544720a1c13
... and masked =            6e81a24732fd6d03ae5af544720a1c13
Tag =                       8ca50da9ae6559e48fd10f6e5c9ca17e
Initial counter =           8ca50da9ae6559e48fd10f6e5c9ca1fe
Result (28 bytes) =         9aab2aeb3faa0a34aea8e2b18ca50da9
                            ae6559e48fd10f6e5c9ca17e

Now a 16 byte plaintext, still with no AAD ๐Ÿงš the length field in the POLYVAL input is the only thing that moves around, so spot the difference, it's giving detail-oriented ๐Ÿ‘‘

Plaintext (16 bytes) =      01000000000000000000000000000000
AAD (0 bytes) =
Key =                       01000000000000000000000000000000
                            00000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = b5d3c529dfafac43136d2d11be284d7f
Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                            456e3c6c05ecc157cdbf0700fedad222
POLYVAL input =             01000000000000000000000000000000
                            00000000000000008000000000000000
POLYVAL result =            74eee2bf7c9a165f8b25dea73db32a6d
POLYVAL result XOR nonce =  77eee2bf7c9a165f8b25dea73db32a6d
... and masked =            77eee2bf7c9a165f8b25dea73db32a6d
Tag =                       c9eac6fa700942702e90862383c6c366
Initial counter =           c9eac6fa700942702e90862383c6c3e6
Result (32 bytes) =         85a01b63025ba19b7fd3ddfc033b3e76
                            c9eac6fa700942702e90862383c6c366

Now it gets bigger ๐Ÿ”ฅ a 32 byte plaintext, two blocks long, still no AAD. Notice the POLYVAL input grows to three lines here ๐Ÿฆ‹

Plaintext (32 bytes) =      01000000000000000000000000000000
                            02000000000000000000000000000000
AAD (0 bytes) =
Key =                       01000000000000000000000000000000
                            00000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = b5d3c529dfafac43136d2d11be284d7f
Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                            456e3c6c05ecc157cdbf0700fedad222
POLYVAL input =             01000000000000000000000000000000
                            02000000000000000000000000000000
                            00000000000000000001000000000000
POLYVAL result =            899b6381b3d46f0def7aa0517ba188f5
POLYVAL result XOR nonce =  8a9b6381b3d46f0def7aa0517ba188f5
... and masked =            8a9b6381b3d46f0def7aa0517ba18875
Tag =                       e819e63abcd020b006a976397632eb5d
Initial counter =           e819e63abcd020b006a976397632ebdd
Result (48 bytes) =         4a6a9db4c8c6549201b9edb53006cba8
                            21ec9cf850948a7c86c68ac7539d027f
                            e819e63abcd020b006a976397632eb5d

A 48 byte plaintext next, still no AAD ๐Ÿ’œ three blocks of plaintext, and the result is now three blocks plus the tag ๐ŸŒˆ

Plaintext (48 bytes) =      01000000000000000000000000000000
                            02000000000000000000000000000000
                            03000000000000000000000000000000
AAD (0 bytes) =
Key =                       01000000000000000000000000000000
                            00000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = b5d3c529dfafac43136d2d11be284d7f
Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                            456e3c6c05ecc157cdbf0700fedad222
POLYVAL input =             01000000000000000000000000000000
                            02000000000000000000000000000000
                            03000000000000000000000000000000
                            00000000000000008001000000000000
POLYVAL result =            c1f8593d8fc29b0c290cae1992f71f51
POLYVAL result XOR nonce =  c2f8593d8fc29b0c290cae1992f71f51
... and masked =            c2f8593d8fc29b0c290cae1992f71f51
Tag =                       790bc96880a99ba804bd12c0e6a22cc4
Initial counter =           790bc96880a99ba804bd12c0e6a22cc4
Result (64 bytes) =         c00d121893a9fa603f48ccc1ca3c57ce
                            7499245ea0046db16c53c7c66fe717e3
                            9cf6c748837b61f6ee3adcee17534ed5
                            790bc96880a99ba804bd12c0e6a22cc4

Next is a 64 byte plaintext, still no AAD โญ four full blocks of plaintext, and the POLYVAL input has five lines because of the length block at the end ๐Ÿ’€

Plaintext (64 bytes) =      01000000000000000000000000000000
                            02000000000000000000000000000000
                            03000000000000000000000000000000
                            04000000000000000000000000000000
AAD (0 bytes) =
Key =                       01000000000000000000000000000000
                            00000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = b5d3c529dfafac43136d2d11be284d7f
Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                            456e3c6c05ecc157cdbf0700fedad222
POLYVAL input =             01000000000000000000000000000000
                            02000000000000000000000000000000
                            03000000000000000000000000000000
                            04000000000000000000000000000000
                            00000000000000000002000000000000
POLYVAL result =            6ef38b06046c7c0e225efaef8e2ec4c4
POLYVAL result XOR nonce =  6df38b06046c7c0e225efaef8e2ec4c4
... and masked =            6df38b06046c7c0e225efaef8e2ec444
Tag =                       112864c269fc0d9d88c61fa47e39aa08
Initial counter =           112864c269fc0d9d88c61fa47e39aa88
Result (80 bytes) =         c2d5160a1f8683834910acdafc41fbb1
                            632d4a353e8b905ec9a5499ac34f96c7
                            e1049eb080883891a4db8caaa1f99dd0
                            04d80487540735234e3744512c6f90ce
                            112864c269fc0d9d88c61fa47e39aa08

Okay now the AAD starts showing up ๐Ÿ™ˆ this one has an 8 byte plaintext and a 1 byte AAD of 01, so the POLYVAL input picks up the AAD block at the front ๐Ÿ’–

Plaintext (8 bytes) =       0200000000000000
AAD (1 bytes) =             01
Key =                       01000000000000000000000000000000
                            00000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = b5d3c529dfafac43136d2d11be284d7f
Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                            456e3c6c05ecc157cdbf0700fedad222
POLYVAL input =             01000000000000000000000000000000
                            02000000000000000000000000000000
                            08000000000000004000000000000000
POLYVAL result =            34e57bafe011b9b36fc6821b7ffb3354
POLYVAL result XOR nonce =  37e57bafe011b9b36fc6821b7ffb3354
... and masked =            37e57bafe011b9b36fc6821b7ffb3354
Tag =                       91213f267e3b452f02d01ae33e4ec854
Initial counter =           91213f267e3b452f02d01ae33e4ec8d4
Result (24 bytes) =         1de22967237a813291213f267e3b452f
                            02d01ae33e4ec854

Same AAD of 01, now a 12 byte plaintext ๐Ÿฆ‹ the length field at the end of the POLYVAL input is the thing to watch here โœจ

Plaintext (12 bytes) =      020000000000000000000000
AAD (1 bytes) =             01
Key =                       01000000000000000000000000000000
                            00000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = b5d3c529dfafac43136d2d11be284d7f
Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                            456e3c6c05ecc157cdbf0700fedad222
POLYVAL input =             01000000000000000000000000000000
                            02000000000000000000000000000000
                            08000000000000006000000000000000
POLYVAL result =            5c47d68a22061c1ad5623a3b66a8e206
POLYVAL result XOR nonce =  5f47d68a22061c1ad5623a3b66a8e206
... and masked =            5f47d68a22061c1ad5623a3b66a8e206
Tag =                       c1a4a19ae800941ccdc57cc8413c277f
Initial counter =           c1a4a19ae800941ccdc57cc8413c27ff
Result (28 bytes) =         163d6f9cc1b346cd453a2e4cc1a4a19a
                            e800941ccdc57cc8413c277f

A 16 byte plaintext with the AAD of 01 ๐ŸŒธ the AAD block and the plaintext block are both full now, so the input is nice and tidy ๐Ÿ’…

Plaintext (16 bytes) =      02000000000000000000000000000000
AAD (1 bytes) =             01
Key =                       01000000000000000000000000000000
                            00000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = b5d3c529dfafac43136d2d11be284d7f
Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                            456e3c6c05ecc157cdbf0700fedad222
POLYVAL input =             01000000000000000000000000000000
                            02000000000000000000000000000000
                            08000000000000008000000000000000
POLYVAL result =            452896726c616746f01d11d82911d478
POLYVAL result XOR nonce =  462896726c616746f01d11d82911d478
... and masked =            462896726c616746f01d11d82911d478
Tag =                       b292d28ff61189e8e49f3875ef91aff7
Initial counter =           b292d28ff61189e8e49f3875ef91aff7
Result (32 bytes) =         c91545823cc24f17dbb0e9e807d5ec17
                            b292d28ff61189e8e49f3875ef91aff7

Last one in this batch ๐Ÿ‘‘ a 32 byte plaintext with the AAD of 01. The POLYVAL input has three plaintext-and-AAD lines, and the length block at the end is what sets it apart from the others ๐ŸŽ€

Plaintext (32 bytes) =      02000000000000000000000000000000
                            03000000000000000000000000000000
AAD (1 bytes) =             01
Key =                       01000000000000000000000000000000
                            00000000000000000000000000000000
Nonce =                     030000000000000000000000
Record authentication key = b5d3c529dfafac43136d2d11be284d7f
Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                            456e3c6c05ecc157cdbf0700fedad222
POLYVAL input =             01000000000000000000000000000000
                            02000000000000000000000000000000
                            03000000000000000000000000000000
                            08000000000000000001000000000000
POLYVAL result =            4e58c1e341c9bb0ae34eda9509dfc90c
POLYVAL result XOR nonce =  4d58c1e341c9bb0ae34eda9509dfc90c
... and masked =            4d58c1e341c9bb0ae34eda9509dfc90c
Tag =                       aea1bad12702e1965604374aab96dbbc
Initial counter =           aea1bad12702e1965604374aab96dbbc
Result (48 bytes) =         07dad364bfc2b9da89116d7bef6daaaf
                            6f255510aa654f920ac81b94e8bad365
                            aea1bad12702e1965604374aab96dbbc

Okay bestie, here are the rest of the test vectors. Copy them exactly, every hex digit matters ๐Ÿ’…โœจ

   Plaintext (64 bytes) =      02000000000000000000000000000000
                               03000000000000000000000000000000
                               04000000000000000000000000000000
                               05000000000000000000000000000000
   AAD (1 bytes) =             01
   Key =                       01000000000000000000000000000000
                               00000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = b5d3c529dfafac43136d2d11be284d7f
   Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                               456e3c6c05ecc157cdbf0700fedad222
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               03000000000000000000000000000000
                               04000000000000000000000000000000
                               05000000000000000000000000000000
                               08000000000000000002000000000000
   POLYVAL result =            da58d2f61b0a9d343b2f37fb0c519733
   POLYVAL result XOR nonce =  d958d2f61b0a9d343b2f37fb0c519733
   ... and masked =            d958d2f61b0a9d343b2f37fb0c519733
   Tag =                       5bde0285037c5de81e5b570a049b62a0
   Initial counter =           5bde0285037c5de81e5b570a049b62a0
   Result (80 bytes) =         67fd45e126bfb9a79930c43aad2d3696
                               7d3f0e4d217c1e551f59727870beefc9
                               8cb933a8fce9de887b1e40799988db1f
                               c3f91880ed405b2dd298318858467c89
                               5bde0285037c5de81e5b570a049b62a0

   Plaintext (4 bytes) =       02000000
   AAD (12 bytes) =            010000000000000000000000
   Key =                       01000000000000000000000000000000
                               00000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = b5d3c529dfafac43136d2d11be284d7f
   Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                               456e3c6c05ecc157cdbf0700fedad222
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               60000000000000002000000000000000
   POLYVAL result =            6dc76ae84b88916e073a303aafde05cf
   POLYVAL result XOR nonce =  6ec76ae84b88916e073a303aafde05cf
   ... and masked =            6ec76ae84b88916e073a303aafde054f
   Tag =                       1835e517741dfddccfa07fa4661b74cf
   Initial counter =           1835e517741dfddccfa07fa4661b74cf
   Result (20 bytes) =         22b3f4cd1835e517741dfddccfa07fa4
                               661b74cf


   Plaintext (20 bytes) =      03000000000000000000000000000000
                               04000000
   AAD (18 bytes) =            01000000000000000000000000000000
                               0200
   Key =                       01000000000000000000000000000000
                               00000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = b5d3c529dfafac43136d2d11be284d7f
   Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                               456e3c6c05ecc157cdbf0700fedad222
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               03000000000000000000000000000000
                               04000000000000000000000000000000
                               9000000000000000a000000000000000
   POLYVAL result =            973ef4fd04bd31d193816ab26f8655ca
   POLYVAL result XOR nonce =  943ef4fd04bd31d193816ab26f8655ca
   ... and masked =            943ef4fd04bd31d193816ab26f86554a
   Tag =                       b879ad976d8242acc188ab59cabfe307
   Initial counter =           b879ad976d8242acc188ab59cabfe387
   Result (36 bytes) =         43dd0163cdb48f9fe3212bf61b201976
                               067f342bb879ad976d8242acc188ab59
                               cabfe307


   Plaintext (18 bytes) =      03000000000000000000000000000000
                               0400
   AAD (20 bytes) =            01000000000000000000000000000000

                               02000000
   Key =                       01000000000000000000000000000000
                               00000000000000000000000000000000
   Nonce =                     030000000000000000000000
   Record authentication key = b5d3c529dfafac43136d2d11be284d7f
   Record encryption key =     b914f4742be9e1d7a2f84addbf96dec3
                               456e3c6c05ecc157cdbf0700fedad222
   POLYVAL input =             01000000000000000000000000000000
                               02000000000000000000000000000000
                               03000000000000000000000000000000
                               04000000000000000000000000000000
                               a0000000000000009000000000000000
   POLYVAL result =            2cbb6b7ab2dbffefb797f825f826870c
   POLYVAL result XOR nonce =  2fbb6b7ab2dbffefb797f825f826870c
   ... and masked =            2fbb6b7ab2dbffefb797f825f826870c
   Tag =                       cfcdf5042112aa29685c912fc2056543
   Initial counter =           cfcdf5042112aa29685c912fc20565c3
   Result (34 bytes) =         462401724b5ce6588d5a54aae5375513
                               a075cfcdf5042112aa29685c912fc205
                               6543

   Plaintext (0 bytes) =
   AAD (0 bytes) =
   Key =                       e66021d5eb8e4f4066d4adb9c33560e4
                               f46e44bb3da0015c94f7088736864200
   Nonce =                     e0eaf5284d884a0e77d31646
   Record authentication key = e40d26f82774aa27f47b047b608b9585
   Record encryption key =     7c7c3d9a542cef53dde0e6de9b580040
                               0f82e73ec5f7ee41b7ba8dcb9ba078c3
   POLYVAL input =             00000000000000000000000000000000
   POLYVAL result =            00000000000000000000000000000000
   POLYVAL result XOR nonce =  e0eaf5284d884a0e77d3164600000000
   ... and masked =            e0eaf5284d884a0e77d3164600000000
   Tag =                       169fbb2fbf389a995f6390af22228a62
   Initial counter =           169fbb2fbf389a995f6390af22228ae2
   Result (16 bytes) =         169fbb2fbf389a995f6390af22228a62


   Plaintext (3 bytes) =       671fdd
   AAD (5 bytes) =             4fbdc66f14
   Key =                       bae8e37fc83441b16034566b7a806c46
                               bb91c3c5aedb64a6c590bc84d1a5e269
   Nonce =                     e4b47801afc0577e34699b9e
   Record authentication key = b546f5a850d0a90adfe39e95c2510fc6
   Record encryption key =     b9d1e239d62cbb5c49273ddac8838bdc
                               c53bca478a770f07087caa4e0a924a55
   POLYVAL input =             4fbdc66f140000000000000000000000
                               671fdd00000000000000000000000000
                               28000000000000001800000000000000
   POLYVAL result =            b91f91f96b159a7c611c05035b839e92
   POLYVAL result XOR nonce =  5dabe9f8c4d5cd0255759e9d5b839e92
   ... and masked =            5dabe9f8c4d5cd0255759e9d5b839e12
   Tag =                       93da9bb81333aee0c785b240d319719d
   Initial counter =           93da9bb81333aee0c785b240d319719d
   Result (19 bytes) =         0eaccb93da9bb81333aee0c785b240d3
                               19719d


   Plaintext (6 bytes) =       195495860f04
   AAD (10 bytes) =            6787f3ea22c127aaf195
   Key =                       6545fc880c94a95198874296d5cc1fd1
                               61320b6920ce07787f86743b275d1ab3
   Nonce =                     2f6d1f0434d8848c1177441f
   Record authentication key = e156e1f9b0b07b780cbe30f259e3c8da
   Record encryption key =     6fc1c494519f944aae52fcd8b14e5b17
                               1b5a9429d3b76e430d49940c0021d612
   POLYVAL input =             6787f3ea22c127aaf195000000000000
                               195495860f0400000000000000000000
                               50000000000000003000000000000000
   POLYVAL result =            2c480ed9d236b1df24c6eec109bd40c1
   POLYVAL result XOR nonce =  032511dde6ee355335b1aade09bd40c1
   ... and masked =            032511dde6ee355335b1aade09bd4041
   Tag =                       6b62b84dc40c84636a5ec12020ec8c2c
   Initial counter =           6b62b84dc40c84636a5ec12020ec8cac
   Result (22 bytes) =         a254dad4f3f96b62b84dc40c84636a5e
                               c12020ec8c2c


   Plaintext (9 bytes) =       c9882e5386fd9f92ec
   AAD (15 bytes) =            489c8fde2be2cf97e74e932d4ed87d
   Key =                       d1894728b3fed1473c528b8426a58299
                               5929a1499e9ad8780c8d63d0ab4149c0
   Nonce =                     9f572c614b4745914474e7c7
   Record authentication key = 0533fd71f4119257361a3ff1469dd4e5
   Record encryption key =     4feba89799be8ac3684fa2bb30ade0ea
                               51390e6d87dcf3627d2ee44493853abe
   POLYVAL input =             489c8fde2be2cf97e74e932d4ed87d00
                               c9882e5386fd9f92ec00000000000000
                               78000000000000004800000000000000
   POLYVAL result =            bf160bc9ded8c63057d2c38aae552fb4
   POLYVAL result XOR nonce =  204127a8959f83a113a6244dae552fb4
   ... and masked =            204127a8959f83a113a6244dae552f34
   Tag =                       c0fd3dc6628dfe55ebb0b9fb2295c8c2
   Initial counter =           c0fd3dc6628dfe55ebb0b9fb2295c8c2
   Result (25 bytes) =         0df9e308678244c44bc0fd3dc6628dfe
                               55ebb0b9fb2295c8c2

Okay the test vectors keep going, bestie, and these are SO satisfying to check ๐ŸŽ€

Plaintext (12 bytes) =      1db2316fd568378da107b52b
AAD (20 bytes) =            0da55210cc1c1b0abde3b2f204d1e9f8
                            b06bc47f
Key =                       a44102952ef94b02b805249bac80e6f6
                            1455bfac8308a2d40d8c845117808235
Nonce =                     5c9e940fea2f582950a70d5a
Record authentication key = 64779ab10ee8a280272f14cc8851b727
Record encryption key =     25f40fc63f49d3b9016a8eeeb75846e0
                            d72ca36ddbd312b6f5ef38ad14bd2651
POLYVAL input =             0da55210cc1c1b0abde3b2f204d1e9f8
                            b06bc47f000000000000000000000000
                            1db2316fd568378da107b52b00000000
                            a0000000000000006000000000000000
POLYVAL result =            cc86ee22c861e1fd474c84676b42739c
POLYVAL result XOR nonce =  90187a2d224eb9d417eb893d6b42739c
... and masked =            90187a2d224eb9d417eb893d6b42731c
Tag =                       404099c2587f64979f21826706d497d5
Initial counter =           404099c2587f64979f21826706d497d5
Result (28 bytes) =         8dbeb9f7255bf5769dd56692404099c2
                            587f64979f21826706d497d5

Next up, a 15-byte plaintext with a 25-byte AAD, still in the same ๐Ÿ”ฅ AES-256 energy:

Plaintext (15 bytes) =      21702de0de18baa9c9596291b08466
AAD (25 bytes) =            f37de21c7ff901cfe8a69615a93fdf7a
                            98cad481796245709f
Key =                       9745b3d1ae06556fb6aa7890bebc18fe
                            6b3db4da3d57aa94842b9803a96e07fb
Nonce =                     6de71860f762ebfbd08284e4
Record authentication key = 27c2959ed4daea3b1f52e849478de376
Record encryption key =     307a38a5a6cf231c0a9af3b527f23a62
                            e9a6ff09aff8ae669f760153e864fc93
POLYVAL input =             f37de21c7ff901cfe8a69615a93fdf7a
                            98cad481796245709f00000000000000
                            21702de0de18baa9c9596291b0846600
                            c8000000000000007800000000000000
POLYVAL result =            c4fa5e5b713853703bcf8e6424505fa5
POLYVAL result XOR nonce =  a91d463b865ab88beb4d0a8024505fa5
... and masked =            a91d463b865ab88beb4d0a8024505f25
Tag =                       b3080d28f6ebb5d3648ce97bd5ba67fd
Initial counter =           b3080d28f6ebb5d3648ce97bd5ba67fd
Result (31 bytes) =         793576dfa5c0f88729a7ed3c2f1bffb3
                            080d28f6ebb5d3648ce97bd5ba67fd

Now an 18-byte plaintext with a 30-byte AAD. The vibes are immaculate ๐Ÿฆ‹

Plaintext (18 bytes) =      b202b370ef9768ec6561c4fe6b7e7296
                            fa85
AAD (30 bytes) =            9c2159058b1f0fe91433a5bdc20e214e
                            ab7fecef4454a10ef0657df21ac7

Key =                       b18853f68d833640e42a3c02c25b6486
                            9e146d7b233987bddfc240871d7576f7
Nonce =                     028ec6eb5ea7e298342a94d4
Record authentication key = 670b98154076ddb59b7a9137d0dcc0f0
Record encryption key =     78116d78507fbe69d4a820c350f55c7c
                            b36c3c9287df0e9614b142b76a587c3f
POLYVAL input =             9c2159058b1f0fe91433a5bdc20e214e
                            ab7fecef4454a10ef0657df21ac70000
                            b202b370ef9768ec6561c4fe6b7e7296
                            fa850000000000000000000000000000
                            f0000000000000009000000000000000
POLYVAL result =            4e4108f09f41d797dc9256f8da8d58c7
POLYVAL result XOR nonce =  4ccfce1bc1e6350fe8b8c22cda8d58c7
... and masked =            4ccfce1bc1e6350fe8b8c22cda8d5847
Tag =                       454fc2a154fea91f8363a39fec7d0a49
Initial counter =           454fc2a154fea91f8363a39fec7d0ac9
Result (34 bytes) =         857e16a64915a787637687db4a951963
                            5cdd454fc2a154fea91f8363a39fec7d
                            0a49

And the last one in C.2, a 21-byte plaintext with a 35-byte AAD. Lowkey my favourite ๐Ÿ‘‘

Plaintext (21 bytes) =      ced532ce4159b035277d4dfbb7db6296
                            8b13cd4eec
AAD (35 bytes) =            734320ccc9d9bbbb19cb81b2af4ecbc3
                            e72834321f7aa0f70b7282b4f33df23f
                            167541
Key =                       3c535de192eaed3822a2fbbe2ca9dfc8
                            8255e14a661b8aa82cc54236093bbc23
Nonce =                     688089e55540db1872504e1c
Record authentication key = cb8c3aa3f8dbaeb4b28a3e86ff6625f8
Record encryption key =     02426ce1aa3ab31313b0848469a1b5fc
                            6c9af9602600b195b04ad407026bc06d
POLYVAL input =             734320ccc9d9bbbb19cb81b2af4ecbc3
                            e72834321f7aa0f70b7282b4f33df23f
                            16754100000000000000000000000000
                            ced532ce4159b035277d4dfbb7db6296
                            8b13cd4eec0000000000000000000000
                            1801000000000000a800000000000000
POLYVAL result =            ffd503c7dd712eb3791b7114b17bb0cf
POLYVAL result XOR nonce =  97558a228831f5ab0b4b3f08b17bb0cf
... and masked =            97558a228831f5ab0b4b3f08b17bb04f
Tag =                       9d6c7029675b89eaf4ba1ded1a286594
Initial counter =           9d6c7029675b89eaf4ba1ded1a286594
Result (37 bytes) =         626660c26ea6612fb17ad91e8e767639
                            edd6c9faee9d6c7029675b89eaf4ba1d
                            ed1a286594

C.3. Counter Wrap Tests โœจ

Okay this section is the big brain part ๐Ÿง  Everything in here uses AEAD_AES_256_GCM_SIV, and the whole point is to check that the block counter wraps around correctly. Iconic testing behaviour, honestly.

Here's the first one, with a 32-byte plaintext and an all-zero key and nonce:

Plaintext (32 bytes) =      00000000000000000000000000000000
                            4db923dc793ee6497c76dcc03a98e108
AAD (0 bytes) =
Key =                       00000000000000000000000000000000
                            00000000000000000000000000000000
Nonce =                     000000000000000000000000
Record authentication key = dc95c078a24089895275f3d86b4fb868
Record encryption key =     779b38d15bffb63d39d6e9ae76a9b2f3
                            75d11b0e3a68c422845c7d4690fa594f
POLYVAL input =             00000000000000000000000000000000
                            4db923dc793ee6497c76dcc03a98e108
                            00000000000000000001000000000000
POLYVAL result =            7367cdb411b730128dd56e8edc0eff56
POLYVAL result XOR nonce =  7367cdb411b730128dd56e8edc0eff56
... and masked =            7367cdb411b730128dd56e8edc0eff56
Tag =                       ffffffff000000000000000000000000
Initial counter =           ffffffff000000000000000000000080
Result (48 bytes) =         f3f80f2cf0cb2dd9c5984fcda908456c
                            c537703b5ba70324a6793a7bf218d3ea
                            ffffffff000000000000000000000000

And the second one, a 24-byte plaintext with the same key and nonce. The counter is doing its thing ๐ŸŒˆ

Plaintext (24 bytes) =      eb3640277c7ffd1303c7a542d02d3e4c
                            0000000000000000
AAD (0 bytes) =
Key =                       00000000000000000000000000000000
                            00000000000000000000000000000000
Nonce =                     000000000000000000000000
Record authentication key = dc95c078a24089895275f3d86b4fb868
Record encryption key =     779b38d15bffb63d39d6e9ae76a9b2f3
                            75d11b0e3a68c422845c7d4690fa594f
POLYVAL input =             eb3640277c7ffd1303c7a542d02d3e4c
                            00000000000000000000000000000000
                            0000000000000000c000000000000000
POLYVAL result =            7367cdb411b730128dd56e8edc0eff56
POLYVAL result XOR nonce =  7367cdb411b730128dd56e8edc0eff56
... and masked =            7367cdb411b730128dd56e8edc0eff56
Tag =                       ffffffff000000000000000000000000
Initial counter =           ffffffff000000000000000000000080
Result (40 bytes) =         18ce4f0b8cb4d0cac65fea8f79257b20
                            888e53e72299e56dffffffff00000000
                            0000000000000000

Acknowledgements

Huge thank-yous to the people who helped make this spec better ๐Ÿซถ The authors are grateful to Daniel Bleichenbacher, Uri Blumenthal, Deb Cooley's team at NSA Information Assurance, Scott Fluhrer, Tetsu Iwata, Tibor Jager, John Mattsson, Ondrej Mosnacek, Kenny Paterson, Bart Preneel, Yannick Seurin, and Bjoern Tackmann for their helpful suggestions and review. Y'all are literally the best ๐Ÿ’–

Authors' Addresses

Here's where you can find the authors, bestie ๐ŸŒธ

Shay Gueron
University of Haifa and Amazon
Abba Khoushy Ave 199
Haifa  3498838
Israel

Email: shay@math.haifa.ac.il


Adam Langley
Google LLC
345 Spear St
San Francisco, CA  94105
United States of America

Email: agl@google.com


Yehuda Lindell
Bar-Ilan University and Unbound Tech
Ramat Gan
5290002
Israel

Email: Yehuda.Lindell@biu.ac.il
๐Ÿ“ฆ where this page comes from