commit d01e8a831282bc948ad5c02e25413f17289af02e
parent fc32da6afa066bdd290380373f01c0642bbdbbc5
Author: Thomas Bracht Laumann Jespersen <t@laumann.xyz>
Date: Mon, 15 Mar 2021 13:17:02 +0100
crypto: Add sha1
This is basically a transcription of Go's generic SHA-1 implementation,
mirroring the implementation from crypto::sha256.
Diffstat:
4 files changed, 333 insertions(+), 0 deletions(-)
diff --git a/crypto/sha1/+test.ha b/crypto/sha1/+test.ha
@@ -0,0 +1,72 @@
+use fmt;
+use hash;
+use strings;
+use strio;
+use io;
+
+@test fn sha1() void = {
+ let sha = sha1();
+ defer hash::finish(sha);
+
+ const vectors = [
+ ("", "da39a3ee5e6b4b0d3255bfef95601890afd80709"),
+ ("abc", "a9993e364706816aba3e25717850c26c9cd0d89d"),
+ ("hello world", "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"),
+ ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "84983e441c3bd26ebaae4aa1f95129e5e54670f1"),
+ ("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", "a49b2446a02c645bf419f995b67091253a04a259"),
+ // From Pro Git Chapter 10.2
+ // https://git-scm.com/book/en/v2/Git-Internals-Git-Objects#_object_storage
+ // output of: echo -n 'what is up, doc?' | git hash-object --stdin
+ ("blob 16\0what is up, doc?", "bd9dbf5aae1a3862dd1526723246b20206e5fc37"),
+ ("Hare is a cool language", "947feae3d0d65cc083c8f3e87858206e36aae908"),
+ ("'UNIX was not designed to stop its users from doing stupid things, as that would also stop them from doing clever things' - Doug Gwyn", "05c8dd2605161bdd0b5d70f1f225f4dd69a01e3b"),
+ ("'Life is too short to run proprietary software' - Bdale Garbee", "91ad4bdc2fbe2b731cbe8bf2958099391c7af3b8"),
+ ("'The central enemy of reliability is complexity.' - Geer et al", "4b6eb2aa55ef59cc59be6d181c64141e7c1e5eab"),
+ ];
+
+ for (let i = 0z; i < len(vectors); i += 1) {
+ const vector = vectors[i];
+ hash::reset(sha);
+ hash::write(sha, strings::to_utf8(vector.0));
+ let sum = hash::sum(sha);
+ defer free(sum);
+
+ let hex = strio::dynamic();
+ defer io::close(hex);
+ for (let j = 0z; j < SIZE; j += 1) {
+ fmt::fprintf(hex, "{:02x}", sum[j]);
+ };
+ if (strio::string(hex) != vector.1) {
+ fmt::errorfln("Vector {}: {} != {}",
+ i, strio::string(hex), vector.1);
+ abort();
+ };
+ };
+};
+
+// Uncomment this test to run the 1GB vector - takes a while to run, so it's
+// left disabled.
+//@test fn sha1_1gb() void = {
+// const input = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno";
+// const expected = "7789f0c9ef7bfc40d93311143dfbe69e2017f592";
+//
+// let sha = sha1();
+// defer hash::finish(sha);
+//
+// for (let i = 0z; i < 16777216; i += 1)
+// hash::write(sha, strings::to_utf8(input));
+//
+// let sum = hash::sum(sha);
+// defer free(sum);
+//
+// let hex = strio::dynamic();
+// defer io::close(hex);
+// for (let i = 0z; i < SIZE; i += 1)
+// fmt::fprintf(hex, "{:02x}", sum[i]);
+//
+// if (strio::string(hex) != expected) {
+// fmt::errorfln("1GB vector: {} != {}",
+// strio::string(hex), expected);
+// abort();
+// };
+//};
diff --git a/crypto/sha1/sha1.ha b/crypto/sha1/sha1.ha
@@ -0,0 +1,221 @@
+use hash;
+use io;
+use crypto::math;
+use endian;
+
+// The size, in bytes, of a SHA-1 digest.
+export def SIZE: size = 20;
+
+export def BLOCKSIZE: size = 64;
+
+def chunk: size = 64;
+def init0: u32 = 0x67452301;
+def init1: u32 = 0xEFCDAB89;
+def init2: u32 = 0x98BADCFE;
+def init3: u32 = 0x10325476;
+def init4: u32 = 0xC3D2E1F0;
+
+type digest = struct {
+ hash: hash::hash,
+ h: [5]u32,
+ x: [chunk]u8,
+ nx: size,
+ ln: size,
+};
+
+export fn sha1() *hash::hash = {
+ let sha = alloc(digest {
+ hash = hash::hash {
+ stream = io::stream {
+ writer = &write,
+ closer = &close,
+ ...
+ },
+ sum = &sum,
+ reset = &reset,
+ sz = SIZE,
+ ...
+ },
+ });
+ let hash = &sha.hash;
+ hash::reset(hash);
+ return hash;
+};
+
+fn write(st: *io::stream, buf: const []u8) (size | io::error) = {
+ let h = st: *digest;
+ let b: []u8 = buf;
+ let nn = len(buf);
+
+ h.ln += nn;
+
+ if (h.nx > 0) {
+ // Compute how many bytes can be copied into h.x
+ let r = len(h.x) - h.nx;
+ let n = if (nn > r) r else nn;
+ h.x[h.nx..] = b[..n];
+ h.nx += n;
+ if (h.nx == chunk) {
+ block(h, h.x[..]);
+ h.nx = 0;
+ };
+ b = b[n..];
+ };
+ if (len(b) >= chunk) {
+ let n = len(b) & ~(chunk - 1);
+ block(h, b[..n]);
+ b = b[n..];
+ };
+ if (len(b) > 0) {
+ let n = len(b);
+ h.x[..n] = b[..];
+ h.nx = n;
+ };
+ return nn;
+};
+
+fn close(st: *io::stream) void = {
+ free(st);
+};
+
+fn reset(h: *hash::hash) void = {
+ let h = h: *digest;
+ h.h[0] = init0;
+ h.h[1] = init1;
+ h.h[2] = init2;
+ h.h[3] = init3;
+ h.h[4] = init4;
+ h.nx = 0;
+ h.ln = 0;
+};
+
+fn sum(h: *hash::hash) []u8 = {
+ let h = h: *digest;
+ let copy = *h;
+ let h = ©
+
+ // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
+ let ln = h.ln;
+ let tmp: [64]u8 = [0x80, 0...];
+ const pad = if ((ln % 64z) < 56z) 56z - ln % 64z
+ else 64 + 56z - ln % 64z;
+ write(&h.hash.stream, tmp[..pad]);
+
+ // Length in bits.
+ ln <<= 3;
+ endian::beputu64(tmp, ln: u64);
+ write(&h.hash.stream, tmp[..8]);
+
+ assert(h.nx == 0);
+
+ // Where we write the digest
+ let d: [SIZE]u8 = [0...];
+ endian::beputu32(d[0..], h.h[0]);
+ endian::beputu32(d[4..], h.h[1]);
+ endian::beputu32(d[8..], h.h[2]);
+ endian::beputu32(d[12..], h.h[3]);
+ endian::beputu32(d[16..], h.h[4]);
+
+ let slice: []u8 = alloc([], SIZE);
+ append(slice, ...d);
+ return slice;
+};
+
+let K0: u32 = 0x5A827999;
+let K1: u32 = 0x6ED9EBA1;
+let K2: u32 = 0x8F1BBCDC;
+let K3: u32 = 0xCA62C1D6;
+
+// A generic, pure Hare version of the SHA-1 block step
+fn block(h: *digest, p: []u8) void = {
+ let w: [16]u32 = [0...];
+
+ let h0 = h.h[0];
+ let h1 = h.h[1];
+ let h2 = h.h[2];
+ let h3 = h.h[3];
+ let h4 = h.h[4];
+
+ for (len(p) >= chunk) {
+ for (let i = 0z; i < 16; i += 1) {
+ let j = i * 4;
+ w[i] = p[j]: u32 << 24
+ | p[j+1]: u32 << 16
+ | p[j+2]: u32 << 8
+ | p[j+3]: u32;
+ };
+ let a = h0;
+ let b = h1;
+ let c = h2;
+ let d = h3;
+ let e = h4;
+
+ // Each of the four 20-iteration rounds differs only in the
+ // computation of f and the choice of Ki for i=0..5
+ let i = 0z;
+ for (i < 16; i += 1) {
+ let f = (b & c) | (~b & d);
+ let t = math::rotl32(a, 5) + f + e + w[i & 0xf] + K0;
+ // The order matters here!
+ e = d; d = c; c = math::rotl32(b, 30); b = a; a = t;
+ };
+ for (i < 20; i += 1) {
+ let tmp = w[(i - 3) & 0xf]
+ ^ w[(i - 8) & 0xf]
+ ^ w[(i - 14) & 0xf]
+ ^ w[i & 0xf];
+ w[i & 0xf] = tmp << 1 | tmp >> 31;
+
+ let f = (b & c) | (~b & d);
+ let t = math::rotl32(a, 5) + f + e + w[i & 0xf] + K0;
+ e = d; d = c; c = math::rotl32(b, 30); b = a; a = t;
+ };
+ for (i < 40; i += 1) {
+ let tmp = w[(i - 3) & 0xf]
+ ^ w[(i - 8) & 0xf]
+ ^ w[(i - 14) & 0xf]
+ ^ w[i & 0xf];
+ w[i & 0xf] = tmp << 1 | tmp >> 31;
+
+ let f = b ^ c ^ d;
+ let t = math::rotl32(a, 5) + f + e + w[i & 0xf] + K1;
+ e = d; d = c; c = math::rotl32(b, 30); b = a; a = t;
+ };
+ for (i < 60; i += 1) {
+ let tmp = w[(i - 3) & 0xf]
+ ^ w[(i - 8) & 0xf]
+ ^ w[(i - 14) & 0xf]
+ ^ w[i & 0xf];
+ w[i & 0xf] = tmp << 1 | tmp >> 31;
+
+ let f = ((b | c) & d) | (b & c);
+ let t = math::rotl32(a, 5) + f + e + w[i & 0xf] + K2;
+ e = d; d = c; c = math::rotl32(b, 30); b = a; a = t;
+ };
+ for (i < 80; i += 1) {
+ let tmp = w[(i - 3) & 0xf]
+ ^ w[(i - 8) & 0xf]
+ ^ w[(i - 14) & 0xf]
+ ^ w[i & 0xf];
+ w[i & 0xf] = tmp << 1 | tmp >> 31;
+
+ let f = b ^ c ^ d;
+ let t = math::rotl32(a, 5) + f + e + w[i & 0xf] + K3;
+ e = d; d = c; c = math::rotl32(b, 30); b = a; a = t;
+ };
+
+ h0 += a;
+ h1 += b;
+ h2 += c;
+ h3 += d;
+ h4 += e;
+
+ p = p[chunk..];
+ };
+
+ h.h[0] = h0;
+ h.h[1] = h1;
+ h.h[2] = h2;
+ h.h[3] = h3;
+ h.h[4] = h4;
+};
diff --git a/gen-stdlib b/gen-stdlib
@@ -164,6 +164,18 @@ crypto_sha256() {
fi
}
+crypto_sha1() {
+ printf '# crypto::sha1\n'
+ if [ $testing -eq 0 ]
+ then
+ gen_srcs crypto::sha1 sha1.ha
+ gen_ssa crypto::sha1 hash io endian
+ else
+ gen_srcs crypto::sha1 sha1.ha +test.ha
+ gen_ssa crypto::sha1 hash io endian fmt strio strings
+ fi
+}
+
dirs() {
printf '# dirs\n'
gen_srcs dirs \
@@ -478,6 +490,7 @@ bufio
bytes
crypto_math
crypto_random
+crypto_sha1
crypto_sha256
dirs
encoding_hex
diff --git a/stdlib.mk b/stdlib.mk
@@ -83,6 +83,9 @@ hare_stdlib_deps+=$(stdlib_crypto_math)
stdlib_crypto_random=$(HARECACHE)/crypto/random/crypto_random.o
hare_stdlib_deps+=$(stdlib_crypto_random)
+stdlib_crypto_sha1=$(HARECACHE)/crypto/sha1/crypto_sha1.o
+hare_stdlib_deps+=$(stdlib_crypto_sha1)
+
stdlib_crypto_sha256=$(HARECACHE)/crypto/sha256/crypto_sha256.o
hare_stdlib_deps+=$(stdlib_crypto_sha256)
@@ -226,6 +229,16 @@ $(HARECACHE)/crypto/random/crypto_random.ssa: $(stdlib_crypto_random_srcs) $(std
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ncrypto::random \
-t$(HARECACHE)/crypto/random/crypto_random.td $(stdlib_crypto_random_srcs)
+# crypto::sha1
+stdlib_crypto_sha1_srcs= \
+ $(STDLIB)/crypto/sha1/sha1.ha
+
+$(HARECACHE)/crypto/sha1/crypto_sha1.ssa: $(stdlib_crypto_sha1_srcs) $(stdlib_rt) $(stdlib_hash) $(stdlib_io) $(stdlib_endian)
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(HARECACHE)/crypto/sha1
+ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ncrypto::sha1 \
+ -t$(HARECACHE)/crypto/sha1/crypto_sha1.td $(stdlib_crypto_sha1_srcs)
+
# crypto::sha256
stdlib_crypto_sha256_srcs= \
$(STDLIB)/crypto/sha256/sha256.ha
@@ -648,6 +661,9 @@ hare_testlib_deps+=$(testlib_crypto_math)
testlib_crypto_random=$(TESTCACHE)/crypto/random/crypto_random.o
hare_testlib_deps+=$(testlib_crypto_random)
+testlib_crypto_sha1=$(TESTCACHE)/crypto/sha1/crypto_sha1.o
+hare_testlib_deps+=$(testlib_crypto_sha1)
+
testlib_crypto_sha256=$(TESTCACHE)/crypto/sha256/crypto_sha256.o
hare_testlib_deps+=$(testlib_crypto_sha256)
@@ -791,6 +807,17 @@ $(TESTCACHE)/crypto/random/crypto_random.ssa: $(testlib_crypto_random_srcs) $(te
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ncrypto::random \
-t$(TESTCACHE)/crypto/random/crypto_random.td $(testlib_crypto_random_srcs)
+# crypto::sha1
+testlib_crypto_sha1_srcs= \
+ $(STDLIB)/crypto/sha1/sha1.ha \
+ $(STDLIB)/crypto/sha1/+test.ha
+
+$(TESTCACHE)/crypto/sha1/crypto_sha1.ssa: $(testlib_crypto_sha1_srcs) $(testlib_rt) $(testlib_hash) $(testlib_io) $(testlib_endian) $(testlib_fmt) $(testlib_strio) $(testlib_strings)
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(TESTCACHE)/crypto/sha1
+ @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ncrypto::sha1 \
+ -t$(TESTCACHE)/crypto/sha1/crypto_sha1.td $(testlib_crypto_sha1_srcs)
+
# crypto::sha256
testlib_crypto_sha256_srcs= \
$(STDLIB)/crypto/sha256/sha256.ha \