diff --git a/README.md b/README.md index f3dc5db..99c1acc 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ Most (**theoretical!**) users should directly use one of the cryptographic *prot - Advanced Encryption Standard (FIPS 197): AES-128, AES-192, AES-256 - ChaCha20 (RFC 7539): ChaCha20 with 64-bit nonce and 64-bit counter, ChaCha20 with 96-bit nonce and 32-bit counter -- Salsa20: Salsa20/20 with 256-key, Salsa20/20 with 128-bit key -- Secure Hashing Algorithm: SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256 +- Salsa20 (Bernstein, not standardized): Salsa20/20 with 256-key, Salsa20/20 with 128-bit key +- Secure Hash Standard (FIPS 180-4): SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256 ### Protocols @@ -40,7 +40,7 @@ Most (**theoretical!**) users should directly use one of the cryptographic *prot - DES, 3DES - Block cipher modes: CBC-PKCS7, CFB, OFB, CTR, GCM - Poly1305 -- SHA-3, HMAC +- SHA-512/t, SHA-3, HMAC - BigIntegers & modular arithmetic - Cryptographically secure random BigInteger generation & primality testing - Elliptic Curve groups (over Fp fields) diff --git a/build.zig b/build.zig index bb8eeed..6b18dca 100644 --- a/build.zig +++ b/build.zig @@ -67,7 +67,7 @@ pub fn build(b: *std.Build) void { // Creates a step for unit testing. This only builds the test executable // but does not run it. const lib_unit_tests = b.addTest(.{ - .root_source_file = b.path("src/root.zig"), + .root_source_file = b.path("src/test.zig"), .target = target, .optimize = optimize, }); diff --git a/src/primitive/blockcipher/aes.zig b/src/primitive/blockcipher/aes.zig index 8c69d40..2f45913 100644 --- a/src/primitive/blockcipher/aes.zig +++ b/src/primitive/blockcipher/aes.zig @@ -63,7 +63,12 @@ const AES_RCON = [_]u32{ // ----------------------------------- ENCRYPTION/DECRYPTION ----------------------------------- // -pub fn aes_encrypt_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 aes_encrypt_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 { // Copy input buffer into state (we're treating the buffer as a column-first matrix). var state: [AES_BLOCK_SIZE]u8 = undefined; @memcpy(state[0..], block_in); @@ -120,63 +125,121 @@ pub fn aes_decrypt_block(n_rounds: comptime_int, block_in: *const [AES_BLOCK_SIZ @memset(state[0..], 0); } -pub fn aes_128_encrypt_block(key: *const [Aes128Parameters.KEY_SIZE]u8, block_in: *const [AES_BLOCK_SIZE]u8, block_out: *[AES_BLOCK_SIZE]u8) void { +pub fn aes_128_encrypt_block( + key: *const [Aes128Parameters.KEY_SIZE]u8, + block_in: *const [AES_BLOCK_SIZE]u8, + block_out: *[AES_BLOCK_SIZE]u8, +) void { // Prepare the subkeys for AddRoundKey. var expanded_key = aes_128_expand_key(key); defer @memset(&expanded_key, 0); // Call the generic encryption procedure. - aes_encrypt_block(Aes128Parameters.N_ROUNDS, block_in, block_out, &expanded_key); + aes_encrypt_block( + Aes128Parameters.N_ROUNDS, + block_in, + block_out, + &expanded_key, + ); } -pub fn aes_128_decrypt_block(key: *const [Aes128Parameters.KEY_SIZE]u8, block_in: *const [AES_BLOCK_SIZE]u8, block_out: *[AES_BLOCK_SIZE]u8) void { +pub fn aes_128_decrypt_block( + key: *const [Aes128Parameters.KEY_SIZE]u8, + block_in: *const [AES_BLOCK_SIZE]u8, + block_out: *[AES_BLOCK_SIZE]u8, +) void { // Prepare the subkeys for AddRoundKey. var expanded_key = aes_128_expand_key(key); defer @memset(&expanded_key, 0); // Call the generic decryption procedure. - aes_decrypt_block(Aes128Parameters.N_ROUNDS, block_in, block_out, &expanded_key); + aes_decrypt_block( + Aes128Parameters.N_ROUNDS, + block_in, + block_out, + &expanded_key, + ); } -pub fn aes_192_encrypt_block(key: *const [Aes192Parameters.KEY_SIZE]u8, block_in: *const [AES_BLOCK_SIZE]u8, block_out: *[AES_BLOCK_SIZE]u8) void { +pub fn aes_192_encrypt_block( + key: *const [Aes192Parameters.KEY_SIZE]u8, + block_in: *const [AES_BLOCK_SIZE]u8, + block_out: *[AES_BLOCK_SIZE]u8, +) void { // Prepare the subkeys for AddRoundKey. var expanded_key = aes_192_expand_key(key); defer @memset(&expanded_key, 0); // Call the generic encryption procedure. - aes_encrypt_block(Aes192Parameters.N_ROUNDS, block_in, block_out, &expanded_key); + aes_encrypt_block( + Aes192Parameters.N_ROUNDS, + block_in, + block_out, + &expanded_key, + ); } -pub fn aes_192_decrypt_block(key: *const [Aes192Parameters.KEY_SIZE]u8, block_in: *const [AES_BLOCK_SIZE]u8, block_out: *[AES_BLOCK_SIZE]u8) void { +pub fn aes_192_decrypt_block( + key: *const [Aes192Parameters.KEY_SIZE]u8, + block_in: *const [AES_BLOCK_SIZE]u8, + block_out: *[AES_BLOCK_SIZE]u8, +) void { // Prepare the subkeys for AddRoundKey. var expanded_key = aes_192_expand_key(key); defer @memset(&expanded_key, 0); // Call the generic decryption procedure. - aes_decrypt_block(Aes192Parameters.N_ROUNDS, block_in, block_out, &expanded_key); + aes_decrypt_block( + Aes192Parameters.N_ROUNDS, + block_in, + block_out, + &expanded_key, + ); } -pub fn aes_256_encrypt_block(key: *const [Aes256Parameters.KEY_SIZE]u8, block_in: *const [AES_BLOCK_SIZE]u8, block_out: *[AES_BLOCK_SIZE]u8) void { +pub fn aes_256_encrypt_block( + key: *const [Aes256Parameters.KEY_SIZE]u8, + block_in: *const [AES_BLOCK_SIZE]u8, + block_out: *[AES_BLOCK_SIZE]u8, +) void { // Prepare the subkeys for AddRoundKey. var expanded_key = aes_256_expand_key(key); defer @memset(&expanded_key, 0); // Call the generic encryption procedure. - aes_encrypt_block(Aes256Parameters.N_ROUNDS, block_in, block_out, &expanded_key); + aes_encrypt_block( + Aes256Parameters.N_ROUNDS, + block_in, + block_out, + &expanded_key, + ); } -pub fn aes_256_decrypt_block(key: *const [Aes256Parameters.KEY_SIZE]u8, block_in: *const [AES_BLOCK_SIZE]u8, block_out: *[AES_BLOCK_SIZE]u8) void { +pub fn aes_256_decrypt_block( + key: *const [Aes256Parameters.KEY_SIZE]u8, + block_in: *const [AES_BLOCK_SIZE]u8, + block_out: *[AES_BLOCK_SIZE]u8, +) void { // Prepare the subkeys for AddRoundKey. var expanded_key = aes_256_expand_key(key); defer @memset(&expanded_key, 0); // Call the generic decryption procedure. - aes_decrypt_block(Aes256Parameters.N_ROUNDS, block_in, block_out, &expanded_key); + aes_decrypt_block( + Aes256Parameters.N_ROUNDS, + block_in, + block_out, + &expanded_key, + ); } // ----------------------------------- KEY EXPANSION ----------------------------------- // -pub fn aes_expand_key(n_rounds: comptime_int, n_key_words: comptime_int, key: *const [n_key_words * 4]u8) [4 * (n_rounds + 1)]u32 { +pub fn aes_expand_key( + n_rounds: comptime_int, + n_key_words: comptime_int, + key: *const [n_key_words * 4]u8, +) [4 * (n_rounds + 1)]u32 { var expanded_key: [4 * (n_rounds + 1)]u32 = undefined; var i: u32 = 0; @@ -197,15 +260,27 @@ pub fn aes_expand_key(n_rounds: comptime_int, n_key_words: comptime_int, key: *c } pub fn aes_128_expand_key(key: *const [Aes128Parameters.KEY_SIZE]u8) [4 * (Aes128Parameters.N_ROUNDS + 1)]u32 { - return aes_expand_key(Aes128Parameters.N_ROUNDS, Aes128Parameters.KEY_SIZE / 4, key); + return aes_expand_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 { - return aes_expand_key(Aes192Parameters.N_ROUNDS, Aes192Parameters.KEY_SIZE / 4, key); + return aes_expand_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 { - return aes_expand_key(Aes256Parameters.N_ROUNDS, Aes256Parameters.KEY_SIZE / 4, key); + return aes_expand_key( + Aes256Parameters.N_ROUNDS, + Aes256Parameters.KEY_SIZE / 4, + key, + ); } // ----------------------------------- AES OPERATIONS ----------------------------------- // @@ -373,15 +448,12 @@ fn gfmult(factor: comptime_int, element: u8) u8 { fn word_to_bytes(word: u32) [4]u8 { var bytes: [4]u8 = undefined; - bytes[0] = @truncate(word >> 24); - bytes[1] = @truncate(word >> 16); - bytes[2] = @truncate(word >> 8); - bytes[3] = @truncate(word); + std.mem.writeInt(u32, &bytes, word, .big); return bytes; } fn bytes_to_word(bytes: *const [4]u8) u32 { - return (@as(u32, bytes[0]) << 24) | (@as(u32, bytes[1]) << 16) | (@as(u32, bytes[2]) << 8) | @as(u32, bytes[3]); + return std.mem.readInt(u32, bytes, .big); } // ----------------------------------- TEST VECTORS ----------------------------------- // @@ -631,7 +703,7 @@ test "AES SubBytes" { }; aes_sub_bytes(&state); - try testing.expect(std.mem.eql(u8, &state, &reference)); + try testing.expectEqualSlices(u8, &reference, &state); } test "AES ShiftRows" { @@ -645,7 +717,7 @@ test "AES ShiftRows" { }; aes_shift_rows(&state); - try testing.expect(std.mem.eql(u8, &state, &reference)); + try testing.expectEqualSlices(u8, &reference, &state); } test "AES MixColumns" { @@ -659,7 +731,7 @@ test "AES MixColumns" { }; aes_mix_columns(&state); - try testing.expect(std.mem.eql(u8, &state, &reference)); + try testing.expectEqualSlices(u8, &reference, &state); } test "AES InvSubBytes" { @@ -673,7 +745,7 @@ test "AES InvSubBytes" { }; aes_inv_sub_bytes(&state); - try testing.expect(std.mem.eql(u8, &state, &reference)); + try testing.expectEqualSlices(u8, &reference, &state); } test "AES InvShiftRows" { @@ -687,7 +759,7 @@ test "AES InvShiftRows" { }; aes_inv_shift_rows(&state); - try testing.expect(std.mem.eql(u8, &state, &reference)); + try testing.expectEqualSlices(u8, &reference, &state); } test "AES InvMixColumns" { @@ -701,5 +773,5 @@ test "AES InvMixColumns" { }; aes_inv_mix_columns(&state); - try testing.expect(std.mem.eql(u8, &state, &reference)); + try testing.expectEqualSlices(u8, &reference, &state); } diff --git a/src/primitive/blockcipher/des.zig b/src/primitive/blockcipher/des.zig index fb2e76b..d7f061f 100644 --- a/src/primitive/blockcipher/des.zig +++ b/src/primitive/blockcipher/des.zig @@ -4,15 +4,12 @@ const testing = std.testing; // ----------------------------------- DES CONSTANTS ----------------------------------- // pub const DES_BLOCK_SIZE = 64 / 8; - pub const DES_TRUE_KEY_SIZE = 56 / 8; pub const DES_ENCODED_KEY_SIZE = 64 / 8; - +pub const DES_N_ROUNDS = 16; pub const DES_SUBKEY_SIZE = 48 / 8; -pub const DES_N_ROUNDS = 16; - -pub const DES_INITIAL_PERMUTATION = [_]u6{ +const DES_INITIAL_PERMUTATION = [_]u6{ 58, 50, 42, 35, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, @@ -23,7 +20,7 @@ pub const DES_INITIAL_PERMUTATION = [_]u6{ 63, 55, 47, 39, 31, 23, 15, 7, }; -pub const DES_INV_INITIAL_PERMUTATION = [_]u6{ +const DES_INV_INITIAL_PERMUTATION = [_]u6{ 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, @@ -34,7 +31,7 @@ pub const DES_INV_INITIAL_PERMUTATION = [_]u6{ 33, 1, 41, 9, 49, 17, 57, 25, }; -pub const DES_BIT_SELECTION_TABLE_E = [_]u5{ +const DES_BIT_SELECTION_TABLE_E = [_]u5{ 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, @@ -45,14 +42,14 @@ pub const DES_BIT_SELECTION_TABLE_E = [_]u5{ 28, 29, 30, 31, 32, 1, }; -pub const DES_PERMUTATION_FUNCTION_P = [_]u5{ +const DES_PERMUTATION_FUNCTION_P = [_]u5{ 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25, }; -pub const DES_KS_SHIFT_SCHEDULE = .{ 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 }; +const DES_KS_SHIFT_SCHEDULE = .{ 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 }; // ----------------------------------- ENCRYPTION/DECRYPTION ----------------------------------- // @@ -192,4 +189,4 @@ fn rotate_halves_left(cd: *[DES_TRUE_KEY_SIZE]u8, positions: comptime_int) void // ----------------------------------- TEST VECTORS ----------------------------------- // -// +// TODO diff --git a/src/primitive/blockcipher/index.zig b/src/primitive/blockcipher/index.zig new file mode 100644 index 0000000..fa4ff3e --- /dev/null +++ b/src/primitive/blockcipher/index.zig @@ -0,0 +1,2 @@ +pub const aes = @import("aes.zig"); +pub const des = @import("des.zig"); diff --git a/src/primitive/digest/index.zig b/src/primitive/digest/index.zig new file mode 100644 index 0000000..30e4a27 --- /dev/null +++ b/src/primitive/digest/index.zig @@ -0,0 +1,2 @@ +pub const sha1 = @import("sha_1.zig"); +pub const sha2 = @import("sha_2.zig"); diff --git a/src/primitive/index.zig b/src/primitive/index.zig new file mode 100644 index 0000000..2b65300 --- /dev/null +++ b/src/primitive/index.zig @@ -0,0 +1,3 @@ +pub const blockcipher = @import("blockcipher/index.zig"); +pub const digest = @import("digest/index.zig"); +pub const streamcipher = @import("streamcipher/index.zig"); diff --git a/src/primitive/streamcipher/chacha20.zig b/src/primitive/streamcipher/chacha20.zig index 8e8ab61..8f44eb3 100644 --- a/src/primitive/streamcipher/chacha20.zig +++ b/src/primitive/streamcipher/chacha20.zig @@ -222,40 +222,22 @@ pub fn chacha20_block_function(ctx: *ChaCha20Ctx) void { // ----------------------------------- LITTLE ENDIAN HELPERS ----------------------------------- // fn chacha20_serialize(L: comptime_int, words: *const [L]u32, bytes: *[L * 4]u8) void { - if (comptime @import("builtin").target.cpu.arch.endian() == .little) { - @memcpy(bytes, @as(*const [L * 4]u8, @ptrCast(words))); - } else { - var tmp: [4]u8 = undefined; - for (0..L) |i| { - tmp = word_to_bytes_le(words[i]); - bytes[i * 4] = tmp[0]; - bytes[i * 4 + 1] = tmp[1]; - bytes[i * 4 + 2] = tmp[2]; - bytes[i * 4 + 3] = tmp[3]; - } - } + for (0..L) |i| + 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 { - if (comptime @import("builtin").target.cpu.arch.endian() == .little) { - @memcpy(@as(*[L * 4]u8, @ptrCast(words)), bytes); - } else { - for (0..L) |i| { - words[i] = bytes_to_word_le(@ptrCast(bytes[(i * 4)..(i * 4 + 4)])); - } - } + for (0..L) |i| + 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 (@as(u32, bytes[3]) << 24) | (@as(u32, bytes[2]) << 16) | (@as(u32, bytes[1]) << 8) | @as(u32, bytes[0]); + return std.mem.readInt(u32, bytes, .little); } fn word_to_bytes_le(word: u32) [4]u8 { var bytes: [4]u8 = undefined; - bytes[3] = @truncate(word >> 24); - bytes[2] = @truncate(word >> 16); - bytes[1] = @truncate(word >> 8); - bytes[0] = @truncate(word); + std.mem.writeInt(u32, &bytes, word, .little); return bytes; } diff --git a/src/primitive/streamcipher/index.zig b/src/primitive/streamcipher/index.zig new file mode 100644 index 0000000..9932fc6 --- /dev/null +++ b/src/primitive/streamcipher/index.zig @@ -0,0 +1,2 @@ +pub const chacha20 = @import("chacha20.zig"); +pub const salsa20 = @import("salsa20.zig"); diff --git a/src/primitive/streamcipher/salsa20.zig b/src/primitive/streamcipher/salsa20.zig index a9b75b9..22fcebb 100644 --- a/src/primitive/streamcipher/salsa20.zig +++ b/src/primitive/streamcipher/salsa20.zig @@ -201,40 +201,22 @@ pub fn salsa20_block_function(ctx: *Salsa20Ctx) void { // ----------------------------------- LITTLE ENDIAN HELPERS ----------------------------------- // fn salsa20_serialize(L: comptime_int, words: *const [L]u32, bytes: *[L * 4]u8) void { - if (comptime @import("builtin").target.cpu.arch.endian() == .little) { - @memcpy(bytes, @as(*const [L * 4]u8, @ptrCast(words))); - } else { - var tmp: [4]u8 = undefined; - for (0..L) |i| { - tmp = word_to_bytes_le(words[i]); - bytes[i * 4] = tmp[0]; - bytes[i * 4 + 1] = tmp[1]; - bytes[i * 4 + 2] = tmp[2]; - bytes[i * 4 + 3] = tmp[3]; - } - } + for (0..L) |i| + std.mem.writeInt(u32, @ptrCast(bytes[(i * 4)..(i * 4 + 4)]), words[i], .little); } fn salsa20_deserialize(L: comptime_int, bytes: *const [L * 4]u8, words: *[L]u32) void { - if (comptime @import("builtin").target.cpu.arch.endian() == .little) { - @memcpy(@as(*[L * 4]u8, @ptrCast(words)), bytes); - } else { - for (0..L) |i| { - words[i] = bytes_to_word_le(@ptrCast(bytes[(i * 4)..(i * 4 + 4)])); - } - } + for (0..L) |i| + 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 (@as(u32, bytes[3]) << 24) | (@as(u32, bytes[2]) << 16) | (@as(u32, bytes[1]) << 8) | @as(u32, bytes[0]); + return std.mem.readInt(u32, bytes, .little); } fn word_to_bytes_le(word: u32) [4]u8 { var bytes: [4]u8 = undefined; - bytes[3] = @truncate(word >> 24); - bytes[2] = @truncate(word >> 16); - bytes[1] = @truncate(word >> 8); - bytes[0] = @truncate(word); + std.mem.writeInt(u32, &bytes, word, .little); return bytes; } diff --git a/src/root.zig b/src/root.zig index 2847ad7..7106ca5 100644 --- a/src/root.zig +++ b/src/root.zig @@ -1,21 +1 @@ -const std = @import("std"); -const testing = std.testing; - -const CryptoError = error{ - InvalidBufferSize, -}; - -// rn just for build, later will be used by high-level API -pub const aes = @import("primitive/blockcipher/aes.zig"); -pub const chacha20 = @import("primitive/streamcipher/chacha20.zig"); -pub const salsa20 = @import("primitive/streamcipher/salsa20.zig"); -pub const sha = @import("primitive/digest/sha.zig"); - -// Leave this for later, maybe make a separate ffi module - -//export fn aes_128_encrypt_block_ffi(block_in: [*c]const u8, len_in: usize, block_out: [*c]u8, len_out: usize) !void { -// if (len_in != AES_128_BLOCK_SIZE or len_out != AES_128_BLOCK_SIZE) -// return CryptoError.InvalidBufferSize; -// -// aes_128_encrypt_block(block_in[0..AES_128_BLOCK_SIZE], block_out[0..AES_128_BLOCK_SIZE]); -//} +pub const primitive = @import("./primitive/index.zig"); diff --git a/src/test.zig b/src/test.zig new file mode 100644 index 0000000..6ebd4e2 --- /dev/null +++ b/src/test.zig @@ -0,0 +1,14 @@ +comptime { + _ = .{ + @import("./primitive/blockcipher/aes.zig"), + @import("./primitive/blockcipher/des.zig"), + @import("./primitive/digest/sha_1.zig"), + @import("./primitive/digest/sha_2.zig"), + @import("./primitive/streamcipher/chacha20.zig"), + @import("./primitive/streamcipher/salsa20.zig"), + }; +} + +test { + @import("std").testing.refAllDecls(@This()); +}