Skip to content

Commit

Permalink
feat: add hashing/sha256 implementation (TheAlgorithms#447)
Browse files Browse the repository at this point in the history
* feat: new hashing package

* feat: sha256 hashing function

* test: sha256 hashing test cases and benchmarks

* doc: improve sha256 docs
  • Loading branch information
paul-leydier authored Nov 23, 2021
1 parent 385fe84 commit 068ea4b
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 0 deletions.
2 changes: 2 additions & 0 deletions hashing/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package hashing containing different implementation of certain hashing
package hashing
3 changes: 3 additions & 0 deletions hashing/hashing_test.go
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
112 changes: 112 additions & 0 deletions hashing/sha256/sha256.go
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
}
48 changes: 48 additions & 0 deletions hashing/sha256/sha256_test.go
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))
}
})
}
}

0 comments on commit 068ea4b

Please sign in to comment.