Skip to content

Commit 8a1abce

Browse files
add sha1 hashing (#763)
Co-authored-by: Rak Laptudirm <[email protected]>
1 parent fba7aba commit 8a1abce

File tree

2 files changed

+152
-0
lines changed

2 files changed

+152
-0
lines changed

hashing/sha1/sha1.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// sha1.go
2+
// description: The SHA-1 hashing function as defined in RFC 3174.
3+
// author: Simon Waldherr
4+
// ref: https://datatracker.ietf.org/doc/html/rfc3174
5+
// see sha1_test.go for testing
6+
7+
package sha1
8+
9+
import (
10+
"encoding/binary" // Used for interacting with uint at the byte level
11+
)
12+
13+
// Constants for SHA-1
14+
const (
15+
h0 uint32 = 0x67452301
16+
h1 uint32 = 0xEFCDAB89
17+
h2 uint32 = 0x98BADCFE
18+
h3 uint32 = 0x10325476
19+
h4 uint32 = 0xC3D2E1F0
20+
)
21+
22+
// pad pads the input message so that its length is congruent to 448 modulo 512
23+
func pad(message []byte) []byte {
24+
originalLength := len(message) * 8
25+
message = append(message, 0x80)
26+
for (len(message)*8)%512 != 448 {
27+
message = append(message, 0x00)
28+
}
29+
30+
lengthBytes := make([]byte, 8)
31+
binary.BigEndian.PutUint64(lengthBytes, uint64(originalLength))
32+
message = append(message, lengthBytes...)
33+
34+
return message
35+
}
36+
37+
// leftRotate rotates x left by n bits
38+
func leftRotate(x, n uint32) uint32 {
39+
return (x << n) | (x >> (32 - n))
40+
}
41+
42+
// Hash computes the SHA-1 hash of the input message
43+
func Hash(message []byte) [20]byte {
44+
message = pad(message)
45+
46+
// Initialize variables
47+
a, b, c, d, e := h0, h1, h2, h3, h4
48+
49+
// Process the message in successive 512-bit chunks
50+
for i := 0; i < len(message); i += 64 {
51+
var w [80]uint32
52+
chunk := message[i : i+64]
53+
54+
// Break chunk into sixteen 32-bit big-endian words
55+
for j := 0; j < 16; j++ {
56+
w[j] = binary.BigEndian.Uint32(chunk[j*4 : (j+1)*4])
57+
}
58+
59+
// Extend the sixteen 32-bit words into eighty 32-bit words
60+
for j := 16; j < 80; j++ {
61+
w[j] = leftRotate(w[j-3]^w[j-8]^w[j-14]^w[j-16], 1)
62+
}
63+
64+
// Initialize hash value for this chunk
65+
A, B, C, D, E := a, b, c, d, e
66+
67+
// Main loop
68+
for j := 0; j < 80; j++ {
69+
var f, k uint32
70+
switch {
71+
case j < 20:
72+
f = (B & C) | ((^B) & D)
73+
k = 0x5A827999
74+
case j < 40:
75+
f = B ^ C ^ D
76+
k = 0x6ED9EBA1
77+
case j < 60:
78+
f = (B & C) | (B & D) | (C & D)
79+
k = 0x8F1BBCDC
80+
default:
81+
f = B ^ C ^ D
82+
k = 0xCA62C1D6
83+
}
84+
85+
temp := leftRotate(A, 5) + f + E + k + w[j]
86+
E = D
87+
D = C
88+
C = leftRotate(B, 30)
89+
B = A
90+
A = temp
91+
}
92+
93+
// Add this chunk's hash to result so far
94+
a += A
95+
b += B
96+
c += C
97+
d += D
98+
e += E
99+
}
100+
101+
// Produce the final hash value (digest)
102+
var digest [20]byte
103+
binary.BigEndian.PutUint32(digest[0:4], a)
104+
binary.BigEndian.PutUint32(digest[4:8], b)
105+
binary.BigEndian.PutUint32(digest[8:12], c)
106+
binary.BigEndian.PutUint32(digest[12:16], d)
107+
binary.BigEndian.PutUint32(digest[16:20], e)
108+
109+
return digest
110+
}

hashing/sha1/sha1_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// sha1_test.go
2+
// description: Tests for the SHA-1 hashing function as defined in RFC 3174.
3+
// author: Simon Waldherr
4+
5+
package sha1
6+
7+
import (
8+
"encoding/hex"
9+
"testing"
10+
)
11+
12+
// Helper function to convert hash output to hex string for comparison
13+
func toHexString(hash [20]byte) string {
14+
return hex.EncodeToString(hash[:])
15+
}
16+
17+
// Test vectors for SHA-1 (from RFC 3174 and other known sources)
18+
var tests = []struct {
19+
input string
20+
expected string
21+
}{
22+
{"", "da39a3ee5e6b4b0d3255bfef95601890afd80709"},
23+
{"a", "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"},
24+
{"abc", "a9993e364706816aba3e25717850c26c9cd0d89d"},
25+
{"message digest", "c12252ceda8be8994d5fa0290a47231c1d16aae3"},
26+
{"abcdefghijklmnopqrstuvwxyz", "32d10c7b8cf96570ca04ce37f2a19d84240d3a89"},
27+
{"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "761c457bf73b14d27e9e9265c46f4b4dda11f940"},
28+
{"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "fecfd28bbc9345891a66d7c1b8ff46e60192d284"},
29+
}
30+
31+
// TestHash verifies that the Hash function produces the correct SHA-1 hash values
32+
func TestHash(t *testing.T) {
33+
for _, tt := range tests {
34+
t.Run(tt.input, func(t *testing.T) {
35+
result := Hash([]byte(tt.input))
36+
resultHex := toHexString(result)
37+
if resultHex != tt.expected {
38+
t.Errorf("SHA-1(%q) = %s; want %s", tt.input, resultHex, tt.expected)
39+
}
40+
})
41+
}
42+
}

0 commit comments

Comments
 (0)