To learn Zig I implemented some crypto functions in Zig. It uses unique Zig comptime features to reduce code duplication
Last active
June 8, 2021 22:50
-
-
Save pingiun/7e6a1e067f997f296bed9bf0ff9900f8 to your computer and use it in GitHub Desktop.
Zig sha256 and sha512 implementations without unnecessary code duplication
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const assert = std.debug.assert; | |
const log = std.log; | |
const math = std.math; | |
const mem = std.mem; | |
const testing = std.testing; | |
const print = std.debug.print; | |
const TypeInfo = std.builtin.TypeInfo; | |
fn Sha2Parameters(comptime intsize: type, comptime messagesize: usize) type { | |
return struct { | |
H0: intsize, | |
H1: intsize, | |
H2: intsize, | |
H3: intsize, | |
H4: intsize, | |
H5: intsize, | |
H6: intsize, | |
H7: intsize, | |
K: [messagesize]intsize, | |
rounds: usize, | |
chunksize: usize, | |
// These parameters have bad names, but there really are not much better names | |
// The only identity they have is where they are used in the round algorithm | |
S0_rotate_0: usize, | |
S0_rotate_1: usize, | |
S0_rotate_2: usize, | |
S1_rotate_0: usize, | |
S1_rotate_1: usize, | |
S1_rotate_2: usize, | |
s0_rotate_0: usize, | |
s0_rotate_1: usize, | |
s0_shift: usize, | |
s1_rotate_0: usize, | |
s1_rotate_1: usize, | |
s1_shift: usize, | |
}; | |
} | |
const Sha256 = Sha2Parameters(u32, 64){ | |
.H0 = 0x6a09e667, | |
.H1 = 0xbb67ae85, | |
.H2 = 0x3c6ef372, | |
.H3 = 0xa54ff53a, | |
.H4 = 0x510e527f, | |
.H5 = 0x9b05688c, | |
.H6 = 0x1f83d9ab, | |
.H7 = 0x5be0cd19, | |
.K = [_]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, | |
}, | |
.rounds = 64, | |
.chunksize = 64, | |
.S0_rotate_0 = 2, | |
.S0_rotate_1 = 13, | |
.S0_rotate_2 = 22, | |
.S1_rotate_0 = 6, | |
.S1_rotate_1 = 11, | |
.S1_rotate_2 = 25, | |
.s0_rotate_0 = 7, | |
.s0_rotate_1 = 18, | |
.s0_shift = 3, | |
.s1_rotate_0 = 17, | |
.s1_rotate_1 = 19, | |
.s1_shift = 10, | |
}; | |
const Sha512 = Sha2Parameters(u64, 80){ | |
.H0 = 0x6a09e667f3bcc908, | |
.H1 = 0xbb67ae8584caa73b, | |
.H2 = 0x3c6ef372fe94f82b, | |
.H3 = 0xa54ff53a5f1d36f1, | |
.H4 = 0x510e527fade682d1, | |
.H5 = 0x9b05688c2b3e6c1f, | |
.H6 = 0x1f83d9abfb41bd6b, | |
.H7 = 0x5be0cd19137e2179, | |
.K = [_]u64{ | |
0x428a2f98d728ae22, | |
0x7137449123ef65cd, | |
0xb5c0fbcfec4d3b2f, | |
0xe9b5dba58189dbbc, | |
0x3956c25bf348b538, | |
0x59f111f1b605d019, | |
0x923f82a4af194f9b, | |
0xab1c5ed5da6d8118, | |
0xd807aa98a3030242, | |
0x12835b0145706fbe, | |
0x243185be4ee4b28c, | |
0x550c7dc3d5ffb4e2, | |
0x72be5d74f27b896f, | |
0x80deb1fe3b1696b1, | |
0x9bdc06a725c71235, | |
0xc19bf174cf692694, | |
0xe49b69c19ef14ad2, | |
0xefbe4786384f25e3, | |
0x0fc19dc68b8cd5b5, | |
0x240ca1cc77ac9c65, | |
0x2de92c6f592b0275, | |
0x4a7484aa6ea6e483, | |
0x5cb0a9dcbd41fbd4, | |
0x76f988da831153b5, | |
0x983e5152ee66dfab, | |
0xa831c66d2db43210, | |
0xb00327c898fb213f, | |
0xbf597fc7beef0ee4, | |
0xc6e00bf33da88fc2, | |
0xd5a79147930aa725, | |
0x06ca6351e003826f, | |
0x142929670a0e6e70, | |
0x27b70a8546d22ffc, | |
0x2e1b21385c26c926, | |
0x4d2c6dfc5ac42aed, | |
0x53380d139d95b3df, | |
0x650a73548baf63de, | |
0x766a0abb3c77b2a8, | |
0x81c2c92e47edaee6, | |
0x92722c851482353b, | |
0xa2bfe8a14cf10364, | |
0xa81a664bbc423001, | |
0xc24b8b70d0f89791, | |
0xc76c51a30654be30, | |
0xd192e819d6ef5218, | |
0xd69906245565a910, | |
0xf40e35855771202a, | |
0x106aa07032bbd1b8, | |
0x19a4c116b8d2d0c8, | |
0x1e376c085141ab53, | |
0x2748774cdf8eeb99, | |
0x34b0bcb5e19b48a8, | |
0x391c0cb3c5c95a63, | |
0x4ed8aa4ae3418acb, | |
0x5b9cca4f7763e373, | |
0x682e6ff3d6b2b8a3, | |
0x748f82ee5defb2fc, | |
0x78a5636f43172f60, | |
0x84c87814a1f0ab72, | |
0x8cc702081a6439ec, | |
0x90befffa23631e28, | |
0xa4506cebde82bde9, | |
0xbef9a3f7b2c67915, | |
0xc67178f2e372532b, | |
0xca273eceea26619c, | |
0xd186b8c721c0c207, | |
0xeada7dd6cde0eb1e, | |
0xf57d4f7fee6ed178, | |
0x06f067aa72176fba, | |
0x0a637dc5a2c898a6, | |
0x113f9804bef90dae, | |
0x1b710b35131c471b, | |
0x28db77f523047d84, | |
0x32caab7b40c72493, | |
0x3c9ebe0a15c9bebc, | |
0x431d67c49c100d4c, | |
0x4cc5d4becb3e42b6, | |
0x597f299cfc657e2a, | |
0x5fcb6fab3ad6faec, | |
0x6c44198c4a475817, | |
}, | |
.rounds = 80, | |
.chunksize = 128, | |
.S0_rotate_0 = 28, | |
.S0_rotate_1 = 34, | |
.S0_rotate_2 = 39, | |
.S1_rotate_0 = 14, | |
.S1_rotate_1 = 18, | |
.S1_rotate_2 = 41, | |
.s0_rotate_0 = 1, | |
.s0_rotate_1 = 8, | |
.s0_shift = 7, | |
.s1_rotate_0 = 19, | |
.s1_rotate_1 = 61, | |
.s1_shift = 6, | |
}; | |
fn ShaHasher(comptime intsize: type, comptime messagesize: usize, comptime params: Sha2Parameters(intsize, messagesize)) type { | |
return struct { | |
const Self = @This(); | |
h0: intsize, | |
h1: intsize, | |
h2: intsize, | |
h3: intsize, | |
h4: intsize, | |
h5: intsize, | |
h6: intsize, | |
h7: intsize, | |
rest: [params.chunksize]u8, | |
restlen: usize, | |
length: @Type(TypeInfo{ .Int = TypeInfo.Int{ .signedness = .unsigned, .bits = params.chunksize }}), | |
pub fn init() Self { | |
return Self{ | |
.h0 = params.H0, | |
.h1 = params.H1, | |
.h2 = params.H2, | |
.h3 = params.H3, | |
.h4 = params.H4, | |
.h5 = params.H5, | |
.h6 = params.H6, | |
.h7 = params.H7, | |
.rest = [_]u8{0} ** params.chunksize, | |
.restlen = 0, | |
.length = 0, | |
}; | |
} | |
pub fn update(self: *Self, message: []const u8) void { | |
// Try to get self.rest full first | |
var skip = math.min(message.len, params.chunksize - self.restlen); | |
mem.copy(u8, self.rest[self.restlen..], message[0..skip]); | |
self.restlen += skip; | |
if (self.restlen == params.chunksize) { | |
self.round(&self.rest); | |
self.restlen = 0; | |
} | |
// Handle the rest of the bytes | |
var bytes = message[skip..]; | |
while (bytes.len >= params.chunksize) { | |
self.round(bytes[0..params.chunksize]); | |
bytes = bytes[params.chunksize..]; | |
} | |
mem.copy(u8, self.rest[self.restlen..], bytes); | |
self.restlen += bytes.len; | |
} | |
pub fn digest(self: *Self) [params.chunksize / 2]u8 { | |
// What's left in self.rest can be extended to two chunks after length | |
// has been appended, so reserve enough for two chunks here | |
var rest = [_]u8{0} ** (params.chunksize * 2); | |
var restlen = self.restlen; | |
mem.copy(u8, &rest, self.rest[0..restlen]); | |
self.length +%= self.restlen * 8; | |
// Add a high bit, as specified in the algorithm | |
rest[restlen] = 1 << 7; | |
restlen += 1; | |
// Check if the length can still fit in the first chunk | |
if (restlen + @sizeOf(@TypeOf(self.length)) > params.chunksize) { | |
restlen = params.chunksize * 2 - @sizeOf(@TypeOf(self.length)); | |
} else { | |
restlen = params.chunksize - @sizeOf(@TypeOf(self.length)); | |
} | |
// Write the length to the correct chunk, inbetween is padded with zeros (as initialized) | |
mem.writeIntSliceBig(@TypeOf(self.length), rest[restlen..(restlen + @sizeOf(@TypeOf(self.length)))], self.length); | |
restlen += @sizeOf(@TypeOf(self.length)); | |
self.round(rest[0..params.chunksize]); | |
if (restlen > params.chunksize) { | |
// Do a last round if we ended up going into the second rest chunk | |
self.round(rest[params.chunksize..]); | |
} | |
var result = [_]u8{0} ** (params.chunksize / 2); | |
mem.writeIntBig(@TypeOf(self.h0), result[0..@sizeOf(intsize)], self.h0); | |
mem.writeIntBig(@TypeOf(self.h1), result[(1 * @sizeOf(intsize))..(2 * @sizeOf(intsize))], self.h1); | |
mem.writeIntBig(@TypeOf(self.h2), result[(2 * @sizeOf(intsize))..(3 * @sizeOf(intsize))], self.h2); | |
mem.writeIntBig(@TypeOf(self.h3), result[(3 * @sizeOf(intsize))..(4 * @sizeOf(intsize))], self.h3); | |
mem.writeIntBig(@TypeOf(self.h4), result[(4 * @sizeOf(intsize))..(5 * @sizeOf(intsize))], self.h4); | |
mem.writeIntBig(@TypeOf(self.h5), result[(5 * @sizeOf(intsize))..(6 * @sizeOf(intsize))], self.h5); | |
mem.writeIntBig(@TypeOf(self.h6), result[(6 * @sizeOf(intsize))..(7 * @sizeOf(intsize))], self.h6); | |
mem.writeIntBig(@TypeOf(self.h7), result[(7 * @sizeOf(intsize))..(8 * @sizeOf(intsize))], self.h7); | |
return result; | |
} | |
pub fn hexdigest(self: *Self) [params.chunksize:0]u8{ | |
var bytedigest = self.digest(); | |
var result = [_:0]u8{0} ** (params.chunksize); | |
var written = std.fmt.bufPrint(&result, "{}", .{std.fmt.fmtSliceHexLower(&bytedigest)}) catch unreachable; | |
return result; | |
} | |
fn round(self: *Self, message: *const [params.chunksize]u8) void { | |
var w = [_]intsize{0} ** params.rounds; | |
for (w[0..16]) |*elem, i| { | |
elem.* = mem.readIntSliceBig(intsize, message[(i * @sizeOf(intsize))..(i * @sizeOf(intsize) + @sizeOf(intsize))]); | |
} | |
for (w[16..params.rounds]) |_, j| { | |
var i = j + 16; | |
var s0 = math.rotr(intsize, w[i - 15], params.s0_rotate_0) ^ math.rotr(intsize, w[i - 15], params.s0_rotate_1) ^ (w[i - 15] >> params.s0_shift); | |
var s1 = math.rotr(intsize, w[i - 2], params.s1_rotate_0) ^ math.rotr(intsize, w[i - 2], params.s1_rotate_1) ^ (w[i - 2] >> params.s1_shift); | |
w[i] = w[i - 16] +% s0 +% w[i - 7] +% s1; | |
} | |
var a = self.h0; | |
var b = self.h1; | |
var c = self.h2; | |
var d = self.h3; | |
var e = self.h4; | |
var f = self.h5; | |
var g = self.h6; | |
var h = self.h7; | |
for (w) |elem, i| { | |
var S1 = math.rotr(intsize, e, params.S1_rotate_0) ^ math.rotr(intsize, e, params.S1_rotate_1) ^ math.rotr(intsize, e, params.S1_rotate_2); | |
var ch = (e & f) ^ (~e & g); | |
var temp1 = h +% S1 +% ch +% params.K[i] +% elem; | |
var S0 = math.rotr(intsize, a, params.S0_rotate_0) ^ math.rotr(intsize, a, params.S0_rotate_1) ^ math.rotr(intsize, a, params.S0_rotate_2); | |
var maj = (a & b) ^ (a & c) ^ (b & c); | |
var temp2 = S0 +% maj; | |
h = g; | |
g = f; | |
f = e; | |
e = d +% temp1; | |
d = c; | |
c = b; | |
b = a; | |
a = temp1 +% temp2; | |
} | |
self.h0 = self.h0 +% a; | |
self.h1 = self.h1 +% b; | |
self.h2 = self.h2 +% c; | |
self.h3 = self.h3 +% d; | |
self.h4 = self.h4 +% e; | |
self.h5 = self.h5 +% f; | |
self.h6 = self.h6 +% g; | |
self.h7 = self.h7 +% h; | |
self.length = self.length +% (params.chunksize * 8); | |
} | |
}; | |
} | |
pub const Sha256Hash = ShaHasher(u32, 64, Sha256); | |
pub const Sha512Hash = ShaHasher(u64, 80, Sha512); | |
test "sha256 test vectors" { | |
{ | |
var hasher = Sha256Hash.init(); | |
testing.expectEqualStrings(&hasher.hexdigest(), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); | |
} | |
{ | |
var hasher = Sha256Hash.init(); | |
hasher.update("abc"); | |
testing.expectEqualStrings(&hasher.hexdigest(), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); | |
} | |
{ | |
var hasher = Sha256Hash.init(); | |
hasher.update("a"); | |
testing.expectEqualStrings(&hasher.hexdigest(), "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"); | |
} | |
{ | |
var hasher = Sha256Hash.init(); | |
hasher.update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); | |
testing.expectEqualStrings(&hasher.hexdigest(), "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"); | |
} | |
{ | |
var hasher = Sha256Hash.init(); | |
var i: usize = 0; | |
while (i < 10000) : (i += 1) { | |
hasher.update("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); | |
} | |
var digest = hasher.hexdigest(); | |
testing.expectEqualStrings(&digest, "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"); | |
} | |
} | |
test "sha512 test vectors" { | |
{ | |
var hasher = Sha512Hash.init(); | |
testing.expectEqualStrings(&hasher.hexdigest(), "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"); | |
} | |
{ | |
var hasher = Sha512Hash.init(); | |
hasher.update("a"); | |
testing.expectEqualStrings(&hasher.hexdigest(), "1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75"); | |
} | |
{ | |
var hasher = Sha512Hash.init(); | |
hasher.update("abc"); | |
testing.expectEqualStrings(&hasher.hexdigest(), "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"); | |
} | |
{ | |
var hasher = Sha512Hash.init(); | |
hasher.update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); | |
testing.expectEqualStrings(&hasher.hexdigest(), "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445"); | |
} | |
{ | |
var hasher = Sha512Hash.init(); | |
var i: usize = 0; | |
while (i < 10000) : (i += 1) { | |
hasher.update("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); | |
} | |
var digest = hasher.hexdigest(); | |
testing.expectEqualStrings(&digest, "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment