From 594a56d164289747a58eae6758efea0dba92de61 Mon Sep 17 00:00:00 2001 From: Simon Waldherr Date: Wed, 23 Oct 2024 19:55:12 +0200 Subject: [PATCH] add sha1 hashing --- hashing/sha1/sha1.go | 110 ++++++++++++++++++++++++++++++++++++++ hashing/sha1/sha1_test.go | 42 +++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 hashing/sha1/sha1.go create mode 100644 hashing/sha1/sha1_test.go diff --git a/hashing/sha1/sha1.go b/hashing/sha1/sha1.go new file mode 100644 index 000000000..ba30fe615 --- /dev/null +++ b/hashing/sha1/sha1.go @@ -0,0 +1,110 @@ +// sha1.go +// description: The SHA-1 hashing function as defined in RFC 3174. +// author: Simon Waldherr +// ref: https://datatracker.ietf.org/doc/html/rfc3174 +// see sha1_test.go for testing + +package sha1 + +import ( + "encoding/binary" // Used for interacting with uint at the byte level +) + +// Constants for SHA-1 +const ( + h0 uint32 = 0x67452301 + h1 uint32 = 0xEFCDAB89 + h2 uint32 = 0x98BADCFE + h3 uint32 = 0x10325476 + h4 uint32 = 0xC3D2E1F0 +) + +// pad pads the input message so that its length is congruent to 448 modulo 512 +func pad(message []byte) []byte { + originalLength := len(message) * 8 + message = append(message, 0x80) + for (len(message)*8)%512 != 448 { + message = append(message, 0x00) + } + + lengthBytes := make([]byte, 8) + binary.BigEndian.PutUint64(lengthBytes, uint64(originalLength)) + message = append(message, lengthBytes...) + + return message +} + +// leftRotate rotates x left by n bits +func leftRotate(x, n uint32) uint32 { + return (x << n) | (x >> (32 - n)) +} + +// Hash computes the SHA-1 hash of the input message +func Hash(message []byte) [20]byte { + message = pad(message) + + // Initialize variables + a, b, c, d, e := h0, h1, h2, h3, h4 + + // Process the message in successive 512-bit chunks + for i := 0; i < len(message); i += 64 { + var w [80]uint32 + chunk := message[i : i+64] + + // Break chunk into sixteen 32-bit big-endian words + for j := 0; j < 16; j++ { + w[j] = binary.BigEndian.Uint32(chunk[j*4 : (j+1)*4]) + } + + // Extend the sixteen 32-bit words into eighty 32-bit words + for j := 16; j < 80; j++ { + w[j] = leftRotate(w[j-3]^w[j-8]^w[j-14]^w[j-16], 1) + } + + // Initialize hash value for this chunk + A, B, C, D, E := a, b, c, d, e + + // Main loop + for j := 0; j < 80; j++ { + var f, k uint32 + switch { + case j < 20: + f = (B & C) | ((^B) & D) + k = 0x5A827999 + case j < 40: + f = B ^ C ^ D + k = 0x6ED9EBA1 + case j < 60: + f = (B & C) | (B & D) | (C & D) + k = 0x8F1BBCDC + default: + f = B ^ C ^ D + k = 0xCA62C1D6 + } + + temp := leftRotate(A, 5) + f + E + k + w[j] + E = D + D = C + C = leftRotate(B, 30) + B = A + A = temp + } + + // Add this chunk's hash to result so far + a += A + b += B + c += C + d += D + e += E + } + + // Produce the final hash value (digest) + var digest [20]byte + binary.BigEndian.PutUint32(digest[0:4], a) + binary.BigEndian.PutUint32(digest[4:8], b) + binary.BigEndian.PutUint32(digest[8:12], c) + binary.BigEndian.PutUint32(digest[12:16], d) + binary.BigEndian.PutUint32(digest[16:20], e) + + return digest +} diff --git a/hashing/sha1/sha1_test.go b/hashing/sha1/sha1_test.go new file mode 100644 index 000000000..f7d1d2079 --- /dev/null +++ b/hashing/sha1/sha1_test.go @@ -0,0 +1,42 @@ +// sha1_test.go +// description: Tests for the SHA-1 hashing function as defined in RFC 3174. +// author: Simon Waldherr + +package sha1 + +import ( + "encoding/hex" + "testing" +) + +// Helper function to convert hash output to hex string for comparison +func toHexString(hash [20]byte) string { + return hex.EncodeToString(hash[:]) +} + +// Test vectors for SHA-1 (from RFC 3174 and other known sources) +var tests = []struct { + input string + expected string +}{ + {"", "da39a3ee5e6b4b0d3255bfef95601890afd80709"}, + {"a", "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"}, + {"abc", "a9993e364706816aba3e25717850c26c9cd0d89d"}, + {"message digest", "c12252ceda8be8994d5fa0290a47231c1d16aae3"}, + {"abcdefghijklmnopqrstuvwxyz", "32d10c7b8cf96570ca04ce37f2a19d84240d3a89"}, + {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "761c457bf73b14d27e9e9265c46f4b4dda11f940"}, + {"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "fecfd28bbc9345891a66d7c1b8ff46e60192d284"}, +} + +// TestHash verifies that the Hash function produces the correct SHA-1 hash values +func TestHash(t *testing.T) { + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result := Hash([]byte(tt.input)) + resultHex := toHexString(result) + if resultHex != tt.expected { + t.Errorf("SHA-1(%q) = %s; want %s", tt.input, resultHex, tt.expected) + } + }) + } +}