# AES-GCM-SIV vector creation

This page documents the code that was used to generate the AES-GCM-SIV test vectors for key lengths not available in the OpenSSL test vectors. All the vectors were generated using OpenSSL and verified with Rust.

## Creation

The following Python script was run to generate the vector files. The OpenSSL test vectors were used as a base and modified to have 192-bit key length.

    # This file is dual licensed under the terms of the Apache License, Version
    # 2.0, and the BSD License. See the LICENSE file in the root of this repository
    # for complete details.

    import binascii

    from cryptography.hazmat.primitives.ciphers.aead import AESGCMSIV

    def convert_key_to_192_bits(key: str) -> str:
        """
        This takes existing 128 and 256-bit keys from test vectors from OpenSSL
        and makes them 192-bit by either appending 0 or truncating the key.
        """
        new_key = binascii.unhexlify(key)
        if len(new_key) == 16:
            new_key += b"\x00" * 8
        elif len(new_key) == 32:
            new_key = new_key[0:24]
        else:
            raise RuntimeError(
                "Unexpected key length. OpenSSL AES-GCM-SIV test vectors only "
                "contain 128-bit and 256-bit keys"
            )

        return binascii.hexlify(new_key).decode("ascii")

    def encrypt(key: str, iv: str, plaintext: str, aad: str) -> (str, str):
        aesgcmsiv = AESGCMSIV(binascii.unhexlify(key))
        encrypted_output = aesgcmsiv.encrypt(
            binascii.unhexlify(iv),
            binascii.unhexlify(plaintext),
            binascii.unhexlify(aad) if aad else None,
        )
        ciphertext, tag = encrypted_output[:-16], encrypted_output[-16:]

        return (
            binascii.hexlify(ciphertext).decode("ascii"),
            binascii.hexlify(tag).decode("ascii"),
        )

    def build_vectors(filename):
        count = 0
        output = []
        key = None
        iv = None
        aad = None
        plaintext = None

        with open(filename) as vector_file:
            for line in vector_file:
                line = line.strip()
                if line.startswith("Key"):
                    if count != 0:
                        ciphertext, tag = encrypt(key, iv, plaintext, aad)
                        output.append(f"Tag = {tag}\nCiphertext = {ciphertext}\n")
                    output.append(f"\nCOUNT = {count}")
                    count += 1
                    aad = None
                    _, key = line.split(" = ")
                    key = convert_key_to_192_bits(key)
                    output.append(f"Key = {key}")
                elif line.startswith("IV"):
                    _, iv = line.split(" = ")
                    output.append(f"IV = {iv}")
                elif line.startswith("AAD"):
                    _, aad = line.split(" = ")
                    output.append(f"AAD = {aad}")
                elif line.startswith("Plaintext"):
                    _, plaintext = line.split(" = ")
                    output.append(f"Plaintext = {plaintext}")

            ciphertext, tag = encrypt(key, iv, plaintext, aad)
            output.append(f"Tag = {tag}\nCiphertext = {ciphertext}\n")
            return "\n".join(output)

    def write_file(data, filename):
        with open(filename, "w") as f:
            f.write(data)

    path = "vectors/cryptography_vectors/ciphers/AES/GCM-SIV/openssl.txt"
    write_file(build_vectors(path), "aes-192-gcm-siv.txt")

Download link: `generate_aes192gcmsiv.py`

## Verification

The following Rust program was used to verify the vectors.

    use aes_gcm_siv::{
        aead::{Aead, KeyInit},
        AesGcmSiv, Nonce,
    };

    use aes::Aes192;
    use aes_gcm_siv::aead::generic_array::GenericArray;
    use aes_gcm_siv::aead::Payload;
    use std::fs::File;
    use std::io;
    use std::io::BufRead;
    use std::path::Path;

    pub type Aes192GcmSiv = AesGcmSiv<Aes192>;

    struct VectorArgs {
        nonce: String,
        key: String,
        aad: String,
        tag: String,
        plaintext: String,
        ciphertext: String,
    }

    fn validate(v: &VectorArgs) {
        let key_bytes = hex::decode(&v.key).unwrap();
        let nonce_bytes = hex::decode(&v.nonce).unwrap();
        let aad_bytes = hex::decode(&v.aad).unwrap();
        let plaintext_bytes = hex::decode(&v.plaintext).unwrap();
        let expected_ciphertext_bytes = hex::decode(&v.ciphertext).unwrap();
        let expected_tag_bytes = hex::decode(&v.tag).unwrap();

        let key_array: [u8; 24] = key_bytes.try_into().unwrap();
        let cipher = Aes192GcmSiv::new(&GenericArray::from(key_array));

        let payload = Payload {
            msg: plaintext_bytes.as_slice(),
            aad: aad_bytes.as_slice(),
        };
        let encrypted_bytes = cipher
            .encrypt(Nonce::from_slice(nonce_bytes.as_slice()), payload)
            .unwrap();
        let (ciphertext_bytes, tag_bytes) = encrypted_bytes.split_at(plaintext_bytes.len());
        assert_eq!(ciphertext_bytes, expected_ciphertext_bytes);
        assert_eq!(tag_bytes, expected_tag_bytes);
    }

    fn validate_vectors(filename: &Path) {
        let file = File::open(filename).expect("Failed to open file");
        let reader = io::BufReader::new(file);

        let mut vector: Option<VectorArgs> = None;

        for line in reader.lines() {
            let line = line.expect("Failed to read line");
            let segments: Vec<&str> = line.splitn(2, " = ").collect();

            match segments.first() {
                Some(&"COUNT") => {
                    if let Some(v) = vector.take() {
                        validate(&v);
                    }
                    vector = Some(VectorArgs {
                        nonce: String::new(),
                        key: String::new(),
                        aad: String::new(),
                        tag: String::new(),
                        plaintext: String::new(),
                        ciphertext: String::new(),
                    });
                }
                Some(&"IV") => {
                    if let Some(v) = &mut vector {
                        v.nonce = segments[1].parse().expect("Failed to parse IV");
                    }
                }
                Some(&"Key") => {
                    if let Some(v) = &mut vector {
                        v.key = segments[1].to_string();
                    }
                }
                Some(&"AAD") => {
                    if let Some(v) = &mut vector {
                        v.aad = segments[1].to_string();
                    }
                }
                Some(&"Tag") => {
                    if let Some(v) = &mut vector {
                        v.tag = segments[1].to_string();
                    }
                }
                Some(&"Plaintext") => {
                    if let Some(v) = &mut vector {
                        v.plaintext = segments[1].to_string();
                    }
                }
                Some(&"Ciphertext") => {
                    if let Some(v) = &mut vector {
                        v.ciphertext = segments[1].to_string();
                    }
                }
                _ => {}
            }
        }

        if let Some(v) = vector {
            validate(&v);
        }
    }

    fn main() {
        validate_vectors(Path::new(
            "vectors/cryptography_vectors/ciphers/AES/GCM-SIV/aes-192-gcm-siv.txt",
        ));
        println!("AES-192-GCM-SIV OK.")
    }

Download link: `main.rs`