Skip to content
This repository has been archived by the owner on Jun 12, 2024. It is now read-only.

Commit

Permalink
add Hash() and HashToString() function to the crypto package (#54)
Browse files Browse the repository at this point in the history
* add Hash() and HashToString() function to the crypto package to have one standard way of hashing across different projects.
Co-authored-by: Lucas Roesler <[email protected]>
  • Loading branch information
trusch authored Sep 1, 2020
1 parent efef3b1 commit ca6a1f6
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
github.com/uber/jaeger-lib v2.0.0+incompatible // indirect
github.com/urfave/negroni v1.0.0
go.uber.org/goleak v1.0.0
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 // indirect
google.golang.org/grpc v1.22.0 // indirect
gopkg.in/yaml.v2 v2.3.0
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6Zh
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180419222023-a2a45943ae67/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down
90 changes: 90 additions & 0 deletions pkg/crypto/hash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package crypto

import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"hash"
"io"
"strings"

"golang.org/x/crypto/sha3"
)

var (
// DefaultHasher is the default implementation for hashing things
// It outputs 32 Bytes and uses a SHA3-256 hash in the current configuration.
// Its generic security strength is 256 bits against preimage attacks,
// and 128 bits against collision attacks.
defaultHasher = basicHasher{sha3.New256()}
)

// Hash is a convenience function calling the default hasher
// WARNING: only pass in data that is json-marshalable. If not, the worst case scenario is that you passed in data with circular references and this will just blow up your CPU
func Hash(data ...interface{}) ([]byte, error) {
return defaultHasher.Hash(data...)
}

// HashToString is a convenience function calling the default hasher and encoding the result as hex string
func HashToString(data ...interface{}) (string, error) {
hash, err := defaultHasher.Hash(data...)
if err != nil {
return "", err
}
return hex.EncodeToString(hash), nil
}

// Hasher provides a method for hashing arbitary data types
type Hasher interface {
Hash(data ...interface{}) ([]byte, error)
}

type basicHasher struct {
hash hash.Hash
}

func (h basicHasher) Hash(args ...interface{}) ([]byte, error) {
h.hash.Reset()

for _, data := range args {
var (
reader io.Reader
encoderError error
)

// setup reader for the data
switch d := data.(type) {
case io.Reader:
reader = d
case []byte:
reader = bytes.NewReader(d)
case string:
reader = strings.NewReader(d)
case fmt.Stringer:
reader = strings.NewReader(d.String())
default:
r, w := io.Pipe()
encoder := json.NewEncoder(w)
go func() {
defer w.Close()
encoderError = encoder.Encode(data)
}()
reader = r
}

// hash all the data
_, err := io.Copy(h.hash, reader)
if err != nil {
return nil, err
}

// check encoder error
if encoderError != nil {
return nil, encoderError
}

}

return h.hash.Sum(nil), nil
}
80 changes: 80 additions & 0 deletions pkg/crypto/hash_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package crypto

import (
"encoding/json"
"reflect"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestHash(t *testing.T) {
cases := []struct {
name string
input []interface{}
expectedOutput string
expectedError error
}{
{
name: "hash a string",
input: []interface{}{"foobar"},
expectedOutput: "09234807e4af85f17c66b48ee3bca89dffd1f1233659f9f940a2b17b0b8c6bc5",
},
{
name: "reproducable results",
input: []interface{}{"foobar"},
expectedOutput: "09234807e4af85f17c66b48ee3bca89dffd1f1233659f9f940a2b17b0b8c6bc5",
},
{
name: "something else",
input: []interface{}{"foobarbaz"},
expectedOutput: "369972cd3fda2b1e239bf114f4c2c65115b05fee2e4e5b2ae19ce7b5d757c572",
},
{
name: "hash multiple strings",
input: []interface{}{"foo", "bar"},
expectedOutput: "09234807e4af85f17c66b48ee3bca89dffd1f1233659f9f940a2b17b0b8c6bc5",
},
{
name: "hash reader",
input: []interface{}{strings.NewReader("foobar")},
expectedOutput: "09234807e4af85f17c66b48ee3bca89dffd1f1233659f9f940a2b17b0b8c6bc5",
},
{
name: "hash bytes",
input: []interface{}{[]byte("foobar")},
expectedOutput: "09234807e4af85f17c66b48ee3bca89dffd1f1233659f9f940a2b17b0b8c6bc5",
},
{
name: "hash complex object",
input: []interface{}{struct{ foo map[string]interface{} }{foo: map[string]interface{}{"a": 123}}},
expectedOutput: "d0a1b2af1705c1b8495b00145082ef7470384e62ac1c4d9b9cdbbe0476c28f8c",
},
{
name: "hash multiple different things",
input: []interface{}{"f", strings.NewReader("oo"), []byte("bar")},
expectedOutput: "09234807e4af85f17c66b48ee3bca89dffd1f1233659f9f940a2b17b0b8c6bc5",
},
{
name: "marshal error is forwarded",
input: []interface{}{map[float64]string{
1.: "lol",
}},
expectedOutput: "",
expectedError: &json.UnsupportedTypeError{
Type: reflect.MapOf(
reflect.TypeOf(1.),
reflect.TypeOf("lol"),
)},
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
hash, err := HashToString(tc.input...)
require.Equal(t, tc.expectedOutput, hash)
require.Equal(t, tc.expectedError, err)
})
}
}

0 comments on commit ca6a1f6

Please sign in to comment.