Refactor tests into a separate module.

This commit is contained in:
Milan Špinka
2025-02-01 17:19:19 +01:00
parent 17e28b8279
commit 0ae582b733
19 changed files with 1348 additions and 1169 deletions

View File

@ -15,10 +15,13 @@ pub fn build(b: *std.Build) void {
// set a preferred release mode, allowing the user to decide how to optimize. // set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
// Define a private module for importing source files in tests.
const src_module = b.createModule(.{
.root_source_file = .{ .cwd_relative = "src/root.zig" },
});
const lib = b.addStaticLibrary(.{ const lib = b.addStaticLibrary(.{
.name = "crypto", .name = "crypto",
// In this case the main source file is merely a path, however, in more
// complicated build scripts, this could be a generated file.
.root_source_file = b.path("src/root.zig"), .root_source_file = b.path("src/root.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
@ -29,63 +32,20 @@ pub fn build(b: *std.Build) void {
// running `zig build`). // running `zig build`).
b.installArtifact(lib); b.installArtifact(lib);
//const exe = b.addExecutable(.{
// .name = "crypto",
// .root_source_file = b.path("src/main.zig"),
// .target = target,
// .optimize = optimize,
//});
// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
//b.installArtifact(exe);
// This *creates* a Run step in the build graph, to be executed when another
// step is evaluated that depends on it. The next line below will establish
// such a dependency.
//const run_cmd = b.addRunArtifact(exe);
// By making the run step depend on the install step, it will be run from the
// installation directory rather than directly from within the cache directory.
// This is not necessary, however, if the application depends on other installed
// files, this ensures they will be present and in the expected location.
//run_cmd.step.dependOn(b.getInstallStep());
// This allows the user to pass arguments to the application in the build
// command itself, like this: `zig build run -- arg1 arg2 etc`
//if (b.args) |args| {
// run_cmd.addArgs(args);
//}
// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build run`
// This will evaluate the `run` step rather than the default, which is "install".
//const run_step = b.step("run", "Run the app");
//run_step.dependOn(&run_cmd.step);
// Creates a step for unit testing. This only builds the test executable // Creates a step for unit testing. This only builds the test executable
// but does not run it. // but does not run it.
const lib_unit_tests = b.addTest(.{ const lib_unit_tests = b.addTest(.{
.root_source_file = b.path("src/test.zig"), .root_source_file = b.path("test/index.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
lib_unit_tests.root_module.addImport("ziggy", src_module);
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
//const exe_unit_tests = b.addTest(.{
// .root_source_file = b.path("src/main.zig"),
// .target = target,
// .optimize = optimize,
//});
//const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
// Similar to creating the run step earlier, this exposes a `test` step to // Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request // the `zig build --help` menu, providing a way for the user to request
// running the unit tests. // running the unit tests.
const test_step = b.step("test", "Run unit tests"); const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step); test_step.dependOn(&run_lib_unit_tests.step);
//test_step.dependOn(&run_exe_unit_tests.step);
} }

View File

@ -1,93 +1,45 @@
const std = @import("std"); const std = @import("std");
const testing = std.testing; const testing = std.testing;
// ----------------------------------- AES CONSTANTS ----------------------------------- // const byte_operations = @import("../../utility/byte_operations.zig");
const word_to_bytes = byte_operations.word_to_bytes_be;
const bytes_to_word = byte_operations.bytes_to_word_be;
pub const AES_BLOCK_SIZE = 128 / 8; // ----------------------------------- PUBLIC CONSTANTS ----------------------------------- //
pub const Aes128Parameters = struct { pub const BLOCK_SIZE = 128 / 8;
pub const KEY_SIZE = 128 / 8;
pub const N_ROUNDS = 10;
};
pub const Aes192Parameters = struct {
pub const KEY_SIZE = 192 / 8;
pub const N_ROUNDS = 12;
};
pub const Aes256Parameters = struct {
pub const KEY_SIZE = 256 / 8;
pub const N_ROUNDS = 14;
};
const AES_SBOX = [_]u8{ pub const KEY_SIZE_128 = 128 / 8;
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, pub const KEY_SIZE_192 = 192 / 8;
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, pub const KEY_SIZE_256 = 256 / 8;
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
};
const AES_INV_SBOX = [_]u8{ // ----------------------------------- ENCRYPTION/DECRYPTION ----------------------------------- //
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
};
const AES_RCON = [_]u32{ pub fn encrypt_block(
0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000,
0x20000000, 0x40000000, 0x80000000, 0x1b000000, 0x36000000,
};
// ----------------------------------- ENCRYPTION/DECRYPTION ----------------------------------- //
pub fn aes_encrypt_block(
n_rounds: comptime_int, n_rounds: comptime_int,
block_in: *const [AES_BLOCK_SIZE]u8, block_in: *const [BLOCK_SIZE]u8,
block_out: *[AES_BLOCK_SIZE]u8, block_out: *[BLOCK_SIZE]u8,
expanded_key: *const [4 * (n_rounds + 1)]u32, expanded_key: *const [4 * (n_rounds + 1)]u32,
) void { ) void {
// Copy input buffer into state (we're treating the buffer as a column-first matrix). // Copy input buffer into state (we're treating the buffer as a column-first matrix).
var state: [AES_BLOCK_SIZE]u8 = undefined; var state: [BLOCK_SIZE]u8 = undefined;
@memcpy(state[0..], block_in); @memcpy(state[0..], block_in);
// Initial AddRoundKey. // Initial AddRoundKey.
aes_add_round_key(&state, expanded_key[0..4]); add_round_key(&state, expanded_key[0..4]);
// Nr - 1 identical rounds. // Nr - 1 identical rounds.
for (1..n_rounds) |round| { for (1..n_rounds) |round| {
aes_sub_bytes(&state); sub_bytes(&state);
aes_shift_rows(&state); shift_rows(&state);
aes_mix_columns(&state); mix_columns(&state);
aes_add_round_key(&state, @ptrCast(expanded_key[(4 * round)..(4 * round + 4)])); add_round_key(&state, @ptrCast(expanded_key[(4 * round)..(4 * round + 4)]));
} }
// Last round is without MixColumns. // Last round is without MixColumns.
aes_sub_bytes(&state); sub_bytes(&state);
aes_shift_rows(&state); shift_rows(&state);
aes_add_round_key(&state, @ptrCast(expanded_key[(4 * n_rounds)..(4 * n_rounds + 4)])); add_round_key(&state, @ptrCast(expanded_key[(4 * n_rounds)..(4 * n_rounds + 4)]));
// Write the result into the destination buffer. // Write the result into the destination buffer.
@memcpy(block_out, &state); @memcpy(block_out, &state);
@ -96,27 +48,32 @@ pub fn aes_encrypt_block(
@memset(state[0..], 0); @memset(state[0..], 0);
} }
pub fn aes_decrypt_block(n_rounds: comptime_int, block_in: *const [AES_BLOCK_SIZE]u8, block_out: *[AES_BLOCK_SIZE]u8, expanded_key: *const [4 * (n_rounds + 1)]u32) void { pub fn decrypt_block(
n_rounds: comptime_int,
block_in: *const [BLOCK_SIZE]u8,
block_out: *[BLOCK_SIZE]u8,
expanded_key: *const [4 * (n_rounds + 1)]u32,
) void {
// Copy input buffer into state (we're treating the buffer as a column-first matrix). // Copy input buffer into state (we're treating the buffer as a column-first matrix).
var state: [AES_BLOCK_SIZE]u8 = undefined; var state: [BLOCK_SIZE]u8 = undefined;
@memcpy(state[0..], block_in); @memcpy(state[0..], block_in);
// Reverse the AddRoundKey that was applied after the last encryption round. // Reverse the AddRoundKey that was applied after the last encryption round.
aes_add_round_key(&state, @ptrCast(expanded_key[(4 * n_rounds)..(4 * n_rounds + 4)])); add_round_key(&state, @ptrCast(expanded_key[(4 * n_rounds)..(4 * n_rounds + 4)]));
// Nr - 1 identical rounds. // Nr - 1 identical rounds.
for (1..n_rounds) |inv_round| { for (1..n_rounds) |inv_round| {
const round = n_rounds - inv_round; const round = n_rounds - inv_round;
aes_inv_shift_rows(&state); inv_shift_rows(&state);
aes_inv_sub_bytes(&state); inv_sub_bytes(&state);
aes_add_round_key(&state, @ptrCast(expanded_key[(4 * round)..(4 * round + 4)])); add_round_key(&state, @ptrCast(expanded_key[(4 * round)..(4 * round + 4)]));
aes_inv_mix_columns(&state); inv_mix_columns(&state);
} }
// Finish last round. // Finish last round.
aes_inv_shift_rows(&state); inv_shift_rows(&state);
aes_inv_sub_bytes(&state); inv_sub_bytes(&state);
aes_add_round_key(&state, expanded_key[0..4]); add_round_key(&state, expanded_key[0..4]);
// Write the result into the destination buffer. // Write the result into the destination buffer.
@memcpy(block_out, &state); @memcpy(block_out, &state);
@ -125,117 +82,87 @@ pub fn aes_decrypt_block(n_rounds: comptime_int, block_in: *const [AES_BLOCK_SIZ
@memset(state[0..], 0); @memset(state[0..], 0);
} }
pub fn aes_128_encrypt_block( pub fn aes128_encrypt_block(
key: *const [Aes128Parameters.KEY_SIZE]u8, key: *const [KEY_SIZE_128]u8,
block_in: *const [AES_BLOCK_SIZE]u8, block_in: *const [BLOCK_SIZE]u8,
block_out: *[AES_BLOCK_SIZE]u8, block_out: *[BLOCK_SIZE]u8,
) void { ) void {
// Prepare the subkeys for AddRoundKey. // Prepare the subkeys for AddRoundKey.
var expanded_key = aes_128_expand_key(key); var expanded_key = expand_key_128(key);
defer @memset(&expanded_key, 0); defer @memset(&expanded_key, 0);
// Call the generic encryption procedure. // Call the generic encryption procedure.
aes_encrypt_block( encrypt_block(N_ROUNDS_128, block_in, block_out, &expanded_key);
Aes128Parameters.N_ROUNDS,
block_in,
block_out,
&expanded_key,
);
} }
pub fn aes_128_decrypt_block( pub fn aes128_decrypt_block(
key: *const [Aes128Parameters.KEY_SIZE]u8, key: *const [KEY_SIZE_128]u8,
block_in: *const [AES_BLOCK_SIZE]u8, block_in: *const [BLOCK_SIZE]u8,
block_out: *[AES_BLOCK_SIZE]u8, block_out: *[BLOCK_SIZE]u8,
) void { ) void {
// Prepare the subkeys for AddRoundKey. // Prepare the subkeys for AddRoundKey.
var expanded_key = aes_128_expand_key(key); var expanded_key = expand_key_128(key);
defer @memset(&expanded_key, 0); defer @memset(&expanded_key, 0);
// Call the generic decryption procedure. // Call the generic decryption procedure.
aes_decrypt_block( decrypt_block(N_ROUNDS_128, block_in, block_out, &expanded_key);
Aes128Parameters.N_ROUNDS,
block_in,
block_out,
&expanded_key,
);
} }
pub fn aes_192_encrypt_block( pub fn aes192_encrypt_block(
key: *const [Aes192Parameters.KEY_SIZE]u8, key: *const [KEY_SIZE_192]u8,
block_in: *const [AES_BLOCK_SIZE]u8, block_in: *const [BLOCK_SIZE]u8,
block_out: *[AES_BLOCK_SIZE]u8, block_out: *[BLOCK_SIZE]u8,
) void { ) void {
// Prepare the subkeys for AddRoundKey. // Prepare the subkeys for AddRoundKey.
var expanded_key = aes_192_expand_key(key); var expanded_key = expand_key_192(key);
defer @memset(&expanded_key, 0); defer @memset(&expanded_key, 0);
// Call the generic encryption procedure. // Call the generic encryption procedure.
aes_encrypt_block( encrypt_block(N_ROUNDS_192, block_in, block_out, &expanded_key);
Aes192Parameters.N_ROUNDS,
block_in,
block_out,
&expanded_key,
);
} }
pub fn aes_192_decrypt_block( pub fn aes192_decrypt_block(
key: *const [Aes192Parameters.KEY_SIZE]u8, key: *const [KEY_SIZE_192]u8,
block_in: *const [AES_BLOCK_SIZE]u8, block_in: *const [BLOCK_SIZE]u8,
block_out: *[AES_BLOCK_SIZE]u8, block_out: *[BLOCK_SIZE]u8,
) void { ) void {
// Prepare the subkeys for AddRoundKey. // Prepare the subkeys for AddRoundKey.
var expanded_key = aes_192_expand_key(key); var expanded_key = expand_key_192(key);
defer @memset(&expanded_key, 0); defer @memset(&expanded_key, 0);
// Call the generic decryption procedure. // Call the generic decryption procedure.
aes_decrypt_block( decrypt_block(N_ROUNDS_192, block_in, block_out, &expanded_key);
Aes192Parameters.N_ROUNDS,
block_in,
block_out,
&expanded_key,
);
} }
pub fn aes_256_encrypt_block( pub fn aes256_encrypt_block(
key: *const [Aes256Parameters.KEY_SIZE]u8, key: *const [KEY_SIZE_256]u8,
block_in: *const [AES_BLOCK_SIZE]u8, block_in: *const [BLOCK_SIZE]u8,
block_out: *[AES_BLOCK_SIZE]u8, block_out: *[BLOCK_SIZE]u8,
) void { ) void {
// Prepare the subkeys for AddRoundKey. // Prepare the subkeys for AddRoundKey.
var expanded_key = aes_256_expand_key(key); var expanded_key = expand_key_256(key);
defer @memset(&expanded_key, 0); defer @memset(&expanded_key, 0);
// Call the generic encryption procedure. // Call the generic encryption procedure.
aes_encrypt_block( encrypt_block(N_ROUNDS_256, block_in, block_out, &expanded_key);
Aes256Parameters.N_ROUNDS,
block_in,
block_out,
&expanded_key,
);
} }
pub fn aes_256_decrypt_block( pub fn aes256_decrypt_block(
key: *const [Aes256Parameters.KEY_SIZE]u8, key: *const [KEY_SIZE_256]u8,
block_in: *const [AES_BLOCK_SIZE]u8, block_in: *const [BLOCK_SIZE]u8,
block_out: *[AES_BLOCK_SIZE]u8, block_out: *[BLOCK_SIZE]u8,
) void { ) void {
// Prepare the subkeys for AddRoundKey. // Prepare the subkeys for AddRoundKey.
var expanded_key = aes_256_expand_key(key); var expanded_key = expand_key_256(key);
defer @memset(&expanded_key, 0); defer @memset(&expanded_key, 0);
// Call the generic decryption procedure. // Call the generic decryption procedure.
aes_decrypt_block( decrypt_block(N_ROUNDS_256, block_in, block_out, &expanded_key);
Aes256Parameters.N_ROUNDS,
block_in,
block_out,
&expanded_key,
);
} }
// ----------------------------------- KEY EXPANSION ----------------------------------- // // ----------------------------------- KEY EXPANSION ----------------------------------- //
pub fn aes_expand_key( pub fn expand_key(
n_rounds: comptime_int, n_rounds: comptime_int,
n_key_words: comptime_int, n_key_words: comptime_int,
key: *const [n_key_words * 4]u8, key: *const [n_key_words * 4]u8,
@ -249,9 +176,9 @@ pub fn aes_expand_key(
while (i <= 4 * n_rounds + 3) : (i += 1) { while (i <= 4 * n_rounds + 3) : (i += 1) {
var temp = expanded_key[i - 1]; var temp = expanded_key[i - 1];
if (i % n_key_words == 0) { if (i % n_key_words == 0) {
temp = aes_sub_word(aes_rot_word(temp)) ^ AES_RCON[i / n_key_words - 1]; temp = sub_word(rot_word(temp)) ^ RCON[i / n_key_words - 1];
} else if (n_key_words > 6 and i % n_key_words == 4) { } else if (n_key_words > 6 and i % n_key_words == 4) {
temp = aes_sub_word(temp); temp = sub_word(temp);
} }
expanded_key[i] = expanded_key[i - n_key_words] ^ temp; expanded_key[i] = expanded_key[i - n_key_words] ^ temp;
} }
@ -259,48 +186,34 @@ pub fn aes_expand_key(
return expanded_key; return expanded_key;
} }
pub fn aes_128_expand_key(key: *const [Aes128Parameters.KEY_SIZE]u8) [4 * (Aes128Parameters.N_ROUNDS + 1)]u32 { pub fn expand_key_128(key: *const [KEY_SIZE_128]u8) [4 * (N_ROUNDS_128 + 1)]u32 {
return aes_expand_key( return expand_key(N_ROUNDS_128, KEY_SIZE_128 / 4, key);
Aes128Parameters.N_ROUNDS,
Aes128Parameters.KEY_SIZE / 4,
key,
);
} }
pub fn aes_192_expand_key(key: *const [Aes192Parameters.KEY_SIZE]u8) [4 * (Aes192Parameters.N_ROUNDS + 1)]u32 { pub fn expand_key_192(key: *const [KEY_SIZE_192]u8) [4 * (N_ROUNDS_192 + 1)]u32 {
return aes_expand_key( return expand_key(N_ROUNDS_192, KEY_SIZE_192 / 4, key);
Aes192Parameters.N_ROUNDS,
Aes192Parameters.KEY_SIZE / 4,
key,
);
} }
pub fn aes_256_expand_key(key: *const [Aes256Parameters.KEY_SIZE]u8) [4 * (Aes256Parameters.N_ROUNDS + 1)]u32 { pub fn expand_key_256(key: *const [KEY_SIZE_256]u8) [4 * (N_ROUNDS_256 + 1)]u32 {
return aes_expand_key( return expand_key(N_ROUNDS_256, KEY_SIZE_256 / 4, key);
Aes256Parameters.N_ROUNDS,
Aes256Parameters.KEY_SIZE / 4,
key,
);
} }
// ----------------------------------- AES OPERATIONS ----------------------------------- // // ----------------------------------- AES OPERATIONS ----------------------------------- //
fn aes_add_round_key(state: *[AES_BLOCK_SIZE]u8, subkey: *const [4]u32) void { pub fn add_round_key(state: *[BLOCK_SIZE]u8, subkey: *const [4]u32) void {
for (0..4) |wi| { for (0..4) |wi| {
const subkey_bytes = word_to_bytes(subkey[wi]); const subkey_bytes = word_to_bytes(subkey[wi]);
for (0..4) |bi| { for (0..4) |bi|
state[wi * 4 + bi] ^= subkey_bytes[bi]; state[wi * 4 + bi] ^= subkey_bytes[bi];
}
} }
} }
fn aes_sub_bytes(state: *[AES_BLOCK_SIZE]u8) void { pub fn sub_bytes(state: *[BLOCK_SIZE]u8) void {
for (0..state.len) |i| { for (0..state.len) |i|
state[i] = AES_SBOX[state[i]]; state[i] = SBOX[state[i]];
}
} }
fn aes_shift_rows(state: *[AES_BLOCK_SIZE]u8) void { pub fn shift_rows(state: *[BLOCK_SIZE]u8) void {
var tmp: u8 = undefined; var tmp: u8 = undefined;
// Note: Since we store the state matrix as an array of columns, // Note: Since we store the state matrix as an array of columns,
@ -329,7 +242,7 @@ fn aes_shift_rows(state: *[AES_BLOCK_SIZE]u8) void {
state[1 * 4 + 3] = tmp; state[1 * 4 + 3] = tmp;
} }
fn aes_mix_one_column(column: *[4]u8) void { fn mix_one_column(column: *[4]u8) void {
const c0 = column[0]; const c0 = column[0];
const c1 = column[1]; const c1 = column[1];
const c2 = column[2]; const c2 = column[2];
@ -341,19 +254,17 @@ fn aes_mix_one_column(column: *[4]u8) void {
column[3] = (xtime(c0) ^ c0) ^ c1 ^ c2 ^ xtime(c3); column[3] = (xtime(c0) ^ c0) ^ c1 ^ c2 ^ xtime(c3);
} }
fn aes_mix_columns(state: *[AES_BLOCK_SIZE]u8) void { pub fn mix_columns(state: *[BLOCK_SIZE]u8) void {
for (0..4) |i| { for (0..4) |i|
aes_mix_one_column(@ptrCast(state[(4 * i)..(4 * i + 4)])); mix_one_column(@ptrCast(state[(4 * i)..(4 * i + 4)]));
}
} }
fn aes_inv_sub_bytes(state: *[AES_BLOCK_SIZE]u8) void { pub fn inv_sub_bytes(state: *[BLOCK_SIZE]u8) void {
for (0..state.len) |i| { for (0..state.len) |i|
state[i] = AES_INV_SBOX[state[i]]; state[i] = INV_SBOX[state[i]];
}
} }
fn aes_inv_shift_rows(state: *[AES_BLOCK_SIZE]u8) void { pub fn inv_shift_rows(state: *[BLOCK_SIZE]u8) void {
var tmp: u8 = undefined; var tmp: u8 = undefined;
// Note: Since we store the state matrix as an array of columns, // Note: Since we store the state matrix as an array of columns,
@ -382,7 +293,7 @@ fn aes_inv_shift_rows(state: *[AES_BLOCK_SIZE]u8) void {
state[2 * 4 + 3] = tmp; state[2 * 4 + 3] = tmp;
} }
fn aes_inv_mix_one_column(column: *[4]u8) void { fn inv_mix_one_column(column: *[4]u8) void {
const c0 = column[0]; const c0 = column[0];
const c1 = column[1]; const c1 = column[1];
const c2 = column[2]; const c2 = column[2];
@ -394,26 +305,24 @@ fn aes_inv_mix_one_column(column: *[4]u8) void {
column[3] = gfmult(0x0b, c0) ^ gfmult(0x0d, c1) ^ gfmult(0x09, c2) ^ gfmult(0x0e, c3); column[3] = gfmult(0x0b, c0) ^ gfmult(0x0d, c1) ^ gfmult(0x09, c2) ^ gfmult(0x0e, c3);
} }
fn aes_inv_mix_columns(state: *[AES_BLOCK_SIZE]u8) void { pub fn inv_mix_columns(state: *[BLOCK_SIZE]u8) void {
for (0..4) |i| { for (0..4) |i|
aes_inv_mix_one_column(@ptrCast(state[(4 * i)..(4 * i + 4)])); inv_mix_one_column(@ptrCast(state[(4 * i)..(4 * i + 4)]));
}
} }
fn aes_sub_word(word: u32) u32 { fn sub_word(word: u32) u32 {
var bytes = word_to_bytes(word); var bytes = word_to_bytes(word);
for (0..4) |i| { for (0..4) |i|
bytes[i] = AES_SBOX[bytes[i]]; bytes[i] = SBOX[bytes[i]];
}
return bytes_to_word(&bytes); return bytes_to_word(&bytes);
} }
fn aes_rot_word(word: u32) u32 { fn rot_word(word: u32) u32 {
const bytes = word_to_bytes(word); const bytes = word_to_bytes(word);
return bytes_to_word(&.{ bytes[1], bytes[2], bytes[3], bytes[0] }); return bytes_to_word(&.{ bytes[1], bytes[2], bytes[3], bytes[0] });
} }
// ----------------------------------- GALOIS FIELD HELPERS ----------------------------------- // // ----------------------------------- GALOIS FIELD HELPERS ----------------------------------- //
fn xtime(element: u8) u8 { fn xtime(element: u8) u8 {
return if (element & 0x80 != 0) return if (element & 0x80 != 0)
@ -444,334 +353,51 @@ fn gfmult(factor: comptime_int, element: u8) u8 {
unreachable; unreachable;
} }
// ----------------------------------- ENDIANNESS HELPERS ----------------------------------- // // ----------------------------------- CRYPTOGRAPHIC CONSTANTS ----------------------------------- //
fn word_to_bytes(word: u32) [4]u8 { pub const N_ROUNDS_128 = 10;
var bytes: [4]u8 = undefined; pub const N_ROUNDS_192 = 12;
std.mem.writeInt(u32, &bytes, word, .big); pub const N_ROUNDS_256 = 14;
return bytes;
}
fn bytes_to_word(bytes: *const [4]u8) u32 { pub const SBOX = [_]u8{
return std.mem.readInt(u32, bytes, .big); 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
} 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
};
// ----------------------------------- TEST VECTORS ----------------------------------- // pub const INV_SBOX = [_]u8{
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
};
test "AES-128 key expansion test vector from FIPS 197 A.1" { pub const RCON = [_]u32{
const key = [Aes128Parameters.KEY_SIZE]u8{ 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000,
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0x20000000, 0x40000000, 0x80000000, 0x1b000000, 0x36000000,
0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, };
};
const expected_expansion = [(Aes128Parameters.N_ROUNDS + 1) * 4]u32{
0x2b7e1516, 0x28aed2a6, 0xabf71588, 0x09cf4f3c,
0xa0fafe17, 0x88542cb1, 0x23a33939, 0x2a6c7605,
0xf2c295f2, 0x7a96b943, 0x5935807a, 0x7359f67f,
0x3d80477d, 0x4716fe3e, 0x1e237e44, 0x6d7a883b,
0xef44a541, 0xa8525b7f, 0xb671253b, 0xdb0bad00,
0xd4d1c6f8, 0x7c839d87, 0xcaf2b8bc, 0x11f915bc,
0x6d88a37a, 0x110b3efd, 0xdbf98641, 0xca0093fd,
0x4e54f70e, 0x5f5fc9f3, 0x84a64fb2, 0x4ea6dc4f,
0xead27321, 0xb58dbad2, 0x312bf560, 0x7f8d292f,
0xac7766f3, 0x19fadc21, 0x28d12941, 0x575c006e,
0xd014f9a8, 0xc9ee2589, 0xe13f0cc8, 0xb6630ca6,
};
try testing.expectEqualSlices(u32, &expected_expansion, &aes_128_expand_key(&key));
}
test "AES-192 key expansion test vector from FIPS 197 A.1" {
const key = [Aes192Parameters.KEY_SIZE]u8{
0x8e, 0x73, 0xb0, 0xf7, 0xda, 0x0e, 0x64, 0x52,
0xc8, 0x10, 0xf3, 0x2b, 0x80, 0x90, 0x79, 0xe5,
0x62, 0xf8, 0xea, 0xd2, 0x52, 0x2c, 0x6b, 0x7b,
};
const expected_expansion = [(Aes192Parameters.N_ROUNDS + 1) * 4]u32{
0x8e73b0f7, 0xda0e6452, 0xc810f32b, 0x809079e5,
0x62f8ead2, 0x522c6b7b, 0xfe0c91f7, 0x2402f5a5,
0xec12068e, 0x6c827f6b, 0x0e7a95b9, 0x5c56fec2,
0x4db7b4bd, 0x69b54118, 0x85a74796, 0xe92538fd,
0xe75fad44, 0xbb095386, 0x485af057, 0x21efb14f,
0xa448f6d9, 0x4d6dce24, 0xaa326360, 0x113b30e6,
0xa25e7ed5, 0x83b1cf9a, 0x27f93943, 0x6a94f767,
0xc0a69407, 0xd19da4e1, 0xec1786eb, 0x6fa64971,
0x485f7032, 0x22cb8755, 0xe26d1352, 0x33f0b7b3,
0x40beeb28, 0x2f18a259, 0x6747d26b, 0x458c553e,
0xa7e1466c, 0x9411f1df, 0x821f750a, 0xad07d753,
0xca400538, 0x8fcc5006, 0x282d166a, 0xbc3ce7b5,
0xe98ba06f, 0x448c773c, 0x8ecc7204, 0x01002202,
};
try testing.expectEqualSlices(u32, &expected_expansion, &aes_192_expand_key(&key));
}
test "AES-256 key expansion test vector from FIPS 197 A.1" {
const key = [Aes256Parameters.KEY_SIZE]u8{
0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe,
0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7,
0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4,
};
const expected_expansion = [(Aes256Parameters.N_ROUNDS + 1) * 4]u32{
0x603deb10, 0x15ca71be, 0x2b73aef0, 0x857d7781,
0x1f352c07, 0x3b6108d7, 0x2d9810a3, 0x0914dff4,
0x9ba35411, 0x8e6925af, 0xa51a8b5f, 0x2067fcde,
0xa8b09c1a, 0x93d194cd, 0xbe49846e, 0xb75d5b9a,
0xd59aecb8, 0x5bf3c917, 0xfee94248, 0xde8ebe96,
0xb5a9328a, 0x2678a647, 0x98312229, 0x2f6c79b3,
0x812c81ad, 0xdadf48ba, 0x24360af2, 0xfab8b464,
0x98c5bfc9, 0xbebd198e, 0x268c3ba7, 0x09e04214,
0x68007bac, 0xb2df3316, 0x96e939e4, 0x6c518d80,
0xc814e204, 0x76a9fb8a, 0x5025c02d, 0x59c58239,
0xde136967, 0x6ccc5a71, 0xfa256395, 0x9674ee15,
0x5886ca5d, 0x2e2f31d7, 0x7e0af1fa, 0x27cf73c3,
0x749c47ab, 0x18501dda, 0xe2757e4f, 0x7401905a,
0xcafaaae3, 0xe4d59b34, 0x9adf6ace, 0xbd10190d,
0xfe4890d1, 0xe6188d0b, 0x046df344, 0x706c631e,
};
try testing.expectEqualSlices(u32, &expected_expansion, &aes_256_expand_key(&key));
}
// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
test "AES-128 ECB encryption" {
const key = [Aes128Parameters.KEY_SIZE]u8{
0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C,
};
const plaintext = [_][AES_BLOCK_SIZE]u8{
.{ 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A },
.{ 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51 },
.{ 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF },
.{ 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 },
};
const ciphertext = [_][AES_BLOCK_SIZE]u8{
.{ 0x3A, 0xD7, 0x7B, 0xB4, 0x0D, 0x7A, 0x36, 0x60, 0xA8, 0x9E, 0xCA, 0xF3, 0x24, 0x66, 0xEF, 0x97 },
.{ 0xF5, 0xD3, 0xD5, 0x85, 0x03, 0xB9, 0x69, 0x9D, 0xE7, 0x85, 0x89, 0x5A, 0x96, 0xFD, 0xBA, 0xAF },
.{ 0x43, 0xB1, 0xCD, 0x7F, 0x59, 0x8E, 0xCE, 0x23, 0x88, 0x1B, 0x00, 0xE3, 0xED, 0x03, 0x06, 0x88 },
.{ 0x7B, 0x0C, 0x78, 0x5E, 0x27, 0xE8, 0xAD, 0x3F, 0x82, 0x23, 0x20, 0x71, 0x04, 0x72, 0x5D, 0xD4 },
};
var buffer: [AES_BLOCK_SIZE]u8 = undefined;
for (plaintext, 0..) |pt, i| {
aes_128_encrypt_block(&key, &pt, &buffer);
try testing.expectEqualSlices(u8, &ciphertext[i], &buffer);
}
}
// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
test "AES-128 ECB decryption" {
const key = [Aes128Parameters.KEY_SIZE]u8{
0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C,
};
const plaintext = [_][AES_BLOCK_SIZE]u8{
.{ 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A },
.{ 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51 },
.{ 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF },
.{ 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 },
};
const ciphertext = [_][AES_BLOCK_SIZE]u8{
.{ 0x3A, 0xD7, 0x7B, 0xB4, 0x0D, 0x7A, 0x36, 0x60, 0xA8, 0x9E, 0xCA, 0xF3, 0x24, 0x66, 0xEF, 0x97 },
.{ 0xF5, 0xD3, 0xD5, 0x85, 0x03, 0xB9, 0x69, 0x9D, 0xE7, 0x85, 0x89, 0x5A, 0x96, 0xFD, 0xBA, 0xAF },
.{ 0x43, 0xB1, 0xCD, 0x7F, 0x59, 0x8E, 0xCE, 0x23, 0x88, 0x1B, 0x00, 0xE3, 0xED, 0x03, 0x06, 0x88 },
.{ 0x7B, 0x0C, 0x78, 0x5E, 0x27, 0xE8, 0xAD, 0x3F, 0x82, 0x23, 0x20, 0x71, 0x04, 0x72, 0x5D, 0xD4 },
};
var buffer: [AES_BLOCK_SIZE]u8 = undefined;
for (ciphertext, 0..) |ct, i| {
aes_128_decrypt_block(&key, &ct, &buffer);
try testing.expectEqualSlices(u8, &plaintext[i], &buffer);
}
}
// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
test "AES-192 ECB encryption" {
const key = [Aes192Parameters.KEY_SIZE]u8{
0x8E, 0x73, 0xB0, 0xF7, 0xDA, 0x0E, 0x64, 0x52,
0xC8, 0x10, 0xF3, 0x2B, 0x80, 0x90, 0x79, 0xE5,
0x62, 0xF8, 0xEA, 0xD2, 0x52, 0x2C, 0x6B, 0x7B,
};
const plaintext = [_][AES_BLOCK_SIZE]u8{
.{ 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A },
.{ 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51 },
.{ 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF },
.{ 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 },
};
const ciphertext = [_][AES_BLOCK_SIZE]u8{
.{ 0xBD, 0x33, 0x4F, 0x1D, 0x6E, 0x45, 0xF2, 0x5F, 0xF7, 0x12, 0xA2, 0x14, 0x57, 0x1F, 0xA5, 0xCC },
.{ 0x97, 0x41, 0x04, 0x84, 0x6D, 0x0A, 0xD3, 0xAD, 0x77, 0x34, 0xEC, 0xB3, 0xEC, 0xEE, 0x4E, 0xEF },
.{ 0xEF, 0x7A, 0xFD, 0x22, 0x70, 0xE2, 0xE6, 0x0A, 0xDC, 0xE0, 0xBA, 0x2F, 0xAC, 0xE6, 0x44, 0x4E },
.{ 0x9A, 0x4B, 0x41, 0xBA, 0x73, 0x8D, 0x6C, 0x72, 0xFB, 0x16, 0x69, 0x16, 0x03, 0xC1, 0x8E, 0x0E },
};
var buffer: [AES_BLOCK_SIZE]u8 = undefined;
for (plaintext, 0..) |pt, i| {
aes_192_encrypt_block(&key, &pt, &buffer);
try testing.expectEqualSlices(u8, &ciphertext[i], &buffer);
}
}
// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
test "AES-192 ECB decryption" {
const key = [Aes192Parameters.KEY_SIZE]u8{
0x8E, 0x73, 0xB0, 0xF7, 0xDA, 0x0E, 0x64, 0x52,
0xC8, 0x10, 0xF3, 0x2B, 0x80, 0x90, 0x79, 0xE5,
0x62, 0xF8, 0xEA, 0xD2, 0x52, 0x2C, 0x6B, 0x7B,
};
const plaintext = [_][AES_BLOCK_SIZE]u8{
.{ 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A },
.{ 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51 },
.{ 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF },
.{ 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 },
};
const ciphertext = [_][AES_BLOCK_SIZE]u8{
.{ 0xBD, 0x33, 0x4F, 0x1D, 0x6E, 0x45, 0xF2, 0x5F, 0xF7, 0x12, 0xA2, 0x14, 0x57, 0x1F, 0xA5, 0xCC },
.{ 0x97, 0x41, 0x04, 0x84, 0x6D, 0x0A, 0xD3, 0xAD, 0x77, 0x34, 0xEC, 0xB3, 0xEC, 0xEE, 0x4E, 0xEF },
.{ 0xEF, 0x7A, 0xFD, 0x22, 0x70, 0xE2, 0xE6, 0x0A, 0xDC, 0xE0, 0xBA, 0x2F, 0xAC, 0xE6, 0x44, 0x4E },
.{ 0x9A, 0x4B, 0x41, 0xBA, 0x73, 0x8D, 0x6C, 0x72, 0xFB, 0x16, 0x69, 0x16, 0x03, 0xC1, 0x8E, 0x0E },
};
var buffer: [AES_BLOCK_SIZE]u8 = undefined;
for (ciphertext, 0..) |ct, i| {
aes_192_decrypt_block(&key, &ct, &buffer);
try testing.expectEqualSlices(u8, &plaintext[i], &buffer);
}
}
// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
test "AES-256 ECB encryption" {
const key = [Aes256Parameters.KEY_SIZE]u8{
0x60, 0x3D, 0xEB, 0x10, 0x15, 0xCA, 0x71, 0xBE, 0x2B, 0x73, 0xAE, 0xF0, 0x85, 0x7D, 0x77, 0x81,
0x1F, 0x35, 0x2C, 0x07, 0x3B, 0x61, 0x08, 0xD7, 0x2D, 0x98, 0x10, 0xA3, 0x09, 0x14, 0xDF, 0xF4,
};
const plaintext = [_][AES_BLOCK_SIZE]u8{
.{ 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A },
.{ 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51 },
.{ 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF },
.{ 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 },
};
const ciphertext = [_][AES_BLOCK_SIZE]u8{
.{ 0xF3, 0xEE, 0xD1, 0xBD, 0xB5, 0xD2, 0xA0, 0x3C, 0x06, 0x4B, 0x5A, 0x7E, 0x3D, 0xB1, 0x81, 0xF8 },
.{ 0x59, 0x1C, 0xCB, 0x10, 0xD4, 0x10, 0xED, 0x26, 0xDC, 0x5B, 0xA7, 0x4A, 0x31, 0x36, 0x28, 0x70 },
.{ 0xB6, 0xED, 0x21, 0xB9, 0x9C, 0xA6, 0xF4, 0xF9, 0xF1, 0x53, 0xE7, 0xB1, 0xBE, 0xAF, 0xED, 0x1D },
.{ 0x23, 0x30, 0x4B, 0x7A, 0x39, 0xF9, 0xF3, 0xFF, 0x06, 0x7D, 0x8D, 0x8F, 0x9E, 0x24, 0xEC, 0xC7 },
};
var buffer: [AES_BLOCK_SIZE]u8 = undefined;
for (plaintext, 0..) |pt, i| {
aes_256_encrypt_block(&key, &pt, &buffer);
try testing.expectEqualSlices(u8, &ciphertext[i], &buffer);
}
}
// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
test "AES-256 ECB decryption" {
const key = [Aes256Parameters.KEY_SIZE]u8{
0x60, 0x3D, 0xEB, 0x10, 0x15, 0xCA, 0x71, 0xBE, 0x2B, 0x73, 0xAE, 0xF0, 0x85, 0x7D, 0x77, 0x81,
0x1F, 0x35, 0x2C, 0x07, 0x3B, 0x61, 0x08, 0xD7, 0x2D, 0x98, 0x10, 0xA3, 0x09, 0x14, 0xDF, 0xF4,
};
const plaintext = [_][AES_BLOCK_SIZE]u8{
.{ 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A },
.{ 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51 },
.{ 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF },
.{ 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 },
};
const ciphertext = [_][AES_BLOCK_SIZE]u8{
.{ 0xF3, 0xEE, 0xD1, 0xBD, 0xB5, 0xD2, 0xA0, 0x3C, 0x06, 0x4B, 0x5A, 0x7E, 0x3D, 0xB1, 0x81, 0xF8 },
.{ 0x59, 0x1C, 0xCB, 0x10, 0xD4, 0x10, 0xED, 0x26, 0xDC, 0x5B, 0xA7, 0x4A, 0x31, 0x36, 0x28, 0x70 },
.{ 0xB6, 0xED, 0x21, 0xB9, 0x9C, 0xA6, 0xF4, 0xF9, 0xF1, 0x53, 0xE7, 0xB1, 0xBE, 0xAF, 0xED, 0x1D },
.{ 0x23, 0x30, 0x4B, 0x7A, 0x39, 0xF9, 0xF3, 0xFF, 0x06, 0x7D, 0x8D, 0x8F, 0x9E, 0x24, 0xEC, 0xC7 },
};
var buffer: [AES_BLOCK_SIZE]u8 = undefined;
for (ciphertext, 0..) |ct, i| {
aes_256_decrypt_block(&key, &ct, &buffer);
try testing.expectEqualSlices(u8, &plaintext[i], &buffer);
}
}
test "AES S-box inverse" {
for (0..256) |x| {
try testing.expectEqual(x, AES_INV_SBOX[AES_SBOX[x]]);
}
}
test "AES SubBytes" {
var state = [AES_BLOCK_SIZE]u8{
0x40, 0xBF, 0xAB, 0xF4, 0x06, 0xEE, 0x4D, 0x30,
0x42, 0xCA, 0x6B, 0x99, 0x7A, 0x5C, 0x58, 0x16,
};
const reference = [AES_BLOCK_SIZE]u8{
0x09, 0x08, 0x62, 0xBF, 0x6F, 0x28, 0xE3, 0x04,
0x2C, 0x74, 0x7F, 0xEE, 0xDA, 0x4A, 0x6A, 0x47,
};
aes_sub_bytes(&state);
try testing.expectEqualSlices(u8, &reference, &state);
}
test "AES ShiftRows" {
var state = [AES_BLOCK_SIZE]u8{
0x09, 0x08, 0x62, 0xBF, 0x6F, 0x28, 0xE3, 0x04,
0x2C, 0x74, 0x7F, 0xEE, 0xDA, 0x4A, 0x6A, 0x47,
};
const reference = [AES_BLOCK_SIZE]u8{
0x09, 0x28, 0x7F, 0x47, 0x6F, 0x74, 0x6A, 0xBF,
0x2C, 0x4A, 0x62, 0x04, 0xDA, 0x08, 0xE3, 0xEE,
};
aes_shift_rows(&state);
try testing.expectEqualSlices(u8, &reference, &state);
}
test "AES MixColumns" {
var state = [AES_BLOCK_SIZE]u8{
0x09, 0x28, 0x7F, 0x47, 0x6F, 0x74, 0x6A, 0xBF,
0x2C, 0x4A, 0x62, 0x04, 0xDA, 0x08, 0xE3, 0xEE,
};
const reference = [AES_BLOCK_SIZE]u8{
0x52, 0x9F, 0x16, 0xC2, 0x97, 0x86, 0x15, 0xCA,
0xE0, 0x1A, 0xAE, 0x54, 0xBA, 0x1A, 0x26, 0x59,
};
aes_mix_columns(&state);
try testing.expectEqualSlices(u8, &reference, &state);
}
test "AES InvSubBytes" {
const reference = [AES_BLOCK_SIZE]u8{
0x40, 0xBF, 0xAB, 0xF4, 0x06, 0xEE, 0x4D, 0x30,
0x42, 0xCA, 0x6B, 0x99, 0x7A, 0x5C, 0x58, 0x16,
};
var state = [AES_BLOCK_SIZE]u8{
0x09, 0x08, 0x62, 0xBF, 0x6F, 0x28, 0xE3, 0x04,
0x2C, 0x74, 0x7F, 0xEE, 0xDA, 0x4A, 0x6A, 0x47,
};
aes_inv_sub_bytes(&state);
try testing.expectEqualSlices(u8, &reference, &state);
}
test "AES InvShiftRows" {
const reference = [AES_BLOCK_SIZE]u8{
0x09, 0x08, 0x62, 0xBF, 0x6F, 0x28, 0xE3, 0x04,
0x2C, 0x74, 0x7F, 0xEE, 0xDA, 0x4A, 0x6A, 0x47,
};
var state = [AES_BLOCK_SIZE]u8{
0x09, 0x28, 0x7F, 0x47, 0x6F, 0x74, 0x6A, 0xBF,
0x2C, 0x4A, 0x62, 0x04, 0xDA, 0x08, 0xE3, 0xEE,
};
aes_inv_shift_rows(&state);
try testing.expectEqualSlices(u8, &reference, &state);
}
test "AES InvMixColumns" {
const reference = [AES_BLOCK_SIZE]u8{
0x09, 0x28, 0x7F, 0x47, 0x6F, 0x74, 0x6A, 0xBF,
0x2C, 0x4A, 0x62, 0x04, 0xDA, 0x08, 0xE3, 0xEE,
};
var state = [AES_BLOCK_SIZE]u8{
0x52, 0x9F, 0x16, 0xC2, 0x97, 0x86, 0x15, 0xCA,
0xE0, 0x1A, 0xAE, 0x54, 0xBA, 0x1A, 0x26, 0x59,
};
aes_inv_mix_columns(&state);
try testing.expectEqualSlices(u8, &reference, &state);
}

View File

@ -186,7 +186,3 @@ fn rotate_halves_left(cd: *[DES_TRUE_KEY_SIZE]u8, positions: comptime_int) void
// TODO // TODO
_ = .{ cd, positions }; _ = .{ cd, positions };
} }
// ----------------------------------- TEST VECTORS ----------------------------------- //
// TODO

View File

@ -5,4 +5,5 @@ pub const padding = @import("padding.zig");
pub const operation_mode = struct { pub const operation_mode = struct {
pub const gcm = @import("mode_gcm.zig"); pub const gcm = @import("mode_gcm.zig");
pub const cbc = @import("mode_cbc.zig");
}; };

View File

@ -0,0 +1,167 @@
const std = @import("std");
const testing = std.testing;
const padding = @import("padding.zig");
const CryptoError = error{
PlaintextExceedsBlockSize,
BufferSizeMismatch,
};
pub fn cipher_fn_t(comptime block_size: u8, comptime key_size: usize) type {
return fn (
key: *const [key_size]u8,
block_in: *const [block_size]u8,
block_out: *[block_size]u8,
) void;
}
pub fn CbcPkcs7Ctx(
comptime block_size: u8,
comptime key_size: usize,
cipher_fn: cipher_fn_t(block_size, key_size),
comptime decrypt: bool,
) type {
return struct {
const BLOCK_SIZE = block_size;
const KEY_SIZE = key_size;
const CIPHER_FN = cipher_fn;
const DECRYPT = decrypt;
iv: [block_size]u8,
key: [key_size]u8,
};
}
pub fn get_padded_length(comptime block_size: u8, plaintext_length: usize) usize {
return (plaintext_length / block_size + 1) * block_size;
}
pub fn get_padded_buffer_length(comptime block_size: u8, plaintext: []const u8) usize {
return get_padded_length(block_size, plaintext.len);
}
pub fn cbc_pkcs7_encrypt_new(
comptime block_size: u8,
comptime key_size: usize,
cipher_fn: cipher_fn_t(block_size, key_size),
key: *const [key_size]u8,
iv: *const [block_size]u8,
) CbcPkcs7Ctx(block_size, key_size, cipher_fn, false) {
return CbcPkcs7Ctx(block_size, key_size, cipher_fn, false){
.iv = iv.*,
.key = key.*,
};
}
pub fn cbc_pkcs7_encrypt_block(
comptime block_size: u8,
comptime key_size: usize,
cipher_fn: cipher_fn_t(block_size, key_size),
ctx: *CbcPkcs7Ctx(block_size, key_size, cipher_fn, false),
plaintext: *const [block_size]u8,
ciphertext: *[block_size]u8,
) void {
var tmp: [block_size]u8 = plaintext.*;
defer @memset(&tmp, 0);
xor(block_size, tmp[0..], ctx.iv[0..], tmp[0..]);
cipher_fn(&ctx.key, &tmp, &tmp);
@memcpy(ctx.iv[0..], tmp[0..]);
@memcpy(ciphertext[0..], tmp[0..]);
}
pub fn cbc_pkcs7_encrypt_final(
comptime block_size: u8,
comptime key_size: usize,
cipher_fn: cipher_fn_t(block_size, key_size),
ctx: *CbcPkcs7Ctx(block_size, key_size, cipher_fn, false),
plaintext: []const u8,
ciphertext: *[block_size]u8,
) !void {
if (plaintext.len >= block_size)
return CryptoError.PlaintextExceedsBlockSize;
var last_block: [block_size]u8 = undefined;
@memcpy(last_block[0..plaintext.len], plaintext[0..]);
defer @memset(&last_block, 0);
padding.pkcs7_pad(block_size, &last_block, plaintext.len);
return cbc_pkcs7_encrypt_block(block_size, key_size, cipher_fn, ctx, &last_block, ciphertext);
}
pub fn cbc_pkcs7_encrypt_destroy(
comptime block_size: u8,
comptime key_size: usize,
cipher_fn: cipher_fn_t(block_size, key_size),
ctx: *CbcPkcs7Ctx(block_size, key_size, cipher_fn, false),
) void {
const ctx_size = @sizeOf(@TypeOf(ctx.*));
const CtxByteSlice = *[ctx_size]u8;
@memset(@as(CtxByteSlice, @ptrCast(ctx)), 0);
}
pub fn cbc_pkcs7_decrypt_new(
comptime block_size: u8,
comptime key_size: usize,
cipher_fn: cipher_fn_t(block_size, key_size),
key: *const [key_size]u8,
iv: *const [block_size]u8,
) CbcPkcs7Ctx(block_size, key_size, cipher_fn, true) {
return CbcPkcs7Ctx(block_size, key_size, cipher_fn, true){
.iv = iv.*,
.key = key.*,
};
}
pub fn cbc_pkcs7_decrypt_block(
comptime block_size: u8,
comptime key_size: usize,
cipher_fn: cipher_fn_t(block_size, key_size),
ctx: *CbcPkcs7Ctx(block_size, key_size, cipher_fn, true),
ciphertext: *const [block_size]u8,
plaintext: *[block_size]u8,
) void {
var tmp: [block_size]u8 = undefined;
defer @memset(&tmp, 0);
cipher_fn(&ctx.key, ciphertext, &tmp);
xor(block_size, tmp[0..], ctx.iv[0..], tmp[0..]);
@memcpy(ctx.iv[0..], ciphertext[0..]);
@memcpy(plaintext[0..], tmp[0..]);
}
pub fn cbc_pkcs7_decrypt_final(
comptime block_size: u8,
comptime key_size: usize,
cipher_fn: cipher_fn_t(block_size, key_size),
ctx: *CbcPkcs7Ctx(block_size, key_size, cipher_fn, true),
ciphertext: []const u8,
plaintext: []u8,
) !u8 {
if (ciphertext.len != block_size or plaintext.len != block_size)
return CryptoError.BufferSizeMismatch;
cbc_pkcs7_decrypt_block(block_size, key_size, cipher_fn, ctx, @ptrCast(ciphertext), @ptrCast(plaintext));
return padding.pkcs7_unpad(block_size, @ptrCast(plaintext));
}
pub fn cbc_pkcs7_decrypt_destroy(
comptime block_size: u8,
comptime key_size: usize,
cipher_fn: cipher_fn_t(block_size, key_size),
ctx: *CbcPkcs7Ctx(block_size, key_size, cipher_fn, true),
) void {
const ctx_size = @sizeOf(@TypeOf(ctx.*));
const CtxByteSlice = *[ctx_size]u8;
@memset(@as(CtxByteSlice, @ptrCast(ctx)), 0);
}
fn xor(comptime block_size: u8, x: *const [block_size]u8, y: *const [block_size]u8, out: *[block_size]u8) void {
for (0..block_size) |i|
out[i] = x[i] ^ y[i];
}

View File

@ -18,7 +18,7 @@ pub fn pkcs7_pad(
block: *[block_size]u8, block: *[block_size]u8,
plaintext_length: usize, plaintext_length: usize,
) void { ) void {
const plaintext_block_residue_length: u8 = plaintext_length % block_size; const plaintext_block_residue_length: u8 = @intCast(plaintext_length % block_size);
const padding_val: u8 = block_size - plaintext_block_residue_length; const padding_val: u8 = block_size - plaintext_block_residue_length;
@memset(block[plaintext_block_residue_length..], padding_val); @memset(block[plaintext_block_residue_length..], padding_val);
} }
@ -39,11 +39,3 @@ pub fn pkcs7_unpad(
return plaintext_residue_length; return plaintext_residue_length;
} }
test "PKCS #7 padding" {
// TODO
}
test "PKCS #7 unpadding" {
// TODO
}

View File

@ -2,23 +2,22 @@ const std = @import("std");
const testing = std.testing; const testing = std.testing;
const sha = @import("./sha_core.zig"); const sha = @import("./sha_core.zig");
pub const MessageLengthLimitExceeded = sha.MessageLengthLimitExceeded;
pub const SHA_1_DIGEST_LENGTH = 160 / 8; pub const DIGEST_LENGTH = 160 / 8;
pub const SHA_1_MESSAGE_BITS_LIMIT = 1 << 64; pub const MESSAGE_BITS_LIMIT = 1 << 64;
pub const Sha1Ctx = struct { pub const Sha1Ctx = struct {
pub const BLOCK_SIZE = 512 / 8; pub const BLOCK_SIZE = 512 / 8;
pub const MESSAGE_SCHEDULE_WORDS = 80; pub const MESSAGE_SCHEDULE_WORDS = 80;
pub const WordType = u32; pub const WordType = u32;
hash: [SHA_1_DIGEST_LENGTH / @sizeOf(WordType)]WordType, hash: [DIGEST_LENGTH / @sizeOf(WordType)]WordType,
message_buffer: [BLOCK_SIZE]u8, message_buffer: [BLOCK_SIZE]u8,
message_length: u64, message_length: u64,
}; };
const SHA_1_IV = [_]u32{ const SHA_1_IV = [_]u32{ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 };
0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0,
};
pub fn sha1_new() Sha1Ctx { pub fn sha1_new() Sha1Ctx {
var ctx = Sha1Ctx{ var ctx = Sha1Ctx{
@ -33,19 +32,19 @@ pub fn sha1_new() Sha1Ctx {
pub fn sha1_update(ctx: *Sha1Ctx, message: []const u8) !void { pub fn sha1_update(ctx: *Sha1Ctx, message: []const u8) !void {
return sha.generic_update( return sha.generic_update(
Sha1Ctx, Sha1Ctx,
SHA_1_MESSAGE_BITS_LIMIT, MESSAGE_BITS_LIMIT,
&sha1_compress_block, &sha1_compress_block,
ctx, ctx,
message, message,
); );
} }
pub fn sha1_final(ctx: *Sha1Ctx, out: *[SHA_1_DIGEST_LENGTH]u8) void { pub fn sha1_final(ctx: *Sha1Ctx, out: *[DIGEST_LENGTH]u8) void {
return sha.generic_final( return sha.generic_final(
Sha1Ctx, Sha1Ctx,
Sha1Ctx.WordType, Sha1Ctx.WordType,
u64, u64,
SHA_1_DIGEST_LENGTH, DIGEST_LENGTH,
sha1_compress_block, sha1_compress_block,
ctx, ctx,
out, out,
@ -110,52 +109,3 @@ inline fn sha1_k(t: comptime_int) u32 {
else => @compileError("SHA-1 `k` constant requested with invalid value of `t`."), else => @compileError("SHA-1 `k` constant requested with invalid value of `t`."),
}; };
} }
// https://www.di-mgt.com.au/sha_testvectors.html
test "SHA-1 basic test" {
try sha.run_hash_precomputed_tests(
Sha1Ctx,
SHA_1_DIGEST_LENGTH,
sha1_new,
sha1_update,
sha1_final,
&.{
.{ .message = "", .hash = "da39a3ee5e6b4b0d3255bfef95601890afd80709" },
.{ .message = "abc", .hash = "a9993e364706816aba3e25717850c26c9cd0d89d" },
.{
.message = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
.hash = "84983e441c3bd26ebaae4aa1f95129e5e54670f1",
},
.{
.message = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
.hash = "a49b2446a02c645bf419f995b67091253a04a259",
},
},
);
}
test "SHA-1 padding test" {
// Here we test every possible length of the message in the last block
// to make sure that the padding is correct in every single case.
// The following are the hashes of the ASCII strings '', 'a', 'aa', etc.
// up until 63 (= [SHA-1 block size in bits] / 8 - 1) concatenated 'a's.
const reference = [64]*const [2 * SHA_1_DIGEST_LENGTH]u8{ "da39a3ee5e6b4b0d3255bfef95601890afd80709", "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8", "e0c9035898dd52fc65c41454cec9c4d2611bfb37", "7e240de74fb1ed08fa08d38063f6a6a91462a815", "70c881d4a26984ddce795f6f71817c9cf4480e79", "df51e37c269aa94d38f93e537bf6e2020b21406c", "f7a9e24777ec23212c54d7a350bc5bea5477fdbb", "e93b4e3c464ffd51732fbd6ded717e9efda28aad", "b480c074d6b75947c02681f31c90c668c46bf6b8", "2882f38e575101ba615f725af5e59bf2333a9a68", "3495ff69d34671d1e15b33a63c1379fdedd3a32a", "755c001f4ae3c8843e5a50dd6aa2fa23893dd3ad", "384fcd160ab3b33174ea279ad26052eee191508a", "897b99631295d204db13e863b296a09e70ab1d65", "128c484ff69fcdc1f82cd3781595cac5185e688f", "7e13c003a8256cd421055563c5da6571d50713c9", "3499c60eea227453c779de50fc84e217e9a53a18", "321a618ba6830de900738b0814d0c9f28ff2fece", "0478095c8ece0bbc11f94663ac2c4f10b29666de", "1335bfa62671b0015c6e20766c07035868edb8f4", "38666b8ba500faa5c2406f4575d42a92379844c2", "035a4ee5d60816878caec161d6cb8e00e9cc539b", "8c2a4e5c8f210b6aaa6c95e1c8e21351959f4541", "85e3737bb8ab36e2866501e517c46fffc085313e", "b1c76aec7674865d5346b3b0d1cb2c223c53e73e", "44f4647e1542a79d7d68ceb7f75d1dbf77fdebfc", "de8280c3a1c7db377f1ec7107c7fb62d374cc09c", "52a00b8461593ce33409d7c5d0411699cbf9cda3", "06587751ce11a8703abc64cab55b0b96d88341aa", "498a75f314a645671bc79a118df385d0d9948484", "cd762363c1c11ecb48611583520bba111f0034d4", "65cf771ad2cc0b1e8f89361e3b7bec2365b3ad24", "68f84a59a3ca2d0e5cb1646fbb164da409b5d8f2", "2368a1ac71c68c4b47b4fb2806508e0eb447aa64", "82d5343f4b2f0fcf6e28672d1f1a10c434f213d5", "f4d3e057abac5109b7e953578fa97968ea34f43a", "6c783ce5cc13ea5ce572eddfaba02f9d1bb90905", "9e55bf6ab8f14b37cc6f69eb7374be6c5cbd2d07", "1290c28910a6c12c9a131f0ecb523114f20f14c2", "4f5fc75bd3c93bccc09fc2de9c95442456053faf", "a56559418dc7908ce5f0b24b05c78e055cb863dc", "52cedd6b110e4330b5186478736afa5203c4f9ea", "32e067e0414932c3edd95fc4176a54bff1ddfe29", "7bd258f2f4cc4b02fca4ea157f55f6d88d26d954", "df51a19b291586bf46450aec1d775f3e02799b55", "4642fe68c57cd01fc68fc11b7f22b940328a7cc4", "03a4de84c189a836eaee643041b34ad2386db70d", "25883f7a0e732e9ab10e594ea59425dfe4d90359", "3e3d6e12b933133de2caa248ea12bd193a67f206", "1e666934c5a35f509aa31bbd9af8a37a1ed13ba6", "6c177354157989a2c6cd7bac80465b13bea25832", "aca32b501c231ef8e2d8703e71415bfbe4ccbc64", "e6479c70bbac662e4cc134cb8bdaade59ff55b66", "d9b66a0801459c8094398ef8f04700a8569c9906", "b05d71c64979cb95fa74a33cdb31a40d258ae02e", "c1c8bbdc22796e28c0e15163d20899b65621d65a", "c2db330f6083854c99d4b5bfb6e8f29f201be699", "f08f24908d682555111be7ff6f004e78283d989a", "5ee0f8895f4e1aae6a6661de5c432e34188a5a2d", "dbc8b8f59ff85a2b1448ed873484b14bf0507246", "13d956033d9af449bfe2c4ef78c17c20469c4bf1", "aeab141db28af3353283b5ccb2a322df0b9b5f56", "67b4b3923fa178d788a9611b76446c96431071f2", "03f09f5b158a7a8cdad920bddc29b81c18a551f5" };
var digest_buffer: [SHA_1_DIGEST_LENGTH]u8 = undefined;
for (0..64) |i| {
var ctx = sha1_new();
for (0..i) |_|
try sha1_update(&ctx, "a");
sha1_final(&ctx, &digest_buffer);
const ref = sha.hex_to_bytes(SHA_1_DIGEST_LENGTH, reference[i]);
try testing.expectEqualSlices(u8, ref[0..], digest_buffer[0..]);
}
}
test "SHA-1 maximum length violation (simulated)" {
var ctx = sha1_new();
ctx.message_length = (1 << 61) - 1; // 2^64 - 8 bits
try testing.expectError(sha.MessageLengthLimitExceeded, sha1_update(&ctx, "a"));
}

File diff suppressed because one or more lines are too long

View File

@ -164,48 +164,3 @@ pub fn deserialize_int_big_endian(T: type, bytes: *const [@sizeOf(T)]u8) T {
res |= @as(T, bytes[i]) << @intCast(8 * (@sizeOf(T) - i - 1)); res |= @as(T, bytes[i]) << @intCast(8 * (@sizeOf(T) - i - 1));
return res; return res;
} }
// ----------------------------------- TEST HELPERS ----------------------------------- //
const testing = @import("std").testing;
pub fn hex_nibble_to_int(ascii_hex: u8) u4 {
const x = ascii_hex;
return @intCast(if (x >= '0' and x <= '9')
x - '0'
else if (x >= 'a' and x <= 'f')
10 + (x - 'a')
else if (x >= 'A' and x <= 'F')
10 + (x - 'A')
else
@panic("Argument is not a valid hex digit!"));
}
pub fn hex_to_bytes(L: comptime_int, hex_string: *const [2 * L]u8) [L]u8 {
var res: [L]u8 = undefined;
for (0..L) |i| {
res[i] = @as(u8, hex_nibble_to_int(hex_string[2 * i])) << 4;
res[i] |= hex_nibble_to_int(hex_string[2 * i + 1]);
}
return res;
}
pub fn run_hash_precomputed_tests(
Ctx: type,
L: comptime_int,
fn_new: *const fn () Ctx,
fn_update: *const fn (*Ctx, []const u8) anyerror!void,
fn_final: *const fn (*Ctx, *[L]u8) void,
tests: []const struct { message: []const u8, hash: *const [2 * L]u8 },
) !void {
var digest_buffer: [L]u8 = undefined;
for (tests) |t| {
var ctx = fn_new();
try fn_update(&ctx, t.message);
fn_final(&ctx, &digest_buffer);
const reference = hex_to_bytes(L, t.hash);
try testing.expectEqualSlices(u8, reference[0..], digest_buffer[0..]);
}
}

View File

@ -1,59 +1,63 @@
const std = @import("std"); const std = @import("std");
const testing = std.testing; const testing = std.testing;
const byte_operations = @import("TODO").utility.byte_operations;
const word_to_bytes = byte_operations.word_to_bytes_le;
const bytes_to_word = byte_operations.bytes_to_word_le;
// ----------------------------------- ERROR DEFINITIONS ----------------------------------- // // ----------------------------------- ERROR DEFINITIONS ----------------------------------- //
const KeyStreamDepleted = error.KeyStreamDepleted; pub const KeyStreamDepleted = error.KeyStreamDepleted;
// ----------------------------------- ChaCha20 CONSTANTS ----------------------------------- // // ----------------------------------- ChaCha20 CONSTANTS ----------------------------------- //
const CHACHA20_BLOCK_SIZE = 512 / 8; pub const BLOCK_SIZE = 512 / 8;
const CHACHA20_BLOCK_WORDS = CHACHA20_BLOCK_SIZE / 4; pub const BLOCK_WORDS = BLOCK_SIZE / 4;
const CHACHA20_KEY_SIZE = 256 / 8; pub const KEY_SIZE = 256 / 8;
const CHACHA20_KEY_WORDS = CHACHA20_KEY_SIZE / 4; pub const KEY_WORDS = KEY_SIZE / 4;
const CHACHA20_NONCE_SIZE = 128 / 8; pub const NONCE_SIZE = 128 / 8;
const CHACHA20_NONCE_WORDS = CHACHA20_NONCE_SIZE / 4; pub const NONCE_WORDS = NONCE_SIZE / 4;
const CHACHA20_CONSTANTS = [128 / 8 / 4]u32{ pub const Parameters_Bernstein = struct {
bytes_to_word_le("expa"),
bytes_to_word_le("nd 3"),
bytes_to_word_le("2-by"),
bytes_to_word_le("te k"),
};
pub const ChaCha20_Bernstein_Parameters = struct {
pub const NONCE_SIZE = 64 / 8; pub const NONCE_SIZE = 64 / 8;
pub const COUNTER_SIZE = 64 / 8; pub const COUNTER_SIZE = 64 / 8;
}; };
pub const ChaCha20_RFC7539_Parameters = struct { pub const Parameters_RFC7539 = struct {
pub const NONCE_SIZE = 96 / 8; pub const NONCE_SIZE = 96 / 8;
pub const COUNTER_SIZE = 32 / 8; pub const COUNTER_SIZE = 32 / 8;
}; };
const CONSTANTS = [128 / 8 / 4]u32{
bytes_to_word("expa"),
bytes_to_word("nd 3"),
bytes_to_word("2-by"),
bytes_to_word("te k"),
};
// ----------------------------------- CONTEXT MANAGEMENT ----------------------------------- // // ----------------------------------- CONTEXT MANAGEMENT ----------------------------------- //
pub const ChaCha20Ctx = struct { pub const ChaCha20Ctx = struct {
key: [CHACHA20_KEY_WORDS]u32, key: [KEY_WORDS]u32,
nonce: [CHACHA20_NONCE_WORDS]u32, nonce: [NONCE_WORDS]u32,
state: [CHACHA20_BLOCK_WORDS]u32, state: [BLOCK_WORDS]u32,
working_state: [CHACHA20_BLOCK_WORDS]u32, working_state: [BLOCK_WORDS]u32,
counter_idx_lsw: u8, counter_idx_lsw: u8,
counter_idx_msw: u8, counter_idx_msw: u8,
keystream_idx: u8, keystream_idx: u8,
}; };
pub fn chacha20_new( pub fn chacha20_new(
key: *const [CHACHA20_KEY_SIZE]u8, key: *const [KEY_SIZE]u8,
counter_size: comptime_int, counter_size: comptime_int,
counter: *const [counter_size]u8, counter: *const [counter_size]u8,
nonce_size: comptime_int, nonce_size: comptime_int,
nonce: *const [nonce_size]u8, nonce: *const [nonce_size]u8,
) ChaCha20Ctx { ) ChaCha20Ctx {
if (comptime counter_size + nonce_size != CHACHA20_NONCE_SIZE) if (comptime counter_size + nonce_size != NONCE_SIZE)
@panic("Invalid ChaCha initialization: The lengths of the counter and nonce must add up to 16 bytes."); @compileError("Invalid ChaCha initialization: The lengths of the counter and nonce must add up to 16 bytes.");
const counter_words = comptime counter_size / 4; const counter_words = comptime counter_size / 4;
const nonce_words = comptime nonce_size / 4; const nonce_words = comptime nonce_size / 4;
@ -68,11 +72,11 @@ pub fn chacha20_new(
.keystream_idx = undefined, .keystream_idx = undefined,
}; };
chacha20_deserialize(CHACHA20_KEY_WORDS, key, &ctx.key); deserialize(KEY_WORDS, key, &ctx.key);
chacha20_deserialize(counter_words, counter, ctx.nonce[0..counter_words]); deserialize(counter_words, counter, ctx.nonce[0..counter_words]);
chacha20_deserialize(nonce_words, nonce, ctx.nonce[counter_words .. counter_words + nonce_words]); deserialize(nonce_words, nonce, ctx.nonce[counter_words .. counter_words + nonce_words]);
chacha20_block_function(&ctx); block_function(&ctx);
ctx.keystream_idx = 0; ctx.keystream_idx = 0;
return ctx; return ctx;
@ -87,38 +91,38 @@ pub fn chacha20_destroy(ctx: *ChaCha20Ctx) void {
} }
pub fn chacha20_bernstein_new( pub fn chacha20_bernstein_new(
key: *const [CHACHA20_KEY_SIZE]u8, key: *const [KEY_SIZE]u8,
nonce: *const [ChaCha20_Bernstein_Parameters.NONCE_SIZE]u8, nonce: *const [Parameters_Bernstein.NONCE_SIZE]u8,
counter: *const [ChaCha20_Bernstein_Parameters.COUNTER_SIZE]u8, counter: *const [Parameters_Bernstein.COUNTER_SIZE]u8,
) ChaCha20Ctx { ) ChaCha20Ctx {
return chacha20_new( return chacha20_new(
key, key,
ChaCha20_Bernstein_Parameters.COUNTER_SIZE, Parameters_Bernstein.COUNTER_SIZE,
counter, counter,
ChaCha20_Bernstein_Parameters.NONCE_SIZE, Parameters_Bernstein.NONCE_SIZE,
nonce, nonce,
); );
} }
pub fn chacha20_rfc7539_new( pub fn chacha20_rfc7539_new(
key: *const [CHACHA20_KEY_SIZE]u8, key: *const [KEY_SIZE]u8,
nonce: *const [ChaCha20_RFC7539_Parameters.NONCE_SIZE]u8, nonce: *const [Parameters_RFC7539.NONCE_SIZE]u8,
counter: *const [ChaCha20_RFC7539_Parameters.COUNTER_SIZE]u8, counter: *const [Parameters_RFC7539.COUNTER_SIZE]u8,
) ChaCha20Ctx { ) ChaCha20Ctx {
return chacha20_new( return chacha20_new(
key, key,
ChaCha20_RFC7539_Parameters.COUNTER_SIZE, Parameters_RFC7539.COUNTER_SIZE,
counter, counter,
ChaCha20_RFC7539_Parameters.NONCE_SIZE, Parameters_RFC7539.NONCE_SIZE,
nonce, nonce,
); );
} }
// ----------------------------------- ENCRYPTION/DECRYPTION ----------------------------------- // // ----------------------------------- ENCRYPTION/DECRYPTION ----------------------------------- //
pub fn chacha20_encrypt_inplace(ctx: *ChaCha20Ctx, plaintext: []u8) !void { pub fn encrypt_inplace(ctx: *ChaCha20Ctx, plaintext: []u8) !void {
for (0..plaintext.len) |i| { for (0..plaintext.len) |i| {
try chacha20_ensure_keystream_expanded(ctx); try ensure_keystream_expanded(ctx);
const keystream: [*]const u8 = @ptrCast(&ctx.state); const keystream: [*]const u8 = @ptrCast(&ctx.state);
plaintext[i] ^= keystream[ctx.keystream_idx]; plaintext[i] ^= keystream[ctx.keystream_idx];
@ -126,19 +130,19 @@ pub fn chacha20_encrypt_inplace(ctx: *ChaCha20Ctx, plaintext: []u8) !void {
} }
} }
pub fn chacha20_decrypt_inplace(ctx: *ChaCha20Ctx, ciphertext: []u8) !void { pub fn decrypt_inplace(ctx: *ChaCha20Ctx, ciphertext: []u8) !void {
return chacha20_encrypt_inplace(ctx, ciphertext); return encrypt_inplace(ctx, ciphertext);
} }
fn chacha20_ensure_keystream_expanded(ctx: *ChaCha20Ctx) !void { pub fn ensure_keystream_expanded(ctx: *ChaCha20Ctx) !void {
if (ctx.keystream_idx == CHACHA20_BLOCK_SIZE) { if (ctx.keystream_idx == BLOCK_SIZE) {
try chacha20_increment_counter(ctx); try increment_counter(ctx);
chacha20_block_function(ctx); block_function(ctx);
ctx.keystream_idx = 0; ctx.keystream_idx = 0;
} }
} }
fn chacha20_increment_counter(ctx: *ChaCha20Ctx) !void { pub fn increment_counter(ctx: *ChaCha20Ctx) !void {
for (ctx.counter_idx_lsw..ctx.counter_idx_msw + 1) |idx| { for (ctx.counter_idx_lsw..ctx.counter_idx_msw + 1) |idx| {
const ov = @addWithOverflow(ctx.nonce[idx], 1); const ov = @addWithOverflow(ctx.nonce[idx], 1);
ctx.nonce[idx] = ov[0]; ctx.nonce[idx] = ov[0];
@ -151,7 +155,7 @@ fn chacha20_increment_counter(ctx: *ChaCha20Ctx) !void {
// ----------------------------------- KEYSTREAM EXPANSION ----------------------------------- // // ----------------------------------- KEYSTREAM EXPANSION ----------------------------------- //
pub fn chacha20_quarter_round(state: *[CHACHA20_BLOCK_WORDS]u32, ia: u8, ib: u8, ic: u8, id: u8) void { pub fn quarter_round(state: *[BLOCK_WORDS]u32, ia: u8, ib: u8, ic: u8, id: u8) void {
var a = state[ia]; var a = state[ia];
var b = state[ib]; var b = state[ib];
var c = state[ic]; var c = state[ic];
@ -176,194 +180,59 @@ pub fn chacha20_quarter_round(state: *[CHACHA20_BLOCK_WORDS]u32, ia: u8, ib: u8,
state[id] = d; state[id] = d;
} }
pub fn chacha20_inner_block(state: *[CHACHA20_BLOCK_WORDS]u32) void { pub fn double_round(state: *[BLOCK_WORDS]u32) void {
chacha20_quarter_round(state, 0, 4, 8, 12); quarter_round(state, 0, 4, 8, 12);
chacha20_quarter_round(state, 1, 5, 9, 13); quarter_round(state, 1, 5, 9, 13);
chacha20_quarter_round(state, 2, 6, 10, 14); quarter_round(state, 2, 6, 10, 14);
chacha20_quarter_round(state, 3, 7, 11, 15); quarter_round(state, 3, 7, 11, 15);
chacha20_quarter_round(state, 0, 5, 10, 15); quarter_round(state, 0, 5, 10, 15);
chacha20_quarter_round(state, 1, 6, 11, 12); quarter_round(state, 1, 6, 11, 12);
chacha20_quarter_round(state, 2, 7, 8, 13); quarter_round(state, 2, 7, 8, 13);
chacha20_quarter_round(state, 3, 4, 9, 14); quarter_round(state, 3, 4, 9, 14);
} }
pub fn chacha20_block_function(ctx: *ChaCha20Ctx) void { pub fn block_function(ctx: *ChaCha20Ctx) void {
// Reset state. // Reset state.
{ {
comptime var i = 0; comptime var i = 0;
@memcpy(ctx.state[i .. i + CHACHA20_CONSTANTS.len], &CHACHA20_CONSTANTS); @memcpy(ctx.state[i .. i + CONSTANTS.len], &CONSTANTS);
i += CHACHA20_CONSTANTS.len; i += CONSTANTS.len;
@memcpy(ctx.state[i .. i + CHACHA20_KEY_WORDS], ctx.key[0..]); @memcpy(ctx.state[i .. i + KEY_WORDS], ctx.key[0..]);
i += CHACHA20_KEY_WORDS; i += KEY_WORDS;
@memcpy(ctx.state[i .. i + CHACHA20_NONCE_WORDS], ctx.nonce[0..]); @memcpy(ctx.state[i .. i + NONCE_WORDS], ctx.nonce[0..]);
i += CHACHA20_NONCE_WORDS; i += NONCE_WORDS;
if (comptime i != CHACHA20_BLOCK_WORDS) if (comptime i != BLOCK_WORDS)
@panic("Invalid ChaCha20 parameters: |constants + key + nonce + counter| != block_size!"); @compileError("Invalid ChaCha20 parameters: |constants + key + nonce + counter| != block_size.");
} }
// Copy state to working_state. // Copy state to working_state.
@memcpy(&ctx.working_state, &ctx.state); @memcpy(&ctx.working_state, &ctx.state);
// Perform all 20 rounds (10 column rounds and 10 diagonal rounds). // Perform all 20 rounds (10 column rounds and 10 diagonal rounds).
for (0..10) |_| { for (0..10) |_|
chacha20_inner_block(&ctx.working_state); double_round(&ctx.working_state);
}
// Add the working_state to the state. // Add the working_state to the state.
for (0..CHACHA20_BLOCK_WORDS) |i| { for (0..BLOCK_WORDS) |i|
ctx.state[i] +%= ctx.working_state[i]; ctx.state[i] +%= ctx.working_state[i];
}
} }
// ----------------------------------- LITTLE ENDIAN HELPERS ----------------------------------- // // ----------------------------------- HELPERS ----------------------------------- //
fn chacha20_serialize(L: comptime_int, words: *const [L]u32, bytes: *[L * 4]u8) void { pub fn serialize(L: comptime_int, words: *const [L]u32, bytes: *[L * 4]u8) void {
for (0..L) |i| for (0..L) |i|
std.mem.writeInt(u32, @ptrCast(bytes[(i * 4)..(i * 4 + 4)]), words[i], .little); std.mem.writeInt(u32, @ptrCast(bytes[(i * 4)..(i * 4 + 4)]), words[i], .little);
} }
fn chacha20_deserialize(L: comptime_int, bytes: *const [L * 4]u8, words: *[L]u32) void { pub fn deserialize(L: comptime_int, bytes: *const [L * 4]u8, words: *[L]u32) void {
for (0..L) |i| for (0..L) |i|
words[i] = std.mem.readInt(u32, @ptrCast(bytes[(i * 4)..(i * 4 + 4)]), .little); words[i] = std.mem.readInt(u32, @ptrCast(bytes[(i * 4)..(i * 4 + 4)]), .little);
} }
fn bytes_to_word_le(bytes: *const [4]u8) u32 {
return std.mem.readInt(u32, bytes, .little);
}
fn word_to_bytes_le(word: u32) [4]u8 {
var bytes: [4]u8 = undefined;
std.mem.writeInt(u32, &bytes, word, .little);
return bytes;
}
// ----------------------------------- GENERIC HELPERS ----------------------------------- //
fn rol(word: u32, bits: comptime_int) u32 { fn rol(word: u32, bits: comptime_int) u32 {
return word << bits | word >> (32 - bits); return word << bits | word >> (32 - bits);
} }
// ----------------------------------- TEST VECTORS ----------------------------------- //
// https://www.rfc-editor.org/rfc/rfc7539#section-2.2.1
test "ChaCha Quarter Round" {
var state = [CHACHA20_BLOCK_WORDS]u32{
0x879531e0, 0xc5ecf37d, 0x516461b1, 0xc9a62f8a,
0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0x2a5f714c,
0x53372767, 0xb00a5631, 0x974c541a, 0x359e9963,
0x5c971061, 0x3d631689, 0x2098d9d6, 0x91dbd320,
};
const reference = [CHACHA20_BLOCK_WORDS]u32{
0x879531e0, 0xc5ecf37d, 0xbdb886dc, 0xc9a62f8a,
0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0xcfacafd2,
0xe46bea80, 0xb00a5631, 0x974c541a, 0x359e9963,
0x5c971061, 0xccc07c79, 0x2098d9d6, 0x91dbd320,
};
chacha20_quarter_round(&state, 2, 7, 8, 13);
try testing.expectEqualSlices(u32, reference[0..], state[0..]);
}
// https://www.rfc-editor.org/rfc/rfc7539#section-2.3.2
test "ChaCha20 Block Function" {
const key = [CHACHA20_KEY_SIZE]u8{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
};
const nonce = [ChaCha20_RFC7539_Parameters.NONCE_SIZE]u8{
0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00,
};
const reference = [CHACHA20_BLOCK_SIZE]u8{
0x10, 0xf1, 0xe7, 0xe4, 0xd1, 0x3b, 0x59, 0x15, 0x50, 0x0f, 0xdd, 0x1f, 0xa3, 0x20, 0x71, 0xc4,
0xc7, 0xd1, 0xf4, 0xc7, 0x33, 0xc0, 0x68, 0x03, 0x04, 0x22, 0xaa, 0x9a, 0xc3, 0xd4, 0x6c, 0x4e,
0xd2, 0x82, 0x64, 0x46, 0x07, 0x9f, 0xaa, 0x09, 0x14, 0xc2, 0xd7, 0x05, 0xd9, 0x8b, 0x02, 0xa2,
0xb5, 0x12, 0x9c, 0xd1, 0xde, 0x16, 0x4e, 0xb9, 0xcb, 0xd0, 0x83, 0xe8, 0xa2, 0x50, 0x3c, 0x4e,
};
var chacha = chacha20_rfc7539_new(&key, &nonce, &word_to_bytes_le(1));
defer chacha20_destroy(&chacha);
chacha20_block_function(&chacha);
var buffer: [CHACHA20_BLOCK_SIZE]u8 = undefined;
chacha20_serialize(CHACHA20_BLOCK_WORDS, &chacha.state, &buffer);
try testing.expectEqualSlices(u8, reference[0..], buffer[0..]);
}
// https://www.rfc-editor.org/rfc/rfc7539#section-2.4.2
test "ChaCha20 Cipher" {
const key = [CHACHA20_KEY_SIZE]u8{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
};
const nonce = [ChaCha20_RFC7539_Parameters.NONCE_SIZE]u8{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00,
};
const counter = word_to_bytes_le(1);
const plaintext = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
const reference = [_]u8{
0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81,
0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b,
0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab, 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57,
0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab, 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8,
0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61, 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e,
0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06, 0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36,
0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6, 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42,
0x87, 0x4d,
};
var chacha = chacha20_rfc7539_new(&key, &nonce, &counter);
defer chacha20_destroy(&chacha);
var buffer: [plaintext.len]u8 = undefined;
@memcpy(&buffer, plaintext);
try chacha20_encrypt_inplace(&chacha, &buffer);
try testing.expectEqualSlices(u8, reference[0..], buffer[0..]);
}
test "ChaCha20 32-bit counter increment edge cases" {
const key = [CHACHA20_KEY_SIZE]u8{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
};
const nonce = [ChaCha20_RFC7539_Parameters.NONCE_SIZE]u8{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00,
};
const counter = word_to_bytes_le(std.math.maxInt(u32) - 1);
var chacha = chacha20_rfc7539_new(&key, &nonce, &counter);
defer chacha20_destroy(&chacha);
// Counter: 2^32 - 2 -> 2^32 - 1, OK
try chacha20_increment_counter(&chacha);
// Counter: 2^32 - 1 -> 2^32, overflow
try testing.expectError(KeyStreamDepleted, chacha20_increment_counter(&chacha));
}
test "ChaCha20 64-bit counter increment edge cases" {
const key = [CHACHA20_KEY_SIZE]u8{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
};
const nonce = [ChaCha20_Bernstein_Parameters.NONCE_SIZE]u8{ 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00 };
const counter = [ChaCha20_Bernstein_Parameters.COUNTER_SIZE]u8{ 0xff, 0xff, 0xff, 0xff, 0xef, 0xbe, 0xad, 0xde };
var chacha = chacha20_bernstein_new(&key, &nonce, &counter);
defer chacha20_destroy(&chacha);
// Counter: 0xdeadbeefffffffff -> 0xdeadbef000000000, OK
try chacha20_increment_counter(&chacha);
try testing.expectEqualSlices(u32, &.{ 0x00000000, 0xdeadbef0 }, chacha.nonce[0..2]);
// Counter: 0xffffffffffffffff -> 0x10000000000000000, overflow
@memset(chacha.nonce[0..2], std.math.maxInt(u32));
try testing.expectError(KeyStreamDepleted, chacha20_increment_counter(&chacha));
}

View File

@ -1 +1,2 @@
pub const primitive = @import("./primitive/index.zig"); pub const primitive = @import("./primitive/index.zig");
pub const utility = @import("./utility/index.zig");

View File

@ -0,0 +1,21 @@
const std = @import("std");
pub fn word_to_bytes_le(word: u32) [4]u8 {
var bytes: [4]u8 = undefined;
std.mem.writeInt(u32, &bytes, word, .little);
return bytes;
}
pub fn bytes_to_word_le(bytes: *const [4]u8) u32 {
return std.mem.readInt(u32, bytes, .little);
}
pub fn word_to_bytes_be(word: u32) [4]u8 {
var bytes: [4]u8 = undefined;
std.mem.writeInt(u32, &bytes, word, .big);
return bytes;
}
pub fn bytes_to_word_be(bytes: *const [4]u8) u32 {
return std.mem.readInt(u32, bytes, .big);
}

1
src/utility/index.zig Normal file
View File

@ -0,0 +1 @@
pub const byte_operations = @import("byte_operations.zig");

View File

@ -1,10 +1,8 @@
comptime { comptime {
_ = .{ _ = .{
@import("./primitive/blockcipher/aes.zig"), @import("./primitive/blockcipher/aes.zig"),
@import("./primitive/blockcipher/des.zig"), @import("./primitive/blockcipher/operation_modes.zig"),
@import("./primitive/blockcipher/mode_gcm.zig"), @import("./primitive/digest/sha.zig"),
@import("./primitive/digest/sha_1.zig"),
@import("./primitive/digest/sha_2.zig"),
@import("./primitive/streamcipher/chacha20.zig"), @import("./primitive/streamcipher/chacha20.zig"),
@import("./primitive/streamcipher/salsa20.zig"), @import("./primitive/streamcipher/salsa20.zig"),
}; };

View File

@ -0,0 +1,322 @@
const std = @import("std");
const testing = std.testing;
const aes = @import("ziggy").primitive.blockcipher.aes;
test "AES-128 key expansion (FIPS 197)" {
const key = [aes.KEY_SIZE_128]u8{
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
};
const expected_expansion = [(aes.N_ROUNDS_128 + 1) * 4]u32{
0x2b7e1516, 0x28aed2a6, 0xabf71588, 0x09cf4f3c,
0xa0fafe17, 0x88542cb1, 0x23a33939, 0x2a6c7605,
0xf2c295f2, 0x7a96b943, 0x5935807a, 0x7359f67f,
0x3d80477d, 0x4716fe3e, 0x1e237e44, 0x6d7a883b,
0xef44a541, 0xa8525b7f, 0xb671253b, 0xdb0bad00,
0xd4d1c6f8, 0x7c839d87, 0xcaf2b8bc, 0x11f915bc,
0x6d88a37a, 0x110b3efd, 0xdbf98641, 0xca0093fd,
0x4e54f70e, 0x5f5fc9f3, 0x84a64fb2, 0x4ea6dc4f,
0xead27321, 0xb58dbad2, 0x312bf560, 0x7f8d292f,
0xac7766f3, 0x19fadc21, 0x28d12941, 0x575c006e,
0xd014f9a8, 0xc9ee2589, 0xe13f0cc8, 0xb6630ca6,
};
try testing.expectEqualSlices(u32, &expected_expansion, &aes.expand_key_128(&key));
}
test "AES-192 key expansion (FIPS 197)" {
const key = [aes.KEY_SIZE_192]u8{
0x8e, 0x73, 0xb0, 0xf7, 0xda, 0x0e, 0x64, 0x52,
0xc8, 0x10, 0xf3, 0x2b, 0x80, 0x90, 0x79, 0xe5,
0x62, 0xf8, 0xea, 0xd2, 0x52, 0x2c, 0x6b, 0x7b,
};
const expected_expansion = [(aes.N_ROUNDS_192 + 1) * 4]u32{
0x8e73b0f7, 0xda0e6452, 0xc810f32b, 0x809079e5,
0x62f8ead2, 0x522c6b7b, 0xfe0c91f7, 0x2402f5a5,
0xec12068e, 0x6c827f6b, 0x0e7a95b9, 0x5c56fec2,
0x4db7b4bd, 0x69b54118, 0x85a74796, 0xe92538fd,
0xe75fad44, 0xbb095386, 0x485af057, 0x21efb14f,
0xa448f6d9, 0x4d6dce24, 0xaa326360, 0x113b30e6,
0xa25e7ed5, 0x83b1cf9a, 0x27f93943, 0x6a94f767,
0xc0a69407, 0xd19da4e1, 0xec1786eb, 0x6fa64971,
0x485f7032, 0x22cb8755, 0xe26d1352, 0x33f0b7b3,
0x40beeb28, 0x2f18a259, 0x6747d26b, 0x458c553e,
0xa7e1466c, 0x9411f1df, 0x821f750a, 0xad07d753,
0xca400538, 0x8fcc5006, 0x282d166a, 0xbc3ce7b5,
0xe98ba06f, 0x448c773c, 0x8ecc7204, 0x01002202,
};
try testing.expectEqualSlices(u32, &expected_expansion, &aes.expand_key_192(&key));
}
test "AES-256 key expansion (FIPS 197)" {
const key = [aes.KEY_SIZE_256]u8{
0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe,
0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7,
0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4,
};
const expected_expansion = [(aes.N_ROUNDS_256 + 1) * 4]u32{
0x603deb10, 0x15ca71be, 0x2b73aef0, 0x857d7781,
0x1f352c07, 0x3b6108d7, 0x2d9810a3, 0x0914dff4,
0x9ba35411, 0x8e6925af, 0xa51a8b5f, 0x2067fcde,
0xa8b09c1a, 0x93d194cd, 0xbe49846e, 0xb75d5b9a,
0xd59aecb8, 0x5bf3c917, 0xfee94248, 0xde8ebe96,
0xb5a9328a, 0x2678a647, 0x98312229, 0x2f6c79b3,
0x812c81ad, 0xdadf48ba, 0x24360af2, 0xfab8b464,
0x98c5bfc9, 0xbebd198e, 0x268c3ba7, 0x09e04214,
0x68007bac, 0xb2df3316, 0x96e939e4, 0x6c518d80,
0xc814e204, 0x76a9fb8a, 0x5025c02d, 0x59c58239,
0xde136967, 0x6ccc5a71, 0xfa256395, 0x9674ee15,
0x5886ca5d, 0x2e2f31d7, 0x7e0af1fa, 0x27cf73c3,
0x749c47ab, 0x18501dda, 0xe2757e4f, 0x7401905a,
0xcafaaae3, 0xe4d59b34, 0x9adf6ace, 0xbd10190d,
0xfe4890d1, 0xe6188d0b, 0x046df344, 0x706c631e,
};
try testing.expectEqualSlices(u32, &expected_expansion, &aes.expand_key_256(&key));
}
// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
test "AES-128 ECB encryption (NIST)" {
const key = [aes.KEY_SIZE_128]u8{
0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C,
};
const plaintext = [_][aes.BLOCK_SIZE]u8{
.{ 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A },
.{ 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51 },
.{ 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF },
.{ 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 },
};
const ciphertext = [_][aes.BLOCK_SIZE]u8{
.{ 0x3A, 0xD7, 0x7B, 0xB4, 0x0D, 0x7A, 0x36, 0x60, 0xA8, 0x9E, 0xCA, 0xF3, 0x24, 0x66, 0xEF, 0x97 },
.{ 0xF5, 0xD3, 0xD5, 0x85, 0x03, 0xB9, 0x69, 0x9D, 0xE7, 0x85, 0x89, 0x5A, 0x96, 0xFD, 0xBA, 0xAF },
.{ 0x43, 0xB1, 0xCD, 0x7F, 0x59, 0x8E, 0xCE, 0x23, 0x88, 0x1B, 0x00, 0xE3, 0xED, 0x03, 0x06, 0x88 },
.{ 0x7B, 0x0C, 0x78, 0x5E, 0x27, 0xE8, 0xAD, 0x3F, 0x82, 0x23, 0x20, 0x71, 0x04, 0x72, 0x5D, 0xD4 },
};
var buffer: [aes.BLOCK_SIZE]u8 = undefined;
for (plaintext, 0..) |pt, i| {
aes.aes128_encrypt_block(&key, &pt, &buffer);
try testing.expectEqualSlices(u8, &ciphertext[i], &buffer);
}
}
// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
test "AES-128 ECB decryption (NIST)" {
const key = [aes.KEY_SIZE_128]u8{
0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C,
};
const plaintext = [_][aes.BLOCK_SIZE]u8{
.{ 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A },
.{ 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51 },
.{ 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF },
.{ 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 },
};
const ciphertext = [_][aes.BLOCK_SIZE]u8{
.{ 0x3A, 0xD7, 0x7B, 0xB4, 0x0D, 0x7A, 0x36, 0x60, 0xA8, 0x9E, 0xCA, 0xF3, 0x24, 0x66, 0xEF, 0x97 },
.{ 0xF5, 0xD3, 0xD5, 0x85, 0x03, 0xB9, 0x69, 0x9D, 0xE7, 0x85, 0x89, 0x5A, 0x96, 0xFD, 0xBA, 0xAF },
.{ 0x43, 0xB1, 0xCD, 0x7F, 0x59, 0x8E, 0xCE, 0x23, 0x88, 0x1B, 0x00, 0xE3, 0xED, 0x03, 0x06, 0x88 },
.{ 0x7B, 0x0C, 0x78, 0x5E, 0x27, 0xE8, 0xAD, 0x3F, 0x82, 0x23, 0x20, 0x71, 0x04, 0x72, 0x5D, 0xD4 },
};
var buffer: [aes.BLOCK_SIZE]u8 = undefined;
for (ciphertext, 0..) |ct, i| {
aes.aes128_decrypt_block(&key, &ct, &buffer);
try testing.expectEqualSlices(u8, &plaintext[i], &buffer);
}
}
// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
test "AES-192 ECB encryption (NIST)" {
const key = [aes.KEY_SIZE_192]u8{
0x8E, 0x73, 0xB0, 0xF7, 0xDA, 0x0E, 0x64, 0x52,
0xC8, 0x10, 0xF3, 0x2B, 0x80, 0x90, 0x79, 0xE5,
0x62, 0xF8, 0xEA, 0xD2, 0x52, 0x2C, 0x6B, 0x7B,
};
const plaintext = [_][aes.BLOCK_SIZE]u8{
.{ 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A },
.{ 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51 },
.{ 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF },
.{ 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 },
};
const ciphertext = [_][aes.BLOCK_SIZE]u8{
.{ 0xBD, 0x33, 0x4F, 0x1D, 0x6E, 0x45, 0xF2, 0x5F, 0xF7, 0x12, 0xA2, 0x14, 0x57, 0x1F, 0xA5, 0xCC },
.{ 0x97, 0x41, 0x04, 0x84, 0x6D, 0x0A, 0xD3, 0xAD, 0x77, 0x34, 0xEC, 0xB3, 0xEC, 0xEE, 0x4E, 0xEF },
.{ 0xEF, 0x7A, 0xFD, 0x22, 0x70, 0xE2, 0xE6, 0x0A, 0xDC, 0xE0, 0xBA, 0x2F, 0xAC, 0xE6, 0x44, 0x4E },
.{ 0x9A, 0x4B, 0x41, 0xBA, 0x73, 0x8D, 0x6C, 0x72, 0xFB, 0x16, 0x69, 0x16, 0x03, 0xC1, 0x8E, 0x0E },
};
var buffer: [aes.BLOCK_SIZE]u8 = undefined;
for (plaintext, 0..) |pt, i| {
aes.aes192_encrypt_block(&key, &pt, &buffer);
try testing.expectEqualSlices(u8, &ciphertext[i], &buffer);
}
}
// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
test "AES-192 ECB decryption (NIST)" {
const key = [aes.KEY_SIZE_192]u8{
0x8E, 0x73, 0xB0, 0xF7, 0xDA, 0x0E, 0x64, 0x52,
0xC8, 0x10, 0xF3, 0x2B, 0x80, 0x90, 0x79, 0xE5,
0x62, 0xF8, 0xEA, 0xD2, 0x52, 0x2C, 0x6B, 0x7B,
};
const plaintext = [_][aes.BLOCK_SIZE]u8{
.{ 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A },
.{ 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51 },
.{ 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF },
.{ 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 },
};
const ciphertext = [_][aes.BLOCK_SIZE]u8{
.{ 0xBD, 0x33, 0x4F, 0x1D, 0x6E, 0x45, 0xF2, 0x5F, 0xF7, 0x12, 0xA2, 0x14, 0x57, 0x1F, 0xA5, 0xCC },
.{ 0x97, 0x41, 0x04, 0x84, 0x6D, 0x0A, 0xD3, 0xAD, 0x77, 0x34, 0xEC, 0xB3, 0xEC, 0xEE, 0x4E, 0xEF },
.{ 0xEF, 0x7A, 0xFD, 0x22, 0x70, 0xE2, 0xE6, 0x0A, 0xDC, 0xE0, 0xBA, 0x2F, 0xAC, 0xE6, 0x44, 0x4E },
.{ 0x9A, 0x4B, 0x41, 0xBA, 0x73, 0x8D, 0x6C, 0x72, 0xFB, 0x16, 0x69, 0x16, 0x03, 0xC1, 0x8E, 0x0E },
};
var buffer: [aes.BLOCK_SIZE]u8 = undefined;
for (ciphertext, 0..) |ct, i| {
aes.aes192_decrypt_block(&key, &ct, &buffer);
try testing.expectEqualSlices(u8, &plaintext[i], &buffer);
}
}
// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
test "AES-256 ECB encryption (NIST)" {
const key = [aes.KEY_SIZE_256]u8{
0x60, 0x3D, 0xEB, 0x10, 0x15, 0xCA, 0x71, 0xBE, 0x2B, 0x73, 0xAE, 0xF0, 0x85, 0x7D, 0x77, 0x81,
0x1F, 0x35, 0x2C, 0x07, 0x3B, 0x61, 0x08, 0xD7, 0x2D, 0x98, 0x10, 0xA3, 0x09, 0x14, 0xDF, 0xF4,
};
const plaintext = [_][aes.BLOCK_SIZE]u8{
.{ 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A },
.{ 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51 },
.{ 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF },
.{ 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 },
};
const ciphertext = [_][aes.BLOCK_SIZE]u8{
.{ 0xF3, 0xEE, 0xD1, 0xBD, 0xB5, 0xD2, 0xA0, 0x3C, 0x06, 0x4B, 0x5A, 0x7E, 0x3D, 0xB1, 0x81, 0xF8 },
.{ 0x59, 0x1C, 0xCB, 0x10, 0xD4, 0x10, 0xED, 0x26, 0xDC, 0x5B, 0xA7, 0x4A, 0x31, 0x36, 0x28, 0x70 },
.{ 0xB6, 0xED, 0x21, 0xB9, 0x9C, 0xA6, 0xF4, 0xF9, 0xF1, 0x53, 0xE7, 0xB1, 0xBE, 0xAF, 0xED, 0x1D },
.{ 0x23, 0x30, 0x4B, 0x7A, 0x39, 0xF9, 0xF3, 0xFF, 0x06, 0x7D, 0x8D, 0x8F, 0x9E, 0x24, 0xEC, 0xC7 },
};
var buffer: [aes.BLOCK_SIZE]u8 = undefined;
for (plaintext, 0..) |pt, i| {
aes.aes256_encrypt_block(&key, &pt, &buffer);
try testing.expectEqualSlices(u8, &ciphertext[i], &buffer);
}
}
// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
test "AES-256 ECB decryption (NIST)" {
const key = [aes.KEY_SIZE_256]u8{
0x60, 0x3D, 0xEB, 0x10, 0x15, 0xCA, 0x71, 0xBE, 0x2B, 0x73, 0xAE, 0xF0, 0x85, 0x7D, 0x77, 0x81,
0x1F, 0x35, 0x2C, 0x07, 0x3B, 0x61, 0x08, 0xD7, 0x2D, 0x98, 0x10, 0xA3, 0x09, 0x14, 0xDF, 0xF4,
};
const plaintext = [_][aes.BLOCK_SIZE]u8{
.{ 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A },
.{ 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x03, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC, 0x45, 0xAF, 0x8E, 0x51 },
.{ 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB, 0xC1, 0x19, 0x1A, 0x0A, 0x52, 0xEF },
.{ 0xF6, 0x9F, 0x24, 0x45, 0xDF, 0x4F, 0x9B, 0x17, 0xAD, 0x2B, 0x41, 0x7B, 0xE6, 0x6C, 0x37, 0x10 },
};
const ciphertext = [_][aes.BLOCK_SIZE]u8{
.{ 0xF3, 0xEE, 0xD1, 0xBD, 0xB5, 0xD2, 0xA0, 0x3C, 0x06, 0x4B, 0x5A, 0x7E, 0x3D, 0xB1, 0x81, 0xF8 },
.{ 0x59, 0x1C, 0xCB, 0x10, 0xD4, 0x10, 0xED, 0x26, 0xDC, 0x5B, 0xA7, 0x4A, 0x31, 0x36, 0x28, 0x70 },
.{ 0xB6, 0xED, 0x21, 0xB9, 0x9C, 0xA6, 0xF4, 0xF9, 0xF1, 0x53, 0xE7, 0xB1, 0xBE, 0xAF, 0xED, 0x1D },
.{ 0x23, 0x30, 0x4B, 0x7A, 0x39, 0xF9, 0xF3, 0xFF, 0x06, 0x7D, 0x8D, 0x8F, 0x9E, 0x24, 0xEC, 0xC7 },
};
var buffer: [aes.BLOCK_SIZE]u8 = undefined;
for (ciphertext, 0..) |ct, i| {
aes.aes256_decrypt_block(&key, &ct, &buffer);
try testing.expectEqualSlices(u8, &plaintext[i], &buffer);
}
}
test "AES S-box inverse is the inverse S-box" {
for (0..256) |x| {
try testing.expectEqual(x, aes.INV_SBOX[aes.SBOX[x]]);
}
}
test "AES SubBytes" {
var state = [aes.BLOCK_SIZE]u8{
0x40, 0xBF, 0xAB, 0xF4, 0x06, 0xEE, 0x4D, 0x30,
0x42, 0xCA, 0x6B, 0x99, 0x7A, 0x5C, 0x58, 0x16,
};
const reference = [aes.BLOCK_SIZE]u8{
0x09, 0x08, 0x62, 0xBF, 0x6F, 0x28, 0xE3, 0x04,
0x2C, 0x74, 0x7F, 0xEE, 0xDA, 0x4A, 0x6A, 0x47,
};
aes.sub_bytes(&state);
try testing.expectEqualSlices(u8, &reference, &state);
}
test "AES ShiftRows" {
var state = [aes.BLOCK_SIZE]u8{
0x09, 0x08, 0x62, 0xBF, 0x6F, 0x28, 0xE3, 0x04,
0x2C, 0x74, 0x7F, 0xEE, 0xDA, 0x4A, 0x6A, 0x47,
};
const reference = [aes.BLOCK_SIZE]u8{
0x09, 0x28, 0x7F, 0x47, 0x6F, 0x74, 0x6A, 0xBF,
0x2C, 0x4A, 0x62, 0x04, 0xDA, 0x08, 0xE3, 0xEE,
};
aes.shift_rows(&state);
try testing.expectEqualSlices(u8, &reference, &state);
}
test "AES MixColumns" {
var state = [aes.BLOCK_SIZE]u8{
0x09, 0x28, 0x7F, 0x47, 0x6F, 0x74, 0x6A, 0xBF,
0x2C, 0x4A, 0x62, 0x04, 0xDA, 0x08, 0xE3, 0xEE,
};
const reference = [aes.BLOCK_SIZE]u8{
0x52, 0x9F, 0x16, 0xC2, 0x97, 0x86, 0x15, 0xCA,
0xE0, 0x1A, 0xAE, 0x54, 0xBA, 0x1A, 0x26, 0x59,
};
aes.mix_columns(&state);
try testing.expectEqualSlices(u8, &reference, &state);
}
test "AES InvSubBytes" {
const reference = [aes.BLOCK_SIZE]u8{
0x40, 0xBF, 0xAB, 0xF4, 0x06, 0xEE, 0x4D, 0x30,
0x42, 0xCA, 0x6B, 0x99, 0x7A, 0x5C, 0x58, 0x16,
};
var state = [aes.BLOCK_SIZE]u8{
0x09, 0x08, 0x62, 0xBF, 0x6F, 0x28, 0xE3, 0x04,
0x2C, 0x74, 0x7F, 0xEE, 0xDA, 0x4A, 0x6A, 0x47,
};
aes.inv_sub_bytes(&state);
try testing.expectEqualSlices(u8, &reference, &state);
}
test "AES InvShiftRows" {
const reference = [aes.BLOCK_SIZE]u8{
0x09, 0x08, 0x62, 0xBF, 0x6F, 0x28, 0xE3, 0x04,
0x2C, 0x74, 0x7F, 0xEE, 0xDA, 0x4A, 0x6A, 0x47,
};
var state = [aes.BLOCK_SIZE]u8{
0x09, 0x28, 0x7F, 0x47, 0x6F, 0x74, 0x6A, 0xBF,
0x2C, 0x4A, 0x62, 0x04, 0xDA, 0x08, 0xE3, 0xEE,
};
aes.inv_shift_rows(&state);
try testing.expectEqualSlices(u8, &reference, &state);
}
test "AES InvMixColumns" {
const reference = [aes.BLOCK_SIZE]u8{
0x09, 0x28, 0x7F, 0x47, 0x6F, 0x74, 0x6A, 0xBF,
0x2C, 0x4A, 0x62, 0x04, 0xDA, 0x08, 0xE3, 0xEE,
};
var state = [aes.BLOCK_SIZE]u8{
0x52, 0x9F, 0x16, 0xC2, 0x97, 0x86, 0x15, 0xCA,
0xE0, 0x1A, 0xAE, 0x54, 0xBA, 0x1A, 0x26, 0x59,
};
aes.inv_mix_columns(&state);
try testing.expectEqualSlices(u8, &reference, &state);
}

View File

@ -0,0 +1,78 @@
const std = @import("std");
const testing = std.testing;
// Use AES to test all of the operation modes.
const aes = @import("ziggy").primitive.blockcipher.aes;
// ----------------------------------- CBC MODE ----------------------------------- //
const cbc = @import("ziggy").primitive.blockcipher.operation_mode.cbc;
test "AES-128-CBC basic test" {
const BS = aes.BLOCK_SIZE;
const KS = aes.KEY_SIZE_128;
const ENC = aes.aes128_encrypt_block;
const DEC = aes.aes128_decrypt_block;
// Plaintext source: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_block_chaining_(CBC)
// Retrieved 2025/02/01
const key = [KS]u8{ 0x0c, 0x49, 0x08, 0x75, 0xd4, 0x63, 0x2c, 0x2a, 0x9a, 0x73, 0xab, 0xb6, 0x5c, 0x67, 0x06, 0x0d };
const iv = [BS]u8{ 0xbe, 0x10, 0x47, 0x59, 0xac, 0xf3, 0x9c, 0x10, 0x1e, 0x2e, 0x37, 0x77, 0x85, 0xe0, 0x13, 0x0e };
const plaintext = "Ehrsam, Meyer, Smith and Tuchman invented the cipher block chaining (CBC) mode of operation in 1976.[23] In CBC mode, each block of plaintext is XORed with the previous ciphertext block before being encrypted. This way, each ciphertext block depends on all plaintext blocks processed up to that point. To make each message unique, an initialization vector must be used in the first block.";
const ciphertext = [_]u8{ 0x1c, 0x15, 0x02, 0xb8, 0x88, 0xfe, 0xf2, 0x23, 0x72, 0xbf, 0x14, 0x7a, 0xf7, 0x11, 0xde, 0x5a, 0x53, 0x7b, 0x3e, 0xf3, 0xe3, 0xea, 0xb7, 0xd8, 0xe2, 0x4d, 0x9a, 0x82, 0xd8, 0x84, 0x96, 0xc2, 0xa4, 0x8a, 0x82, 0xd5, 0xe9, 0x46, 0x3d, 0x4e, 0xf2, 0xc2, 0xcc, 0x62, 0x73, 0x9d, 0x6e, 0xf8, 0x86, 0xd0, 0xac, 0xb0, 0x0a, 0x80, 0xef, 0x3a, 0x83, 0x74, 0xb7, 0xf9, 0x4f, 0xc5, 0x52, 0x2c, 0x5c, 0x52, 0xd8, 0x41, 0xd4, 0x0b, 0xc2, 0xa9, 0xa6, 0xa3, 0x7b, 0xcc, 0x2e, 0x15, 0xd5, 0xc9, 0x8e, 0x6a, 0x0b, 0x50, 0xe2, 0xbf, 0xcb, 0x09, 0x61, 0x1b, 0x26, 0x78, 0x1a, 0x23, 0xe7, 0xd5, 0x0c, 0x50, 0x3f, 0xc8, 0xa9, 0xb4, 0xea, 0x97, 0xa1, 0xaf, 0xf4, 0xcd, 0xb4, 0x76, 0x0e, 0x4c, 0xa9, 0xfc, 0xf8, 0xbd, 0x45, 0xbb, 0x7b, 0xd3, 0x84, 0x52, 0xac, 0x29, 0xa3, 0xee, 0x62, 0x23, 0xc1, 0x6a, 0xca, 0x1c, 0x62, 0x0a, 0x8b, 0xce, 0x36, 0x9e, 0x7e, 0xb5, 0x5b, 0x2a, 0xa8, 0x85, 0x37, 0x51, 0x0c, 0xed, 0x05, 0xca, 0xb6, 0xc6, 0xd9, 0xc0, 0x0d, 0x9d, 0x57, 0x2a, 0xcd, 0x79, 0xf6, 0x23, 0x36, 0x6d, 0x50, 0x29, 0xff, 0xa2, 0x71, 0x5a, 0x77, 0x0e, 0x26, 0x41, 0x2f, 0x22, 0xc6, 0x8f, 0xdf, 0x2a, 0x0f, 0xb6, 0x19, 0xb7, 0xdb, 0x6b, 0x62, 0x04, 0x50, 0x97, 0x13, 0x8d, 0x60, 0xfa, 0x31, 0x89, 0x36, 0x9e, 0x46, 0xf9, 0x51, 0xcc, 0xd0, 0x90, 0x8b, 0x3d, 0x7a, 0x67, 0xfa, 0x16, 0xdf, 0x3a, 0xe8, 0x96, 0x9c, 0xac, 0x18, 0x7a, 0x12, 0x55, 0x52, 0xcf, 0xc0, 0xeb, 0x0b, 0x49, 0xc2, 0xda, 0x1a, 0x20, 0xaa, 0xee, 0x04, 0x14, 0xa5, 0x23, 0xbf, 0x60, 0x95, 0x09, 0x19, 0x55, 0xa4, 0x93, 0xfe, 0xab, 0x33, 0x6d, 0x4f, 0x51, 0xcb, 0xff, 0xe5, 0x69, 0x39, 0x4c, 0x1a, 0x8c, 0xf9, 0x12, 0x0d, 0x67, 0x7c, 0x10, 0xe9, 0xf7, 0xdf, 0xa6, 0x92, 0x4f, 0x6c, 0x55, 0xca, 0x42, 0xa2, 0x7b, 0xd1, 0xbf, 0x3b, 0xcf, 0xc1, 0xda, 0xad, 0x80, 0x12, 0x95, 0xe7, 0x86, 0x4a, 0x04, 0x1f, 0x01, 0x9e, 0x64, 0xaf, 0x02, 0xcd, 0xb3, 0x84, 0x39, 0x42, 0x8b, 0x04, 0x04, 0x5d, 0x76, 0x20, 0x49, 0x3b, 0x87, 0x03, 0x31, 0xa5, 0x40, 0x4d, 0xca, 0x0e, 0x15, 0x28, 0x03, 0x77, 0xbc, 0xe4, 0x70, 0x69, 0x0d, 0x21, 0x80, 0xa7, 0x92, 0xf7, 0xe0, 0x74, 0x4d, 0xe7, 0xf0, 0x26, 0x55, 0xe7, 0x14, 0xb7, 0xde, 0xa0, 0xf5, 0xd3, 0xe7, 0xea, 0x2b, 0x0b, 0x28, 0x7c, 0xd5, 0x36, 0x0f, 0xb0, 0x83, 0x42, 0x0d, 0x1d, 0xf5, 0xc9, 0xcc, 0xe2, 0x68, 0x16, 0xa1, 0xcc, 0xcb, 0x09, 0x2c, 0x85, 0xdf, 0xec, 0x02, 0xf5, 0x5f, 0x67, 0x6d, 0x04, 0xae, 0x3a, 0x34, 0xa8, 0x46, 0x9d, 0x80, 0xd8, 0xee, 0xfb, 0x12, 0x9f, 0x25, 0xd5, 0x0b, 0x38, 0xfe, 0x99, 0x59, 0xeb, 0xb9 };
// Test encryption.
{
const ct_length = comptime cbc.get_padded_buffer_length(BS, plaintext);
var ct_buffer: [ct_length]u8 = undefined;
var ctx = cbc.cbc_pkcs7_encrypt_new(BS, KS, ENC, &key, &iv);
defer cbc.cbc_pkcs7_encrypt_destroy(BS, KS, ENC, &ctx);
var pt_processed_bytes: usize = 0;
while (plaintext.len - pt_processed_bytes >= aes.BLOCK_SIZE) : (pt_processed_bytes += aes.BLOCK_SIZE)
cbc.cbc_pkcs7_encrypt_block(
BS,
KS,
ENC,
&ctx,
@ptrCast(plaintext[pt_processed_bytes .. pt_processed_bytes + aes.BLOCK_SIZE]),
@ptrCast(ct_buffer[pt_processed_bytes .. pt_processed_bytes + aes.BLOCK_SIZE]),
);
try cbc.cbc_pkcs7_encrypt_final(BS, KS, ENC, &ctx, plaintext[pt_processed_bytes..], @ptrCast(ct_buffer[pt_processed_bytes..]));
try testing.expectEqualSlices(u8, ciphertext[0..], ct_buffer[0..]);
}
// Test decryption.
{
var pt_buffer: [ciphertext.len]u8 = undefined;
var ctx = cbc.cbc_pkcs7_decrypt_new(BS, KS, DEC, &key, &iv);
defer cbc.cbc_pkcs7_decrypt_destroy(BS, KS, DEC, &ctx);
var ct_processed_bytes: usize = 0;
while (ciphertext.len - ct_processed_bytes >= 2 * aes.BLOCK_SIZE) : (ct_processed_bytes += aes.BLOCK_SIZE)
cbc.cbc_pkcs7_decrypt_block(
BS,
KS,
DEC,
&ctx,
@ptrCast(ciphertext[ct_processed_bytes .. ct_processed_bytes + aes.BLOCK_SIZE]),
@ptrCast(pt_buffer[ct_processed_bytes .. ct_processed_bytes + aes.BLOCK_SIZE]),
);
const residue_length = try cbc.cbc_pkcs7_decrypt_final(
BS,
KS,
DEC,
&ctx,
ciphertext[ct_processed_bytes..],
pt_buffer[ct_processed_bytes..],
);
try testing.expectEqual(plaintext.len % aes.BLOCK_SIZE, residue_length);
try testing.expectEqualSlices(u8, plaintext[0..], pt_buffer[0..plaintext.len]);
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,129 @@
const std = @import("std");
const testing = std.testing;
const byte_operations = @import("ziggy").utility.byte_operations;
const word_to_bytes = byte_operations.word_to_bytes_le;
const bytes_to_word = byte_operations.bytes_to_word_le;
const chacha20 = @import("ziggy").primitive.streamcipher.chacha20;
// ----------------------------------- TEST VECTORS ----------------------------------- //
// https://www.rfc-editor.org/rfc/rfc7539#section-2.2.1
test "ChaCha Quarter Round" {
var state = [chacha20.BLOCK_WORDS]u32{
0x879531e0, 0xc5ecf37d, 0x516461b1, 0xc9a62f8a,
0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0x2a5f714c,
0x53372767, 0xb00a5631, 0x974c541a, 0x359e9963,
0x5c971061, 0x3d631689, 0x2098d9d6, 0x91dbd320,
};
const reference = [chacha20.BLOCK_WORDS]u32{
0x879531e0, 0xc5ecf37d, 0xbdb886dc, 0xc9a62f8a,
0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0xcfacafd2,
0xe46bea80, 0xb00a5631, 0x974c541a, 0x359e9963,
0x5c971061, 0xccc07c79, 0x2098d9d6, 0x91dbd320,
};
chacha20.quarter_round(&state, 2, 7, 8, 13);
try testing.expectEqualSlices(u32, reference[0..], state[0..]);
}
// https://www.rfc-editor.org/rfc/rfc7539#section-2.3.2
test "ChaCha20 Block Function" {
const key = [chacha20.KEY_SIZE]u8{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
};
const nonce = [chacha20.Parameters_RFC7539.NONCE_SIZE]u8{
0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00,
};
const reference = [chacha20.BLOCK_SIZE]u8{
0x10, 0xf1, 0xe7, 0xe4, 0xd1, 0x3b, 0x59, 0x15, 0x50, 0x0f, 0xdd, 0x1f, 0xa3, 0x20, 0x71, 0xc4,
0xc7, 0xd1, 0xf4, 0xc7, 0x33, 0xc0, 0x68, 0x03, 0x04, 0x22, 0xaa, 0x9a, 0xc3, 0xd4, 0x6c, 0x4e,
0xd2, 0x82, 0x64, 0x46, 0x07, 0x9f, 0xaa, 0x09, 0x14, 0xc2, 0xd7, 0x05, 0xd9, 0x8b, 0x02, 0xa2,
0xb5, 0x12, 0x9c, 0xd1, 0xde, 0x16, 0x4e, 0xb9, 0xcb, 0xd0, 0x83, 0xe8, 0xa2, 0x50, 0x3c, 0x4e,
};
var chacha = chacha20.chacha20_rfc7539_new(&key, &nonce, &word_to_bytes(1));
defer chacha20.chacha20_destroy(&chacha);
chacha20.block_function(&chacha);
var buffer: [chacha20.BLOCK_SIZE]u8 = undefined;
chacha20.serialize(chacha20.BLOCK_WORDS, &chacha.state, &buffer);
try testing.expectEqualSlices(u8, reference[0..], buffer[0..]);
}
// https://www.rfc-editor.org/rfc/rfc7539#section-2.4.2
test "ChaCha20 Cipher" {
const key = [chacha20.KEY_SIZE]u8{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
};
const nonce = [chacha20.Parameters_RFC7539.NONCE_SIZE]u8{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00,
};
const counter = word_to_bytes(1);
const plaintext = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
const reference = [_]u8{
0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81,
0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b,
0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab, 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57,
0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab, 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8,
0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61, 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e,
0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06, 0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36,
0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6, 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42,
0x87, 0x4d,
};
var chacha = chacha20.chacha20_rfc7539_new(&key, &nonce, &counter);
defer chacha20.chacha20_destroy(&chacha);
var buffer: [plaintext.len]u8 = undefined;
@memcpy(&buffer, plaintext);
try chacha20.encrypt_inplace(&chacha, &buffer);
try testing.expectEqualSlices(u8, reference[0..], buffer[0..]);
}
test "ChaCha20 32-bit counter increment edge cases" {
const key = [chacha20.KEY_SIZE]u8{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
};
const nonce = [chacha20.Parameters_RFC7539.NONCE_SIZE]u8{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00,
};
const counter = word_to_bytes(std.math.maxInt(u32) - 1);
var chacha = chacha20.chacha20_rfc7539_new(&key, &nonce, &counter);
defer chacha20.chacha20_destroy(&chacha);
// Counter: 2^32 - 2 -> 2^32 - 1, OK
try chacha20.increment_counter(&chacha);
// Counter: 2^32 - 1 -> 2^32, overflow
try testing.expectError(chacha20.KeyStreamDepleted, chacha20.increment_counter(&chacha));
}
test "ChaCha20 64-bit counter increment edge cases" {
const key = [chacha20.KEY_SIZE]u8{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
};
const nonce = [chacha20.Parameters_Bernstein.NONCE_SIZE]u8{ 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00 };
const counter = [chacha20.Parameters_Bernstein.COUNTER_SIZE]u8{ 0xff, 0xff, 0xff, 0xff, 0xef, 0xbe, 0xad, 0xde };
var chacha = chacha20.chacha20_bernstein_new(&key, &nonce, &counter);
defer chacha20.chacha20_destroy(&chacha);
// Counter: 0xdeadbeefffffffff -> 0xdeadbef000000000, OK
try chacha20.increment_counter(&chacha);
try testing.expectEqualSlices(u32, &.{ 0x00000000, 0xdeadbef0 }, chacha.nonce[0..2]);
// Counter: 0xffffffffffffffff -> 0x10000000000000000, overflow
@memset(chacha.nonce[0..2], std.math.maxInt(u32));
try testing.expectError(chacha20.KeyStreamDepleted, chacha20.increment_counter(&chacha));
}

View File