Generalize SHA, implement SHA-2.

This commit is contained in:
Milan Špinka
2025-01-29 02:25:23 +01:00
parent c4a83d297f
commit d7f3e3e015
3 changed files with 352 additions and 96 deletions

View File

@ -23,7 +23,7 @@ Most (**theoretical!**) users should directly use one of the cryptographic *prot
- Advanced Encryption Standard (FIPS 197): AES-128, AES-192, AES-256 - 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 - 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 - Salsa20: Salsa20/20 with 256-key, Salsa20/20 with 128-bit key
- Secure Hashing Algorithm: SHA-1 - Secure Hashing Algorithm: SHA-1, SHA-224, SHA-256
### Protocols ### Protocols
@ -40,7 +40,7 @@ Most (**theoretical!**) users should directly use one of the cryptographic *prot
- DES, 3DES - DES, 3DES
- Block cipher modes: CBC-PKCS7, CFB, OFB, CTR, GCM - Block cipher modes: CBC-PKCS7, CFB, OFB, CTR, GCM
- Poly1305 - Poly1305
- SHA-2, SHA-3 - SHA-3, HMAC
- BigIntegers & modular arithmetic - BigIntegers & modular arithmetic
- Cryptographically secure random BigInteger generation & primality testing - Cryptographically secure random BigInteger generation & primality testing
- Elliptic Curve groups (over Fp fields) - Elliptic Curve groups (over Fp fields)

View File

@ -1,14 +1,15 @@
const std = @import("std"); const std = @import("std");
const testing = std.testing; const testing = std.testing;
const builtin = @import("builtin");
const dbg = builtin.mode == builtin.Mode.Debug;
// ----------------------------------- ERROR DEFINITIONS ----------------------------------- // // ----------------------------------- ERROR DEFINITIONS ----------------------------------- //
pub const MessageLengthLimitExceeded = error.MessageLengthLimitExceeded; pub const MessageLengthLimitExceeded = error.MessageLengthLimitExceeded;
// ----------------------------------- SHA CONSTANTS ----------------------------------- // // ----------------------------------- SHA CONSTANTS ----------------------------------- //
//pub const ShaAlgorithm = enum { Sha1, Sha224, Sha256, Sha384, Sha512, Sha512_224, Sha512_256 };
const SHA_1_DIGEST_LENGTH = 160 / 8; const SHA_1_DIGEST_LENGTH = 160 / 8;
const SHA_224_DIGEST_LENGTH = 224 / 8; const SHA_224_DIGEST_LENGTH = 224 / 8;
const SHA_256_DIGEST_LENGTH = 256 / 8; const SHA_256_DIGEST_LENGTH = 256 / 8;
@ -49,17 +50,16 @@ const Sha1Ctx = struct {
const BLOCK_SIZE = 512 / 8; const BLOCK_SIZE = 512 / 8;
const MESSAGE_SCHEDULE_WORDS = 80; const MESSAGE_SCHEDULE_WORDS = 80;
message_schedule: [MESSAGE_SCHEDULE_WORDS]u32, hash: [SHA_1_DIGEST_LENGTH / @sizeOf(u32)]u32,
hash: [SHA_1_DIGEST_LENGTH / 4]u32,
message_buffer: [BLOCK_SIZE]u8, message_buffer: [BLOCK_SIZE]u8,
message_length: u64, message_length: u64,
}; };
const Sha2Ctx = struct { const Sha2Ctx = struct {
const BLOCK_SIZE = 512 / 8; const BLOCK_SIZE = 512 / 8;
const MESSAGE_SCHEDULE_WORDS = 64;
message_schedule: [16]u32, hash: [SHA_256_DIGEST_LENGTH / @sizeOf(u32)]u32,
hash: [8]u32,
message_buffer: [BLOCK_SIZE]u8, message_buffer: [BLOCK_SIZE]u8,
message_length: u64, message_length: u64,
@ -69,8 +69,7 @@ const Sha2Ctx = struct {
const Sha3Ctx = struct { const Sha3Ctx = struct {
const BLOCK_SIZE = 1024 / 8; const BLOCK_SIZE = 1024 / 8;
message_schedule: [16]u64, hash: [SHA_512_DIGEST_LENGTH / @sizeOf(u64)]u64,
hash: [8]u64,
message_buffer: [BLOCK_SIZE]u8, message_buffer: [BLOCK_SIZE]u8,
message_length: u128, message_length: u128,
@ -80,27 +79,21 @@ const Sha3Ctx = struct {
t_is_384: bool, t_is_384: bool,
}; };
pub fn sha1_new() Sha1Ctx { pub fn sha_generic_update(
var ctx = Sha1Ctx{ ShaCtx: type,
.message_schedule = undefined, limit_length_bits: comptime_int,
.hash = undefined, compression_function: *const fn (*ShaCtx) void,
.message_buffer = undefined, ctx: *ShaCtx,
.message_length = 0, message: []const u8,
}; ) !void {
@memcpy(&ctx.hash, &SHA_1_IV); if (ctx.message_length + message.len >= limit_length_bits / 8)
return ctx;
}
pub fn sha1_update(ctx: *Sha1Ctx, message: []const u8) !void {
// SHA-1 can digest a message of a maximum length of (2^64 - 1) bits due to the nature of its padding.
if (ctx.message_length + message.len >= ((1 << 64) / 8))
return MessageLengthLimitExceeded; return MessageLengthLimitExceeded;
const cnt_buffered_bytes = ctx.message_length % Sha1Ctx.BLOCK_SIZE; const cnt_buffered_bytes = ctx.message_length % ShaCtx.BLOCK_SIZE;
// Simplest case - the message did not fully fill the block size // Simplest case - the message does not fully fill the block yet,
// so it's just copied to the context and no hashing is done yet. // so it's just copied to the context buffer and no hashing is done.
if (cnt_buffered_bytes + message.len < Sha1Ctx.BLOCK_SIZE) { if (cnt_buffered_bytes + message.len < Sha2Ctx.BLOCK_SIZE) {
@memcpy( @memcpy(
ctx.message_buffer[cnt_buffered_bytes .. cnt_buffered_bytes + message.len], ctx.message_buffer[cnt_buffered_bytes .. cnt_buffered_bytes + message.len],
message[0..], message[0..],
@ -112,18 +105,21 @@ pub fn sha1_update(ctx: *Sha1Ctx, message: []const u8) !void {
// Otherwise: first, copy & hash the first block. // Otherwise: first, copy & hash the first block.
@memcpy( @memcpy(
ctx.message_buffer[cnt_buffered_bytes..], ctx.message_buffer[cnt_buffered_bytes..],
message[0 .. Sha1Ctx.BLOCK_SIZE - cnt_buffered_bytes], message[0 .. ShaCtx.BLOCK_SIZE - cnt_buffered_bytes],
); );
sha1_hash_one_block(ctx); compression_function(ctx);
var cnt_message_bytes_processed = Sha1Ctx.BLOCK_SIZE - cnt_buffered_bytes; var cnt_message_bytes_processed = ShaCtx.BLOCK_SIZE - cnt_buffered_bytes;
ctx.message_length += cnt_message_bytes_processed; ctx.message_length += cnt_message_bytes_processed;
// Then, as long as there is at least another block available, copy and hash it. // Then, as long as there is at least another block available, copy and hash it.
while (message.len - cnt_message_bytes_processed >= Sha1Ctx.BLOCK_SIZE) { while (message.len - cnt_message_bytes_processed >= ShaCtx.BLOCK_SIZE) {
@memcpy(ctx.message_buffer[0..], message[cnt_message_bytes_processed .. cnt_message_bytes_processed + Sha1Ctx.BLOCK_SIZE]); @memcpy(
sha1_hash_one_block(ctx); ctx.message_buffer[0..],
ctx.message_length += Sha1Ctx.BLOCK_SIZE; message[cnt_message_bytes_processed .. cnt_message_bytes_processed + ShaCtx.BLOCK_SIZE],
cnt_message_bytes_processed += Sha1Ctx.BLOCK_SIZE; );
compression_function(ctx);
ctx.message_length += ShaCtx.BLOCK_SIZE;
cnt_message_bytes_processed += ShaCtx.BLOCK_SIZE;
} }
// Finally, copy any leftover bytes to the context buffer without hashing. // Finally, copy any leftover bytes to the context buffer without hashing.
@ -135,27 +131,33 @@ pub fn sha1_update(ctx: *Sha1Ctx, message: []const u8) !void {
ctx.message_length += cnt_leftover_bytes; ctx.message_length += cnt_leftover_bytes;
} }
pub fn sha1_final(ctx: *Sha1Ctx, out: *[SHA_1_DIGEST_LENGTH]u8) void { pub fn sha_generic_final(
// The message length is stored in the padding as a 64-bit int. ShaCtx: type,
const message_length_bytes = 64 / 8; WordType: type,
MessageLengthIntType: type,
digest_length: comptime_int,
compression_function: *const fn (*ShaCtx) void,
ctx: *ShaCtx,
out: *[digest_length]u8,
) void {
const message_length_bytes = @typeInfo(MessageLengthIntType).Int.bits / 8;
const cnt_leftover_bytes = ctx.message_length % ShaCtx.BLOCK_SIZE;
const cnt_leftover_bytes = ctx.message_length % Sha1Ctx.BLOCK_SIZE; // Simple case: The leftover message is short enough* for the padding to only span one block.
// *: "Short enough" depends on the particular SHA variant.
// Simpler case: The leftover message is shorter than 446 bits if (cnt_leftover_bytes < ShaCtx.BLOCK_SIZE - message_length_bytes) {
// (or 55 bytes) and the padding only spans one block. const cnt_padding_bytes = ShaCtx.BLOCK_SIZE - message_length_bytes - cnt_leftover_bytes;
if (cnt_leftover_bytes < Sha1Ctx.BLOCK_SIZE - (message_length_bytes)) {
const cnt_padding_bytes = Sha1Ctx.BLOCK_SIZE - message_length_bytes - cnt_leftover_bytes;
// The padding (without the message length) is a single 1 bit followed by 0 bits. // The padding (without the message length) is a single 1 bit followed by 0 bits.
ctx.message_buffer[cnt_leftover_bytes] = 0x80; ctx.message_buffer[cnt_leftover_bytes] = 0x80;
@memset(ctx.message_buffer[cnt_leftover_bytes + 1 .. cnt_leftover_bytes + cnt_padding_bytes], 0x00); @memset(ctx.message_buffer[cnt_leftover_bytes + 1 .. cnt_leftover_bytes + cnt_padding_bytes], 0x00);
// The length is appended. // The length is appended.
const length = serialize_int_big_endian(u64, ctx.message_length * 8); const length = serialize_int_big_endian(MessageLengthIntType, ctx.message_length * 8);
@memcpy(ctx.message_buffer[cnt_leftover_bytes + cnt_padding_bytes ..], length[0..]); @memcpy(ctx.message_buffer[cnt_leftover_bytes + cnt_padding_bytes ..], length[0..]);
// The padded block is finally hashed. // The padded block is finally hashed.
sha1_hash_one_block(ctx); compression_function(ctx);
} }
// Otherwise, the padding spans 2 blocks in total // Otherwise, the padding spans 2 blocks in total
// and two more hash iterations are performed. // and two more hash iterations are performed.
@ -163,30 +165,67 @@ pub fn sha1_final(ctx: *Sha1Ctx, out: *[SHA_1_DIGEST_LENGTH]u8) void {
// Pad and hash the first block. // Pad and hash the first block.
ctx.message_buffer[cnt_leftover_bytes] = 0x80; ctx.message_buffer[cnt_leftover_bytes] = 0x80;
@memset(ctx.message_buffer[cnt_leftover_bytes + 1 ..], 0x00); @memset(ctx.message_buffer[cnt_leftover_bytes + 1 ..], 0x00);
sha1_hash_one_block(ctx); compression_function(ctx);
// Hash the second block. // Hash the second block.
@memset(ctx.message_buffer[0..(Sha1Ctx.BLOCK_SIZE - message_length_bytes)], 0x00); @memset(ctx.message_buffer[0..(ShaCtx.BLOCK_SIZE - message_length_bytes)], 0x00);
const length = serialize_int_big_endian(u64, ctx.message_length * 8); const length = serialize_int_big_endian(MessageLengthIntType, ctx.message_length * 8);
@memcpy(ctx.message_buffer[(Sha1Ctx.BLOCK_SIZE - message_length_bytes)..], length[0..]); @memcpy(ctx.message_buffer[(ShaCtx.BLOCK_SIZE - message_length_bytes)..], length[0..]);
sha1_hash_one_block(ctx); compression_function(ctx);
} }
// Serialize the result. // Serialize the result.
for (0..SHA_1_DIGEST_LENGTH / 4) |w| { const word_size = @typeInfo(WordType).Int.bits / 8;
const serialized_word = serialize_int_big_endian(u32, ctx.hash[w]); for (0..digest_length / word_size) |w| {
@memcpy(out[(w * 4)..(w * 4 + 4)], serialized_word[0..]); const serialized_word = serialize_int_big_endian(WordType, ctx.hash[w]);
@memcpy(out[(w * word_size)..((w + 1) * word_size)], serialized_word[0..]);
} }
} }
pub fn sha1_hash_one_block(ctx: *Sha1Ctx) void { // ----------------------------------- SHA-1 ----------------------------------- //
// Prepare the message schedule.
for (0..Sha1Ctx.BLOCK_SIZE / 4) |t| pub fn sha1_new() Sha1Ctx {
ctx.message_schedule[t] = deserialize_int_big_endian(u32, @ptrCast(ctx.message_buffer[(t * 4)..(t * 4 + 4)])); var ctx = Sha1Ctx{
for (Sha1Ctx.BLOCK_SIZE / 4..Sha1Ctx.MESSAGE_SCHEDULE_WORDS) |t| { .hash = undefined,
ctx.message_schedule[t] = rotl( .message_buffer = undefined,
.message_length = 0,
};
@memcpy(&ctx.hash, &SHA_1_IV);
return ctx;
}
pub fn sha1_update(ctx: *Sha1Ctx, message: []const u8) !void {
return sha_generic_update(
Sha1Ctx,
1 << 64,
&sha1_compress_block,
ctx,
message,
);
}
pub fn sha1_final(ctx: *Sha1Ctx, out: *[SHA_1_DIGEST_LENGTH]u8) void {
return sha_generic_final(
Sha1Ctx,
u32, u32,
ctx.message_schedule[t - 3] ^ ctx.message_schedule[t - 8] ^ ctx.message_schedule[t - 14] ^ ctx.message_schedule[t - 16], u64,
SHA_1_DIGEST_LENGTH,
sha1_compress_block,
ctx,
out,
);
}
pub fn sha1_compress_block(ctx: *Sha1Ctx) void {
// Prepare the message schedule.
var message_schedule: [Sha1Ctx.MESSAGE_SCHEDULE_WORDS]u32 = undefined;
for (0..Sha1Ctx.BLOCK_SIZE / 4) |t|
message_schedule[t] = deserialize_int_big_endian(u32, @ptrCast(ctx.message_buffer[(t * 4)..(t * 4 + 4)]));
for (Sha1Ctx.BLOCK_SIZE / 4..Sha1Ctx.MESSAGE_SCHEDULE_WORDS) |t| {
message_schedule[t] = rotl(
u32,
message_schedule[t - 3] ^ message_schedule[t - 8] ^ message_schedule[t - 14] ^ message_schedule[t - 16],
1, 1,
); );
} }
@ -200,7 +239,7 @@ pub fn sha1_hash_one_block(ctx: *Sha1Ctx) void {
// Perform the actual hashing. // Perform the actual hashing.
inline for (0..Sha1Ctx.MESSAGE_SCHEDULE_WORDS) |t| { inline for (0..Sha1Ctx.MESSAGE_SCHEDULE_WORDS) |t| {
const tmp = rotl(u32, a, 5) +% sha1_f(t, b, c, d) +% e +% sha1_k(t) +% ctx.message_schedule[t]; const tmp = rotl(u32, a, 5) +% sha1_f(t, b, c, d) +% e +% sha1_k(t) +% message_schedule[t];
e = d; e = d;
d = c; d = c;
c = rotl(u32, b, 30); c = rotl(u32, b, 30);
@ -236,21 +275,14 @@ inline fn sha1_k(t: comptime_int) u32 {
}; };
} }
// ----------------------------------- SHA-2 ----------------------------------- //
pub fn sha2_new(t: comptime_int) Sha2Ctx { pub fn sha2_new(t: comptime_int) Sha2Ctx {
if (comptime t != 224 and t != 256) if (comptime t != 224 and t != 256)
@compileError("SHA-2 context can only be initialized in 224-bit and 256-bit mode."); @compileError("SHA-2 context can only be initialized in 224-bit and 256-bit mode.");
var ctx = Sha2Ctx{ var ctx = Sha2Ctx{
.message_schedule = undefined,
.hash = undefined, .hash = undefined,
.a = if (t == 224) SHA_224_IV[0] else SHA_256_IV[0],
.b = if (t == 224) SHA_224_IV[1] else SHA_256_IV[1],
.c = if (t == 224) SHA_224_IV[2] else SHA_256_IV[2],
.d = if (t == 224) SHA_224_IV[3] else SHA_256_IV[3],
.e = if (t == 224) SHA_224_IV[4] else SHA_256_IV[4],
.f = if (t == 224) SHA_224_IV[5] else SHA_256_IV[5],
.g = if (t == 224) SHA_224_IV[6] else SHA_256_IV[6],
.h = if (t == 224) SHA_224_IV[7] else SHA_256_IV[7],
.message_buffer = undefined, .message_buffer = undefined,
.message_length = 0, .message_length = 0,
.t_is_224 = (t == 224), .t_is_224 = (t == 224),
@ -260,18 +292,112 @@ pub fn sha2_new(t: comptime_int) Sha2Ctx {
return ctx; return ctx;
} }
pub fn sha2_update(ctx: *Sha2Ctx, message: []const u8) !void {
// All variants of SHA-2 can digest a message of a maximum length of (2^64 - 1) bits
// due to the nature of its padding (which is identical to SHA-1).
return sha_generic_update(
Sha2Ctx,
1 << 64,
&sha2_compress_block,
ctx,
message,
);
}
fn sha2_compress_block(ctx: *Sha2Ctx) void {
// Prepare the message schedule.
var message_schedule: [Sha2Ctx.MESSAGE_SCHEDULE_WORDS]u32 = undefined;
for (0..Sha2Ctx.BLOCK_SIZE / 4) |t|
message_schedule[t] = deserialize_int_big_endian(u32, @ptrCast(ctx.message_buffer[(t * 4)..(t * 4 + 4)]));
for (Sha2Ctx.BLOCK_SIZE / 4..Sha2Ctx.MESSAGE_SCHEDULE_WORDS) |t| {
message_schedule[t] = sha2_sigma_256_1(message_schedule[t - 2]) +% message_schedule[t - 7] +% sha2_sigma_256_0(message_schedule[t - 15]) +% message_schedule[t - 16];
}
// Initialize working variables.
var a = ctx.hash[0];
var b = ctx.hash[1];
var c = ctx.hash[2];
var d = ctx.hash[3];
var e = ctx.hash[4];
var f = ctx.hash[5];
var g = ctx.hash[6];
var h = ctx.hash[7];
// Perform the actual hashing.
inline for (0..Sha2Ctx.MESSAGE_SCHEDULE_WORDS) |t| {
const tmp1 = h +% sha2_Sigma_256_1(e) +% ch(u32, e, f, g) +% sha2_k_256[t] +% message_schedule[t];
const tmp2 = sha2_Sigma_256_0(a) +% maj(u32, a, b, c);
h = g;
g = f;
f = e;
e = d +% tmp1;
d = c;
c = b;
b = a;
a = tmp1 +% tmp2;
}
// Add the result to the previous hash state.
ctx.hash[0] +%= a;
ctx.hash[1] +%= b;
ctx.hash[2] +%= c;
ctx.hash[3] +%= d;
ctx.hash[4] +%= e;
ctx.hash[5] +%= f;
ctx.hash[6] +%= g;
ctx.hash[7] +%= h;
}
const sha2_k_256 = [Sha2Ctx.MESSAGE_SCHEDULE_WORDS]u32{
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
};
fn sha2_Sigma_256_0(x: u32) u32 {
return rotr(u32, x, 2) ^ rotr(u32, x, 13) ^ rotr(u32, x, 22);
}
fn sha2_Sigma_256_1(x: u32) u32 {
return rotr(u32, x, 6) ^ rotr(u32, x, 11) ^ rotr(u32, x, 25);
}
fn sha2_sigma_256_0(x: u32) u32 {
return rotr(u32, x, 7) ^ rotr(u32, x, 18) ^ shr(u32, x, 3);
}
fn sha2_sigma_256_1(x: u32) u32 {
return rotr(u32, x, 17) ^ rotr(u32, x, 19) ^ shr(u32, x, 10);
}
pub fn sha224_new() Sha2Ctx { pub fn sha224_new() Sha2Ctx {
return sha2_new(224); return sha2_new(224);
} }
pub fn sha224_update(ctx: *Sha2Ctx, message: []const u8) !void { pub fn sha224_update(ctx: *Sha2Ctx, message: []const u8) !void {
// TODO if (dbg and !ctx.t_is_224)
_ = .{ ctx, message }; @panic("Debug: Attempt to call sha224_update on a SHA-256 context.");
return sha2_update(ctx, message);
} }
pub fn sha224_final(ctx: *Sha1Ctx, out: [SHA_224_DIGEST_LENGTH]u8) void { pub fn sha224_final(ctx: *Sha2Ctx, out: *[SHA_224_DIGEST_LENGTH]u8) void {
// TODO if (dbg and !ctx.t_is_224)
_ = .{ ctx, out }; @panic("Debug: Attempt to call sha224_final on a SHA-256 context.");
return sha_generic_final(
Sha2Ctx,
u32,
u64,
SHA_224_DIGEST_LENGTH,
&sha2_compress_block,
ctx,
out,
);
} }
pub fn sha256_new() Sha2Ctx { pub fn sha256_new() Sha2Ctx {
@ -279,18 +405,32 @@ pub fn sha256_new() Sha2Ctx {
} }
pub fn sha256_update(ctx: *Sha2Ctx, message: []const u8) !void { pub fn sha256_update(ctx: *Sha2Ctx, message: []const u8) !void {
// TODO if (dbg and ctx.t_is_224)
_ = .{ ctx, message }; @panic("Debug: Attempt to call sha256_update on a SHA-224 context.");
return sha2_update(ctx, message);
} }
pub fn sha256_final(ctx: *Sha2Ctx, out: [SHA_256_DIGEST_LENGTH]u8) void { pub fn sha256_final(ctx: *Sha2Ctx, out: *[SHA_256_DIGEST_LENGTH]u8) void {
// TODO if (dbg and ctx.t_is_224)
_ = .{ ctx, out }; @panic("Debug: Attempt to call sha256_final on a SHA-224 context.");
return sha_generic_final(
Sha2Ctx,
u32,
u64,
SHA_256_DIGEST_LENGTH,
&sha2_compress_block,
ctx,
out,
);
} }
// ----------------------------------- SHA-3 ----------------------------------- //
pub fn sha3_new(t: comptime_int) Sha3Ctx { pub fn sha3_new(t: comptime_int) Sha3Ctx {
if (comptime t != 224 and t != 256 and t != 384 and t != 512) if (comptime t != 224 and t != 256 and t != 384 and t != 512)
@compileError("SHA-3 context can only be initialized in 224, 256, 384 and 512-bit mode."); @compileError(
"SHA-3 context can currently only be initialized in 224, 256, 384 and 512-bit mode.",
);
const iv: *const [Sha3Ctx.BLOCK_SIZE]u64 = switch (t) { const iv: *const [Sha3Ctx.BLOCK_SIZE]u64 = switch (t) {
224 => &SHA_512_224_IV, 224 => &SHA_512_224_IV,
@ -301,16 +441,7 @@ pub fn sha3_new(t: comptime_int) Sha3Ctx {
}; };
var ctx = Sha3Ctx{ var ctx = Sha3Ctx{
.message_schedule = undefined,
.hash = undefined, .hash = undefined,
.a = iv[0],
.b = iv[1],
.c = iv[2],
.d = iv[3],
.e = iv[4],
.f = iv[5],
.g = iv[6],
.h = iv[7],
.message_buffer = undefined, .message_buffer = undefined,
.message_length = 0, .message_length = 0,
.t_is_224 = (t == 224), .t_is_224 = (t == 224),
@ -331,7 +462,7 @@ pub fn sha384_update(ctx: *Sha3Ctx, message: []const u8) !void {
_ = .{ ctx, message }; _ = .{ ctx, message };
} }
pub fn sha384_final(ctx: *Sha3Ctx, out: [SHA_384_DIGEST_LENGTH]u8) void { pub fn sha384_final(ctx: *Sha3Ctx, out: *[SHA_384_DIGEST_LENGTH]u8) void {
// TODO // TODO
_ = .{ ctx, out }; _ = .{ ctx, out };
} }
@ -345,7 +476,7 @@ pub fn sha512_update(ctx: *Sha3Ctx, message: []const u8) !void {
_ = .{ ctx, message }; _ = .{ ctx, message };
} }
pub fn sha512_final(ctx: *Sha3Ctx, out: [SHA_512_DIGEST_LENGTH]u8) void { pub fn sha512_final(ctx: *Sha3Ctx, out: *[SHA_512_DIGEST_LENGTH]u8) void {
// TODO // TODO
_ = .{ ctx, out }; _ = .{ ctx, out };
} }
@ -359,7 +490,7 @@ pub fn sha512_224_update(ctx: *Sha3Ctx, message: []const u8) !void {
_ = .{ ctx, message }; _ = .{ ctx, message };
} }
pub fn sha512_224_final(ctx: *Sha3Ctx, out: [SHA_512_224_DIGEST_LENGTH]u8) void { pub fn sha512_224_final(ctx: *Sha3Ctx, out: *[SHA_512_224_DIGEST_LENGTH]u8) void {
// TODO // TODO
_ = .{ ctx, out }; _ = .{ ctx, out };
} }
@ -373,12 +504,12 @@ pub fn sha512_256_update(ctx: *Sha3Ctx, message: []const u8) !void {
_ = .{ ctx, message }; _ = .{ ctx, message };
} }
pub fn sha512_256_final(ctx: *Sha3Ctx, out: [SHA_512_256_DIGEST_LENGTH]u8) void { pub fn sha512_256_final(ctx: *Sha3Ctx, out: *[SHA_512_256_DIGEST_LENGTH]u8) void {
// TODO // TODO
_ = .{ ctx, out }; _ = .{ ctx, out };
} }
// ----------------------------------- Non-linear functions ----------------------------------- // // ----------------------------------- LOGICAL FUNCTIONS ----------------------------------- //
fn ch(T: type, x: T, y: T, z: T) T { fn ch(T: type, x: T, y: T, z: T) T {
return (x & y) ^ (~x & z); return (x & y) ^ (~x & z);
@ -400,6 +531,16 @@ fn rotl(T: type, word: T, bits: comptime_int) T {
return (word << bits) | (word >> (@bitSizeOf(T) - bits)); return (word << bits) | (word >> (@bitSizeOf(T) - bits));
} }
fn rotr(T: type, word: T, bits: comptime_int) T {
if (comptime bits >= @bitSizeOf(T))
@compileError("Will not rotate word right by more bits than it has!");
return rotl(T, word, @bitSizeOf(T) - bits);
}
fn shr(T: type, word: T, bits: comptime_int) T {
return word >> bits;
}
fn serialize_int_big_endian(T: type, int: T) [@sizeOf(T)]u8 { fn serialize_int_big_endian(T: type, int: T) [@sizeOf(T)]u8 {
var res: [@sizeOf(T)]u8 = undefined; var res: [@sizeOf(T)]u8 = undefined;
for (0..@sizeOf(T)) |i| for (0..@sizeOf(T)) |i|
@ -492,3 +633,115 @@ test "SHA-1 maximum length violation (simulated)" {
ctx.message_length = (1 << 61) - 1; // 2^64 - 8 bits ctx.message_length = (1 << 61) - 1; // 2^64 - 8 bits
try testing.expectError(MessageLengthLimitExceeded, sha1_update(&ctx, "a")); try testing.expectError(MessageLengthLimitExceeded, sha1_update(&ctx, "a"));
} }
// https://www.di-mgt.com.au/sha_testvectors.html
test "SHA-224 basic test" {
const tests = [_]struct {
message: []const u8,
hash: *const [2 * SHA_224_DIGEST_LENGTH]u8,
}{
.{ .message = "", .hash = "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f" },
.{ .message = "abc", .hash = "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7" },
.{
.message = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
.hash = "75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525",
},
.{
.message = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
.hash = "c97ca9a559850ce97a04a96def6d99a9e0e0e2ab14e6b8df265fc0b3",
},
};
var digest_buffer: [SHA_224_DIGEST_LENGTH]u8 = undefined;
for (tests) |t| {
var ctx = sha224_new();
try sha224_update(&ctx, t.message);
sha224_final(&ctx, &digest_buffer);
const reference = hex_to_bytes(SHA_224_DIGEST_LENGTH, t.hash);
try testing.expectEqualSlices(u8, reference[0..], digest_buffer[0..]);
}
}
test "SHA-224 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-224 block size in bits] / 8 - 1) concatenated 'a's.
const reference = [64]*const [2 * SHA_224_DIGEST_LENGTH]u8{ "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", "abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5", "2ef29e646f6de95de993a59eb1a94cbf52986892949a0abc015a01f7", "ed782653bfec275cf37d027511a68cece08d1e53df1360c762ce043a", "fb56e0a07d1d4ba4a73d42a07d671f65f96513dc12f9d25f2b5ee86e", "04c15392c64c3db52f9a7fcfb7c0c370900e0308007617899430d088", "2886915dd9dedb0ce015f500ffb570901a69a8357728f893eef45344", "b1de26e908098a9b462c11fdd577613d356a7ff06ddb42fda3ab002b", "57dde80cb5dd10cee89424d7d9d8c4fd7949c882da78110e660dec06", "be63cd48d0b068927261c401a33a386136911c3420a5c335e00599fd", "194c4c5045aee1019b77cb1be88ac68034eb2ddd5a8368054141a11f", "5fa4fb5daff0a9b069061839e5605caff0446465f82268775a226333", "85e4d4fcca88691c8008ce19bd9a3de6b9814dd055c8d21bfd4037bb", "da79142fa39f7f11b990aa9dc4994b6e9ce49454058d5423869acf4c", "bd3278d31dec829db380cfe7d7100fecb4b853f054684687f4c27717", "e541fff1289f562eff589da3c8358d91e4dd589bf5dcc37555eb84fe", "0e0a4176c0c6966926acc6fccd2febd418a407df26f76645139d1320", "d5f1096a707710cf5038ed370f7839543eaea0ed054c7484319e2f9c", "e3a0b5557f7894cbb18d6ed06268242212f098d61e179e80a5e96dbc", "fbb6819175a23811c01073f6142af14e80f2d0c598bed6649f307e1f", "43586eff52cbaf9f22482f34a9437ff45bd2e7312ad586b3dd82802f", "2b9d728b01e2c27a40c5fc27d51d5c93f6a160f0f25da8d924e2b570", "eb7ff45dd3d5871ffd6aee24400367cc835aa0d2311d46b2a48b0424", "ac4f39fb481df8f86b6ddf6e527e61893d259b49810416584b468d22", "c4da7f3f63b9da485b734a270f1af6d132b15f177e06445faec5586b", "91a67814117203c32cfc333869bc74f8c721c10672c3516042d88a0b", "7824f1cc7591643231cefe91bbf6fa88b233bf1e8229a59a2cd01c15", "735d169738ebd05edc44cc49a6a99352815cafec12c10d5bd27e8359", "919523d5847d1ee4f9d72f6bd86e3d1bfb785831237a9d580b87448c", "4c166aebdf561231f2b679d8e8457667c0f374043de76d8e7306cb49", "31723190cc4bc6ae686caeea44c54b7b50e30941b8dca3404fe180d8", "f9b67111fdab7e860d1dbe801efa9b75b563e0ee606a9cbabc1288b9", "37e7740b3af132a8edc5e1636817ce1625b831e0ffd47e5c93d4e3b8", "21093fb6a83a36a2b55afa76da42d66de4ebc3b99fb340739d068500", "62d7fcd624a2c1d674ead77584bd66e06d2a6ed160702a594dd6a26e", "507fba56abf0fe3856d0d61527d2a96b18e234bad594bdc969fbfdf4", "6d3a8a79bcc6222986f3b3f17b716774de66d321d51ec34e49995b18", "b622cd8b687970603662b70401a36e464f6860ad337be21c86f17dce", "9c63438f755166862211cc708592f918d31c24e7e70080d6d08e3685", "0126a8fdfaea25bb65fa0b8d6586cd64eee956369b5d2c814607e9f9", "47e263d6419979086bd9a8c4f93591cba373912b68e2ae86085bd2df", "7d8c659cacc50ffc474948b79d65ed98bdab16f6b8a511d184593c86", "da1619b92df1a450be79a14a21e270cb2257230f7dfdc61128e0b49e", "babea0fecfea0aa82d1580abca47dad191f5b9d5a51788d64f678393", "61dd4e5d414a5ae61e76a9b7524223f3cdaf7c9c02b73c175b3e03dd", "fc50179169329ce825404d027fdab44058efc9f28ccddd694a31960e", "b5b7bef9dba5e6a57cecdc03d9a539667f3ca131343de3b6763d7463", "58756e846cce4e08b2ae1103ec3dd2c5755c15f94c1127782dde82c5", "73e0009122e9f4d311459277b81009e9cecc4b3dccf785d4ad476a14", "c0af565a56aeccfd2d40455f20d2c9431a7ab88c61e94973c97cff91", "df427221dc453d5c1466081d9d6e9da3155d5d0dff2a90eb0425036c", "7820fc9fc80c5ed788738da53fbfa6cf1fa981d656a3bb1e68cdf281", "163a72bc0462179bf0486f8a139da514913670d12bbe1d84efc44556", "3fae7c2d692c1610c4a20a17a790d256c3b0071bcdf6fb7fb9538681", "282e1dec88fa36a1070631cca69e3c08a5e18e29fb0b6f6927fbcc0d", "fb0bd626a70c28541dfa781bb5cc4d7d7f56622a58f01a0b1ddd646f", "d40854fc9caf172067136f2e29e1380b14626bf6f0dd06779f820dcd", "b5d09534784ab6578128bce7f28a96a56e3b45c4f734f74739076249", "00df3f1eaa489fd28a9de6e6d7b55402c4e3a56928c5043d77240237", "a82137820aaae9e66f277c3a9254f4a6078c47b410bc9d9a761c2e0b", "efda4316fe2d457d622cf1fc42993d41566f77449b7494b38e250c41", "54e3b540f6792b6a4570f5225717686fbf670fd0dfd3802e4ace9d77", "0daa67402af98b9988c65471b2589dbcdd8bb39569ed77c592aca4a4", "1d4e051f4d6fed2a63fd2421e65834cec00d64456553de3496ae8b1d" };
var digest_buffer: [SHA_224_DIGEST_LENGTH]u8 = undefined;
for (0..64) |i| {
var ctx = sha224_new();
for (0..i) |_|
try sha224_update(&ctx, "a");
sha224_final(&ctx, &digest_buffer);
const ref = hex_to_bytes(SHA_224_DIGEST_LENGTH, reference[i]);
try testing.expectEqualSlices(u8, ref[0..], digest_buffer[0..]);
}
}
test "SHA-224 maximum length violation (simulated)" {
var ctx = sha224_new();
ctx.message_length = (1 << 61) - 1; // 2^64 - 8 bits
try testing.expectError(MessageLengthLimitExceeded, sha224_update(&ctx, "a"));
}
// https://www.di-mgt.com.au/sha_testvectors.html
test "SHA-256 basic test" {
const tests = [_]struct {
message: []const u8,
hash: *const [2 * SHA_256_DIGEST_LENGTH]u8,
}{
.{ .message = "", .hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" },
.{ .message = "abc", .hash = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" },
.{
.message = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
.hash = "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1",
},
.{
.message = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
.hash = "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1",
},
};
var digest_buffer: [SHA_256_DIGEST_LENGTH]u8 = undefined;
for (tests) |t| {
var ctx = sha256_new();
try sha256_update(&ctx, t.message);
sha256_final(&ctx, &digest_buffer);
const reference = hex_to_bytes(SHA_256_DIGEST_LENGTH, t.hash);
try testing.expectEqualSlices(u8, reference[0..], digest_buffer[0..]);
}
}
test "SHA-256 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-256 block size in bits] / 8 - 1) concatenated 'a's.
const reference = [64]*const [2 * SHA_256_DIGEST_LENGTH]u8{ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", "961b6dd3ede3cb8ecbaacbd68de040cd78eb2ed5889130cceb4c49268ea4d506", "9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", "61be55a8e2f6b4e172338bddf184d6dbee29c98853e0a0485ecee7f27b9af0b4", "ed968e840d10d2d313a870bc131a4e2c311d7ad09bdf32b3418147221f51a6e2", "ed02457b5c41d964dbd2f2a609d63fe1bb7528dbe55e1abf5b52c249cd735797", "e46240714b5db3a23eee60479a623efba4d633d27fe4f03c904b9e219a7fbe60", "1f3ce40415a2081fa3eee75fc39fff8e56c22270d1a978a7249b592dcebd20b4", "f2aca93b80cae681221f0445fa4e2cae8a1f9f8fa1e1741d9639caad222f537d", "bf2cb58a68f684d95a3b78ef8f661c9a4e5b09e82cc8f9cc88cce90528caeb27", "28cb017dfc99073aa1b47c1b30f413e3ce774c4991eb4158de50f9dbb36d8043", "f24abc34b13fade76e805799f71187da6cd90b9cac373ae65ed57f143bd664e5", "a689d786e81340e45511dec6c7ab2d978434e5db123362450fe10cfac70d19d0", "82cab7df0abfb9d95dca4e5937ce2968c798c726fea48c016bf9763221efda13", "ef2df0b539c6c23de0f4cbe42648c301ae0e22e887340a4599fb4ef4e2678e48", "0c0beacef8877bbf2416eb00f2b5dc96354e26dd1df5517320459b1236860f8c", "b860666ee2966dd8f903be44ee605c6e1366f926d9f17a8f49937d11624eb99d", "c926defaaa3d13eda2fc63a553bb7fb7326bece6e7cb67ca5296e4727d89bab4", "a0b4aaab8a966e2193ba172d68162c4656860197f256b5f45f0203397ff3f99c", "42492da06234ad0ac76f5d5debdb6d1ae027cffbe746a1c13b89bb8bc0139137", "7df8e299c834de198e264c3e374bc58ecd9382252a705c183beb02f275571e3b", "ec7c494df6d2a7ea36668d656e6b8979e33641bfea378c15038af3964db057a3", "897d3e95b65f26676081f8b9f3a98b6ee4424566303e8d4e7c7522ebae219eab", "09f61f8d9cd65e6a0c258087c485b6293541364e42bd97b2d7936580c8aa3c54", "2f521e2a7d0bd812cbc035f4ed6806eb8d851793b04ba147e8f66b72f5d1f20f", "9976d549a25115dab4e36d0c1fb8f31cb07da87dd83275977360eb7dc09e88de", "cc0616e61cbd6e8e5e34e9fb2d320f37de915820206f5696c31f1fbd24aa16de", "9c547cb8115a44883b9f70ba68f75117cd55359c92611875e386f8af98c172ab", "6913c9c7fd42fe23df8b6bcd4dbaf1c17748948d97f2980b432319c39eddcf6c", "3a54fc0cbc0b0ef48b6507b7788096235d10292dd3ae24e22f5aa062d4f9864a", "61c60b487d1a921e0bcc9bf853dda0fb159b30bf57b2e2d2c753b00be15b5a09", "3ba3f5f43b92602683c19aee62a20342b084dd5971ddd33808d81a328879a547", "852785c805c77e71a22340a54e9d95933ed49121e7d2bf3c2d358854bc1359ea", "a27c896c4859204843166af66f0e902b9c3b3ed6d2fd13d435abc020065c526f", "629362afc62c74497caed2272e30f8125ecd0965f8d8d7cfc4e260f7f8dd319d", "22c1d24bcd03e9aee9832efccd6da613fc702793178e5f12c945c7b67ddda933", "21ec055b38ce759cd4d0f477e9bdec2c5b8199945db4439bae334a964df6246c", "365a9c3e2c2af0a56e47a9dac51c2c5381bf8f41273bad3175e0e619126ad087", "b4d5e56e929ba4cda349e9274e3603d0be246b82016bca20f363963c5f2d6845", "e33cdf9c7f7120b98e8c78408953e07f2ecd183006b5606df349b4c212acf43e", "c0f8bd4dbc2b0c03107c1c37913f2a7501f521467f45dd0fef6958e9a4692719", "7a538607fdaab9296995929f451565bbb8142e1844117322aafd2b3d76b01aff", "66d34fba71f8f450f7e45598853e53bfc23bbd129027cbb131a2f4ffd7878cd0", "16849877c6c21ef0bfa68e4f6747300ddb171b170b9f00e189edc4c2fc4db93e", "52789e3423b72beeb898456a4f49662e46b0cbb960784c5ef4b1399d327e7c27", "6643110c5628fff59edf76d82d5bf573bf800f16a4d65dfb1e5d6f1a46296d0b", "11eaed932c6c6fddfc2efc394e609facf4abe814fc6180d03b14fce13a07d0e5", "97daac0ee9998dfcad6c9c0970da5ca411c86233a944c25b47566f6a7bc1ddd5", "8f9bec6a62dd28ebd36d1227745592de6658b36974a3bb98a4c582f683ea6c42", "160b4e433e384e05e537dc59b467f7cb2403f0214db15c5db58862a3f1156d2e", "bfc5fe0e360152ca98c50fab4ed7e3078c17debc2917740d5000913b686ca129", "6c1b3dc7a706b9dc81352a6716b9c666c608d8626272c64b914ab05572fc6e84", "abe346a7259fc90b4c27185419628e5e6af6466b1ae9b5446cac4bfc26cf05c4", "a3f01b6939256127582ac8ae9fb47a382a244680806a3f613a118851c1ca1d47", "9f4390f8d30c2dd92ec9f095b65e2b9ae9b0a925a5258e241c9f1e910f734318", "b35439a4ac6f0948b6d6f9e3c6af0f5f590ce20f1bde7090ef7970686ec6738a", "f13b2d724659eb3bf47f2dd6af1accc87b81f09f59f2b75e5c0bed6589dfe8c6", "d5c039b748aa64665782974ec3dc3025c042edf54dcdc2b5de31385b094cb678", "111bb261277afd65f0744b247cd3e47d386d71563d0ed995517807d5ebd4fba3", "11ee391211c6256460b6ed375957fadd8061cafbb31daf967db875aebd5aaad4", "35d5fc17cfbbadd00f5e710ada39f194c5ad7c766ad67072245f1fad45f0f530", "f506898cc7c2e092f9eb9fadae7ba50383f5b46a2a4fe5597dbb553a78981268", "7d3e74a05d7db15bce4ad9ec0658ea98e3f06eeecf16b4c6fff2da457ddc2f34" };
var digest_buffer: [SHA_256_DIGEST_LENGTH]u8 = undefined;
for (0..64) |i| {
var ctx = sha256_new();
for (0..i) |_|
try sha256_update(&ctx, "a");
sha256_final(&ctx, &digest_buffer);
const ref = hex_to_bytes(SHA_256_DIGEST_LENGTH, reference[i]);
try testing.expectEqualSlices(u8, ref[0..], digest_buffer[0..]);
}
}
test "SHA-256 maximum length violation (simulated)" {
var ctx = sha256_new();
ctx.message_length = (1 << 61) - 1; // 2^64 - 8 bits
try testing.expectError(MessageLengthLimitExceeded, sha256_update(&ctx, "a"));
}

View File

@ -6,7 +6,10 @@ const CryptoError = error{
}; };
// rn just for build, later will be used by high-level API // rn just for build, later will be used by high-level API
const aes = @import("primitive/blockcipher/aes.zig"); 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 // Leave this for later, maybe make a separate ffi module