forked from TheAlgorithms/Go
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add hashing/sha256 implementation (TheAlgorithms#447)
* feat: new hashing package * feat: sha256 hashing function * test: sha256 hashing test cases and benchmarks * doc: improve sha256 docs
- Loading branch information
1 parent
385fe84
commit 068ea4b
Showing
4 changed files
with
165 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// Package hashing containing different implementation of certain hashing | ||
package hashing |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// Empty test file to keep track of all the tests for the algorithms. | ||
|
||
package hashing |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// sha256.go | ||
// description: The sha256 cryptographic hash function as defined in the RFC6234 standard. | ||
// author: [Paul Leydier] (https://github.com/paul-leydier) | ||
// ref: https://datatracker.ietf.org/doc/html/rfc6234 | ||
// ref: https://en.wikipedia.org/wiki/SHA-2 | ||
// see sha256_test.go | ||
|
||
package sha256 | ||
|
||
import ( | ||
"encoding/binary" // Used for interacting with uint at the byte level | ||
"math/bits" // Used for bits rotation operations | ||
) | ||
|
||
var K = [64]uint32{ | ||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, | ||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, | ||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, | ||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, | ||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, | ||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, | ||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, | ||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, | ||
} | ||
|
||
const chunkSize = 64 | ||
|
||
// pad returns a padded version of the input message, such as the padded message's length is a multiple | ||
// of 512 bits. | ||
// The padding methodology is as follows: | ||
// A "1" bit is appended at the end of the input message, followed by m "0" bits such as the length is | ||
// 64 bits short of a 512 bits multiple. The remaining 64 bits are filled with the initial length of the | ||
// message, represented as a 64-bits unsigned integer. | ||
// For more details, see: https://datatracker.ietf.org/doc/html/rfc6234#section-4.1 | ||
func pad(message []byte) []byte { | ||
L := make([]byte, 8) | ||
binary.BigEndian.PutUint64(L, uint64(len(message)*8)) | ||
message = append(message, 0x80) // "1" bit followed by 7 "0" bits | ||
for (len(message)+8)%64 != 0 { | ||
message = append(message, 0x00) // 8 "0" bits | ||
} | ||
message = append(message, L...) | ||
|
||
return message | ||
} | ||
|
||
// Hash hashes the input message using the sha256 hashing function, and return a 32 byte array. | ||
// The implementation follows the RGC6234 standard, which is documented | ||
// at https://datatracker.ietf.org/doc/html/rfc6234 | ||
func Hash(message []byte) [32]byte { | ||
message = pad(message) | ||
|
||
// Initialize round constants | ||
h0, h1, h2, h3, h4, h5, h6, h7 := uint32(0x6a09e667), uint32(0xbb67ae85), uint32(0x3c6ef372), uint32(0xa54ff53a), | ||
uint32(0x510e527f), uint32(0x9b05688c), uint32(0x1f83d9ab), uint32(0x5be0cd19) | ||
|
||
// Iterate through 512-bit chunks | ||
for chunkStart := 0; chunkStart < len(message); chunkStart += chunkSize { | ||
// Message schedule | ||
var w [64]uint32 | ||
for i := 0; i*4 < chunkSize; i++ { | ||
w[i] = binary.BigEndian.Uint32(message[chunkStart+i*4 : chunkStart+(i+1)*4]) | ||
} | ||
|
||
// Extend the 16 bytes chunk to the whole 64 bytes message schedule | ||
for i := 16; i < 64; i++ { | ||
s0 := bits.RotateLeft32(w[i-15], -7) ^ bits.RotateLeft32(w[i-15], -18) ^ (w[i-15] >> 3) | ||
s1 := bits.RotateLeft32(w[i-2], -17) ^ bits.RotateLeft32(w[i-2], -19) ^ (w[i-2] >> 10) | ||
w[i] = w[i-16] + s0 + w[i-7] + s1 | ||
} | ||
|
||
// Actual hashing loop | ||
a, b, c, d, e, f, g, h := h0, h1, h2, h3, h4, h5, h6, h7 | ||
for i := 0; i < 64; i++ { | ||
S1 := bits.RotateLeft32(e, -6) ^ bits.RotateLeft32(e, -11) ^ bits.RotateLeft32(e, -25) | ||
ch := (e & f) ^ ((^e) & g) | ||
tmp1 := h + S1 + ch + K[i] + w[i] | ||
S0 := bits.RotateLeft32(a, -2) ^ bits.RotateLeft32(a, -13) ^ bits.RotateLeft32(a, -22) | ||
maj := (a & b) ^ (a & c) ^ (b & c) | ||
tmp2 := S0 + maj | ||
h = g | ||
g = f | ||
f = e | ||
e = d + tmp1 | ||
d = c | ||
c = b | ||
b = a | ||
a = tmp1 + tmp2 | ||
} | ||
h0 += a | ||
h1 += b | ||
h2 += c | ||
h3 += d | ||
h4 += e | ||
h5 += f | ||
h6 += g | ||
h7 += h | ||
} | ||
|
||
// Export digest | ||
digest := [32]byte{} | ||
binary.BigEndian.PutUint32(digest[:4], h0) | ||
binary.BigEndian.PutUint32(digest[4:8], h1) | ||
binary.BigEndian.PutUint32(digest[8:12], h2) | ||
binary.BigEndian.PutUint32(digest[12:16], h3) | ||
binary.BigEndian.PutUint32(digest[16:20], h4) | ||
binary.BigEndian.PutUint32(digest[20:24], h5) | ||
binary.BigEndian.PutUint32(digest[24:28], h6) | ||
binary.BigEndian.PutUint32(digest[28:], h7) | ||
|
||
return digest | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package sha256 | ||
|
||
import ( | ||
"encoding/hex" | ||
"testing" | ||
) | ||
|
||
func TestHash(t *testing.T) { | ||
testCases := []struct { | ||
in string | ||
expected string | ||
}{ | ||
{"hello world", "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"}, | ||
{"", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, | ||
{"a", "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"}, | ||
{"The quick brown fox jumps over the lazy dog", "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"}, | ||
{"The quick brown fox jumps over the lazy dog.", "ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c"}, | ||
{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "2d8c2f6d978ca21712b5f6de36c9d31fa8e96a4fa5d8ff8b0188dfb9e7c171bb"}, | ||
} | ||
for _, tc := range testCases { | ||
res := Hash([]byte(tc.in)) | ||
result := hex.EncodeToString(res[:]) | ||
if result != tc.expected { | ||
t.Fatalf("Hash(%s) = %s, expected %s", tc.in, result, tc.expected) | ||
} | ||
} | ||
} | ||
|
||
func BenchmarkHash(b *testing.B) { | ||
testCases := []struct { | ||
name string | ||
in string | ||
}{ | ||
{"hello world", "hello world"}, | ||
{"empty", ""}, | ||
{"a", "a"}, | ||
{"The quick brown fox jumps over the lazy dog", "The quick brown fox jumps over the lazy dog"}, | ||
{"The quick brown fox jumps over the lazy dog.", "The quick brown fox jumps over the lazy dog."}, | ||
{"Lorem ipsum", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."}, | ||
} | ||
for _, testCase := range testCases { | ||
b.Run(testCase.name, func(b *testing.B) { | ||
for i := 0; i < b.N; i++ { | ||
Hash([]byte(testCase.in)) | ||
} | ||
}) | ||
} | ||
} |