Skip to content

Commit

Permalink
add sha1 hashing (#763)
Browse files Browse the repository at this point in the history
Co-authored-by: Rak Laptudirm <[email protected]>
  • Loading branch information
SimonWaldherr and raklaptudirm authored Nov 26, 2024
1 parent fba7aba commit 8a1abce
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 0 deletions.
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)
}
})
}
}

0 comments on commit 8a1abce

Please sign in to comment.