Skip to content

Instantly share code, notes, and snippets.

@pingiun
Last active June 8, 2021 22:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pingiun/7e6a1e067f997f296bed9bf0ff9900f8 to your computer and use it in GitHub Desktop.
Save pingiun/7e6a1e067f997f296bed9bf0ff9900f8 to your computer and use it in GitHub Desktop.
Zig sha256 and sha512 implementations without unnecessary code duplication

To learn Zig I implemented some crypto functions in Zig. It uses unique Zig comptime features to reduce code duplication

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