Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions hashing/sha1/sha1.go
Original file line number Diff line number Diff line change
@@ -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
}
42 changes: 42 additions & 0 deletions hashing/sha1/sha1_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
Loading