Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

AesSiv

AES-SIV: Deterministic authenticated encryption - the same message always produces the same ciphertext.

What it does: Encrypts data and protects it from tampering. Unlike most encryption modes, AES-SIV is deterministic: encrypting the same message with the same key always produces the same ciphertext (unless you provide an optional nonce).

When to use AES-SIV:

When NOT to use AES-SIV:

Unique features:

Security properties:

AES-SIV has better security properties than AES-GCM-SIV, but is must slower.

How it works: Combines two keys - one for authentication (S2V) and one for encryption (CTR mode). The total key size is double the AES key size (256 bits for AES-128-SIV, 512 bits for AES-256-SIV).

Defined in RFC 5297.

aes_siv.AesSiv
fn AesSiv(comptime Aes: anytype) type

File

lib/std/crypto/aes_siv.zig:47

Code

fn AesSiv(comptime Aes: anytype) type {
    debug.assert(Aes.block.block_length == 16);

    return struct {
        pub const tag_length = 16;
        pub const key_length = Aes.key_bits / 8 * 2; // SIV uses 2x key size

        const CmacImpl = Cmac(Aes);

        /// S2V (String to Vector) - RFC 5297 Section 2.4
        /// Derives a synthetic IV from the key and input strings using CMAC.
        /// This function implements a cryptographic pseudo-random function that maps
        /// a variable-length vector of strings to a fixed 128-bit output.
        fn s2v(iv: *[16]u8, key: [Aes.key_bits / 8]u8, strings: []const []const u8) void {
            assert(strings.len > 0);
            assert(strings.len <= 127); // S2V limitation

            var d: [16]u8 = undefined;

            // Special case: single empty string
            if (strings.len == 1 and strings[0].len == 0) {
                CmacImpl.create(&d, &[_]u8{}, &key);
                iv.* = d;
                return;
            }

            // Initialize with CMAC of zero block
            const zero_block: [16]u8 = @splat(0);
            CmacImpl.create(&d, &zero_block, &key);

            // Process all strings except the last one
            var i: usize = 0;
            while (i < strings.len - 1) : (i += 1) {
                d = dbl(d);
                var tmp: [16]u8 = undefined;
                CmacImpl.create(&tmp, strings[i], &key);
                for (&d, tmp) |*b, t| {
                    b.* ^= t;
                }
            }

            // Process the final string
            const sn = strings[strings.len - 1];
            if (sn.len >= 16) {
                // XOR d with the last 16 bytes of Sn,
                // and give the entire Sn to CMAC incrementally.
                var cmac = CmacImpl.init(&key);
                const prefix = sn.len - 16;
                cmac.update(sn[0..prefix]);

                var tail: [16]u8 = undefined;
                for (&tail, sn[prefix..][0..16], d) |*out, s, db| {
                    out.* = s ^ db;
                }
                cmac.update(&tail);

                cmac.final(iv);
            } else {
                // Pad and XOR
                d = dbl(d);
                var padded: [16]u8 = @splat(0);
                @memcpy(padded[0..sn.len], sn);
                padded[sn.len] = 0x80;
                for (&d, padded) |*b, p| {
                    b.* ^= p;
                }
                CmacImpl.create(iv, &d, &key);
            }
        }

        /// Double operation as defined in RFC 5297.
        /// Performs multiplication by x (i.e., left shift by 1) in GF(2^128).
        /// This is the same operation used in CMAC subkey generation.
        /// If the MSB is set, XORs with the polynomial 0x87 after shifting.
        fn dbl(d: [16]u8) [16]u8 {
            // Read as big-endian 128-bit integer
            const val = mem.readInt(u128, &d, .big);

            // Left shift by 1, and XOR with 0x87 if MSB was set
            const doubled = (val << 1) ^ (0x87 & -%(@as(u128, val >> 127)));

            // Write back as big-endian
            var result: [16]u8 = undefined;
            mem.writeInt(u128, &result, doubled, .big);
            return result;
        }

        /// Encrypt plaintext using AES-SIV
        /// `c`: Output buffer for ciphertext (same size as plaintext)
        /// `tag`: Output buffer for authentication tag (synthetic IV)
        /// `m`: Plaintext to encrypt
        /// `ad`: Optional associated data
        /// `nonce`: Optional nonce (if provided, will be added as last AD component)
        /// `key`: Combined key (2x AES key size)
        pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: ?[]const u8, nonce: ?[]const u8, key: [key_length]u8) void {
            debug.assert(c.len == m.len);

            // Split key into K1 (for S2V) and K2 (for CTR)
            const k1 = key[0 .. Aes.key_bits / 8];
            const k2 = key[Aes.key_bits / 8 ..];

            // Prepare strings for S2V: AD components followed by plaintext
            var strings_buf: [128][]const u8 = undefined;
            var strings_len: usize = 0;

            if (ad) |a| {
                strings_buf[strings_len] = a;
                strings_len += 1;
            }
            if (nonce) |n| {
                strings_buf[strings_len] = n;
                strings_len += 1;
            }
            strings_buf[strings_len] = m;
            strings_len += 1;

            // Compute synthetic IV using S2V
            s2v(tag, k1.*, strings_buf[0..strings_len]);

            // Clear the 31st and 63rd bits for use as CTR IV
            var ctr_iv = tag.*;
            ctr_iv[8] &= 0x7f;
            ctr_iv[12] &= 0x7f;

            // Encrypt plaintext using CTR mode
            const aes_ctx = Aes.initEnc(k2.*);
            modes.ctr(@TypeOf(aes_ctx), aes_ctx, c, m, ctr_iv, .big);
        }

        /// Decrypt ciphertext using AES-SIV
        /// `m`: Output buffer for decrypted plaintext
        /// `c`: Ciphertext to decrypt
        /// `tag`: Authentication tag (synthetic IV)
        /// `ad`: Optional associated data (must match encryption)
        /// `nonce`: Optional nonce (must match encryption)
        /// `key`: Combined key (2x AES key size)
        pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: ?[]const u8, nonce: ?[]const u8, key: [key_length]u8) AuthenticationError!void {
            assert(c.len == m.len);

            // Split key into K1 (for S2V) and K2 (for CTR)
            const k1 = key[0 .. Aes.key_bits / 8];
            const k2 = key[Aes.key_bits / 8 ..];

            // Clear the 31st and 63rd bits for use as CTR IV
            var ctr_iv = tag;
            ctr_iv[8] &= 0x7f;
            ctr_iv[12] &= 0x7f;

            // Decrypt ciphertext using CTR mode
            const aes_ctx = Aes.initEnc(k2.*);
            modes.ctr(@TypeOf(aes_ctx), aes_ctx, m, c, ctr_iv, .big);

            // Prepare strings for S2V: AD components followed by plaintext
            var strings_buf: [128][]const u8 = undefined;
            var strings_len: usize = 0;

            if (ad) |a| {
                strings_buf[strings_len] = a;
                strings_len += 1;
            }
            if (nonce) |n| {
                strings_buf[strings_len] = n;
                strings_len += 1;
            }
            strings_buf[strings_len] = m;
            strings_len += 1;

            // Verify synthetic IV using S2V
            var computed_tag: [tag_length]u8 = undefined;
            s2v(&computed_tag, k1.*, strings_buf[0..strings_len]);

            // Verify tag
            const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
            if (!verify) {
                crypto.secureZero(u8, &computed_tag);
                @memset(m, undefined);
                return error.AuthenticationFailed;
            }
        }

        /// Encrypts plaintext with multiple associated data components.
        /// This is the most general form of AES-SIV encryption that accepts
        /// a vector of up to 126 associated data strings as specified in RFC 5297.
        pub fn encryptWithAdVector(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const []const u8, key: [key_length]u8) void {
            debug.assert(c.len == m.len);
            debug.assert(ad.len <= 126); // AES-SIV supports at most 126 associated data components

            // Split key into K1 (for S2V) and K2 (for CTR)
            const k1 = key[0 .. Aes.key_bits / 8];
            const k2 = key[Aes.key_bits / 8 ..];

            // Prepare strings for S2V: AD components followed by plaintext
            var strings_buf: [128][]const u8 = undefined;
            var strings_len: usize = 0;

            for (ad) |a| {
                strings_buf[strings_len] = a;
                strings_len += 1;
            }
            strings_buf[strings_len] = m;
            strings_len += 1;

            // Compute synthetic IV using S2V
            s2v(tag, k1.*, strings_buf[0..strings_len]);

            // Clear the 31st and 63rd bits for use as CTR IV
            var ctr_iv = tag.*;
            ctr_iv[8] &= 0x7f;
            ctr_iv[12] &= 0x7f;

            // Encrypt plaintext using CTR mode
            const aes_ctx = Aes.initEnc(k2.*);
            modes.ctr(@TypeOf(aes_ctx), aes_ctx, c, m, ctr_iv, .big);
        }

        /// Decrypts ciphertext with multiple associated data components.
        /// This is the most general form of AES-SIV decryption that accepts
        /// a vector of up to 126 associated data strings as specified in RFC 5297.
        pub fn decryptWithAdVector(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const []const u8, key: [key_length]u8) AuthenticationError!void {
            assert(c.len == m.len);
            assert(ad.len <= 126); // AES-SIV supports at most 126 associated data components

            // Split key into K1 (for S2V) and K2 (for CTR)
            const k1 = key[0 .. Aes.key_bits / 8];
            const k2 = key[Aes.key_bits / 8 ..];

            // Clear the 31st and 63rd bits for use as CTR IV
            var ctr_iv = tag;
            ctr_iv[8] &= 0x7f;
            ctr_iv[12] &= 0x7f;

            // Decrypt ciphertext using CTR mode
            const aes_ctx = Aes.initEnc(k2.*);
            modes.ctr(@TypeOf(aes_ctx), aes_ctx, m, c, ctr_iv, .big);

            // Prepare strings for S2V: AD components followed by plaintext
            var strings_buf: [128][]const u8 = undefined;
            var strings_len: usize = 0;

            for (ad) |a| {
                strings_buf[strings_len] = a;
                strings_len += 1;
            }
            strings_buf[strings_len] = m;
            strings_len += 1;

            // Verify synthetic IV using S2V
            var computed_tag: [tag_length]u8 = undefined;
            s2v(&computed_tag, k1.*, strings_buf[0..strings_len]);

            // Verify tag
            const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
            if (!verify) {
                crypto.secureZero(u8, &computed_tag);
                @memset(m, undefined);
                return error.AuthenticationFailed;
            }
        }
    };
}