Add ahash package.
This commit is contained in:
parent
0dc478746a
commit
41df73d7a8
|
@ -0,0 +1,258 @@
|
||||||
|
// Package ahash provides support for hashing data with a selectable
|
||||||
|
// hash function.
|
||||||
|
package ahash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"errors"
|
||||||
|
"hash"
|
||||||
|
"hash/adler32"
|
||||||
|
"hash/crc32"
|
||||||
|
"hash/crc64"
|
||||||
|
"hash/fnv"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/kisom/goutils/assert"
|
||||||
|
"golang.org/x/crypto/blake2b"
|
||||||
|
"golang.org/x/crypto/blake2s"
|
||||||
|
"golang.org/x/crypto/md4"
|
||||||
|
"golang.org/x/crypto/ripemd160"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sha224Slicer(bs []byte) []byte {
|
||||||
|
sum := sha256.Sum224(bs)
|
||||||
|
return sum[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func sha256Slicer(bs []byte) []byte {
|
||||||
|
sum := sha256.Sum256(bs)
|
||||||
|
return sum[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func sha384Slicer(bs []byte) []byte {
|
||||||
|
sum := sha512.Sum384(bs)
|
||||||
|
return sum[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func sha512Slicer(bs []byte) []byte {
|
||||||
|
sum := sha512.Sum512(bs)
|
||||||
|
return sum[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
var sliceFunctions = map[string]func([]byte) []byte{
|
||||||
|
"sha224": sha224Slicer,
|
||||||
|
"sha256": sha256Slicer,
|
||||||
|
"sha384": sha384Slicer,
|
||||||
|
"sha512": sha512Slicer,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash represents a generic hash function that may or may not be secure. It
|
||||||
|
// satisfies the hash.Hash interface.
|
||||||
|
type Hash struct {
|
||||||
|
hash.Hash
|
||||||
|
secure bool
|
||||||
|
algo string
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashAlgo returns the name of the underlying hash algorithm.
|
||||||
|
func (h *Hash) HashAlgo() string {
|
||||||
|
return h.algo
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSecure returns true if the Hash is a cryptographic hash.
|
||||||
|
func (h *Hash) IsSecure() bool {
|
||||||
|
return h.secure
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum32 returns true if the underlying hash is a 32-bit hash; if is, the
|
||||||
|
// uint32 parameter will contain the hash.
|
||||||
|
func (h *Hash) Sum32() (uint32, bool) {
|
||||||
|
h32, ok := h.Hash.(hash.Hash32)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return h32.Sum32(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHash32 returns true if the underlying hash is a 32-bit hash function.
|
||||||
|
func (h *Hash) IsHash32() bool {
|
||||||
|
_, ok := h.Hash.(hash.Hash32)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum64 returns true if the underlying hash is a 64-bit hash; if is, the
|
||||||
|
// uint64 parameter will contain the hash.
|
||||||
|
func (h *Hash) Sum64() (uint64, bool) {
|
||||||
|
h64, ok := h.Hash.(hash.Hash64)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return h64.Sum64(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHash64 returns true if the underlying hash is a 64-bit hash function.
|
||||||
|
func (h *Hash) IsHash64() bool {
|
||||||
|
_, ok := h.Hash.(hash.Hash64)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func blakeFunc(bf func(key []byte) (hash.Hash, error)) func() hash.Hash {
|
||||||
|
return func() hash.Hash {
|
||||||
|
h, err := bf(nil)
|
||||||
|
assert.NoError(err, "while constructing a BLAKE2 hash function")
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var secureHashes = map[string]func() hash.Hash{
|
||||||
|
"ripemd160": ripemd160.New,
|
||||||
|
"sha224": sha256.New224,
|
||||||
|
"sha256": sha256.New,
|
||||||
|
"sha384": sha512.New384,
|
||||||
|
"sha512": sha512.New,
|
||||||
|
"sha3-224": sha3.New224,
|
||||||
|
"sha3-256": sha3.New256,
|
||||||
|
"sha3-384": sha3.New384,
|
||||||
|
"sha3-512": sha3.New512,
|
||||||
|
"blake2s-256": blakeFunc(blake2s.New256),
|
||||||
|
"blake2b-256": blakeFunc(blake2b.New256),
|
||||||
|
"blake2b-384": blakeFunc(blake2b.New384),
|
||||||
|
"blake2b-512": blakeFunc(blake2b.New512),
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHash32(f func () hash.Hash32) func () hash.Hash {
|
||||||
|
return func() hash.Hash {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHash64(f func () hash.Hash64) func () hash.Hash {
|
||||||
|
return func() hash.Hash {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCRC64(tab uint64) func () hash.Hash {
|
||||||
|
return newHash64(
|
||||||
|
func() hash.Hash64 {
|
||||||
|
return crc64.New(crc64.MakeTable(tab))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var insecureHashes = map[string]func() hash.Hash{
|
||||||
|
"md4": md4.New,
|
||||||
|
"md5": md5.New,
|
||||||
|
"sha1": sha1.New,
|
||||||
|
"adler32": newHash32(adler32.New),
|
||||||
|
"crc32-ieee": newHash32(crc32.NewIEEE),
|
||||||
|
"crc64": newCRC64(crc64.ISO),
|
||||||
|
"crc64-ecma": newCRC64(crc64.ECMA),
|
||||||
|
"fnv1-32a": newHash32(fnv.New32a),
|
||||||
|
"fnv1-32": newHash32(fnv.New32),
|
||||||
|
"fnv1-64a": newHash64(fnv.New64a),
|
||||||
|
"fnv1-64": newHash64(fnv.New64),
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Hash for the specified algorithm.
|
||||||
|
func New(algo string) (*Hash, error) {
|
||||||
|
h := &Hash{algo: algo}
|
||||||
|
|
||||||
|
hf, ok := secureHashes[algo]
|
||||||
|
if ok {
|
||||||
|
h.Hash = hf()
|
||||||
|
h.secure = true
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hf, ok = insecureHashes[algo]
|
||||||
|
if ok {
|
||||||
|
h.Hash = hf()
|
||||||
|
h.secure = false
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("chash: unsupport hash algorithm " + algo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum returns the digest (not the hex digest) of the data using the given
|
||||||
|
// algorithm.
|
||||||
|
func Sum(algo string, data []byte) ([]byte, error) {
|
||||||
|
h, err := New(algo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.Sum(nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SumReader reads all the data from the given io.Reader and returns the
|
||||||
|
// digest (not the hex digest) from the specified algorithm.
|
||||||
|
func SumReader(algo string, r io.Reader) ([]byte, error) {
|
||||||
|
h, err := New(algo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(h, r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.Sum(nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var insecureHashList, secureHashList, hashList []string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
shl := len(secureHashes) // secure hash list length
|
||||||
|
ihl := len(insecureHashes) // insecure hash list length
|
||||||
|
ahl := shl + ihl // all hash list length
|
||||||
|
|
||||||
|
insecureHashList = make([]string, 0, ihl)
|
||||||
|
secureHashList = make([]string, 0, shl)
|
||||||
|
hashList = make([]string, 0, ahl)
|
||||||
|
|
||||||
|
for algo := range insecureHashes {
|
||||||
|
insecureHashList = append(insecureHashList, algo)
|
||||||
|
}
|
||||||
|
sort.Strings(insecureHashList)
|
||||||
|
|
||||||
|
for algo := range secureHashes {
|
||||||
|
secureHashList = append(secureHashList, algo)
|
||||||
|
}
|
||||||
|
sort.Strings(secureHashList)
|
||||||
|
|
||||||
|
hashList = append(hashList, insecureHashList...)
|
||||||
|
hashList = append(hashList, secureHashList...)
|
||||||
|
sort.Strings(hashList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashList returns a sorted list of all the hash algorithms supported by the
|
||||||
|
// package.
|
||||||
|
func HashList() []string {
|
||||||
|
return hashList[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecureHashList returns a sorted list of all the secure (cryptographic) hash
|
||||||
|
// algorithms supported by the package.
|
||||||
|
func SecureHashList() []string {
|
||||||
|
return secureHashList[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsecureHashList returns a sorted list of all the insecure hash algorithms
|
||||||
|
// supported by the package.
|
||||||
|
func InsecureHashList() []string {
|
||||||
|
return insecureHashList[:]
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
package ahash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kisom/goutils/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSecureHash(t *testing.T) {
|
||||||
|
algo := "sha256"
|
||||||
|
h, err := New(algo)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, h.IsSecure(), algo + " should be a secure hash")
|
||||||
|
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
||||||
|
assert.BoolT(t, !h.IsHash32(), algo + " isn't actually a 32-bit hash")
|
||||||
|
assert.BoolT(t, !h.IsHash64(), algo + " isn't actually a 64-bit hash")
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
var expected = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
sum, err := Sum(algo, data)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
||||||
|
|
||||||
|
data = []byte("hello, world")
|
||||||
|
buf := bytes.NewBuffer(data)
|
||||||
|
expected = "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
|
||||||
|
sum, err = SumReader(algo, buf)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
||||||
|
|
||||||
|
data = []byte("hello world")
|
||||||
|
_, err = h.Write(data)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
unExpected := "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
|
||||||
|
sum = h.Sum(nil)
|
||||||
|
assert.BoolT(t, fmt.Sprintf("%x", sum) != unExpected, fmt.Sprintf("hash shouldn't have returned %x", unExpected))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsecureHash(t *testing.T) {
|
||||||
|
algo := "md5"
|
||||||
|
h, err := New(algo)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, !h.IsSecure(), algo + " shouldn't be a secure hash")
|
||||||
|
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
||||||
|
assert.BoolT(t, !h.IsHash32(), algo + " isn't actually a 32-bit hash")
|
||||||
|
assert.BoolT(t, !h.IsHash64(), algo + " isn't actually a 64-bit hash")
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
var expected = "d41d8cd98f00b204e9800998ecf8427e"
|
||||||
|
sum, err := Sum(algo, data)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
||||||
|
|
||||||
|
data = []byte("hello, world")
|
||||||
|
buf := bytes.NewBuffer(data)
|
||||||
|
expected = "e4d7f1b4ed2e42d15898f4b27b019da4"
|
||||||
|
sum, err = SumReader(algo, buf)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
||||||
|
|
||||||
|
data = []byte("hello world")
|
||||||
|
_, err = h.Write(data)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
unExpected := "e4d7f1b4ed2e42d15898f4b27b019da4"
|
||||||
|
sum = h.Sum(nil)
|
||||||
|
assert.BoolT(t, fmt.Sprintf("%x", sum) != unExpected, fmt.Sprintf("hash shouldn't have returned %x", unExpected))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHash32(t *testing.T) {
|
||||||
|
algo := "crc32-ieee"
|
||||||
|
h, err := New(algo)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, !h.IsSecure(), algo + " shouldn't be a secure hash")
|
||||||
|
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
||||||
|
assert.BoolT(t, h.IsHash32(), algo + " is actually a 32-bit hash")
|
||||||
|
assert.BoolT(t, !h.IsHash64(), algo + " isn't actually a 64-bit hash")
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
var expected uint32
|
||||||
|
|
||||||
|
h.Write(data)
|
||||||
|
sum, ok := h.Sum32()
|
||||||
|
assert.BoolT(t, ok, algo + " should be able to return a Sum32")
|
||||||
|
assert.BoolT(t, expected == sum, fmt.Sprintf("%s returned the %d but expected %d", algo, sum, expected))
|
||||||
|
|
||||||
|
data = []byte("hello, world")
|
||||||
|
expected = 0xffab723a
|
||||||
|
h.Write(data)
|
||||||
|
sum, ok = h.Sum32()
|
||||||
|
assert.BoolT(t, ok, algo + " should be able to return a Sum32")
|
||||||
|
assert.BoolT(t, expected == sum, fmt.Sprintf("%s returned the %d but expected %d", algo, sum, expected))
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
data = []byte("hello world")
|
||||||
|
h.Write(data)
|
||||||
|
sum, ok = h.Sum32()
|
||||||
|
assert.BoolT(t, ok, algo + " should be able to return a Sum32")
|
||||||
|
assert.BoolT(t, expected != sum, fmt.Sprintf("%s returned %d but shouldn't have", algo, sum, expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHash64(t *testing.T) {
|
||||||
|
algo := "crc64"
|
||||||
|
h, err := New(algo)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, !h.IsSecure(), algo + " shouldn't be a secure hash")
|
||||||
|
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
||||||
|
assert.BoolT(t, h.IsHash64(), algo + " is actually a 64-bit hash")
|
||||||
|
assert.BoolT(t, !h.IsHash32(), algo + " isn't actually a 32-bit hash")
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
var expected uint64
|
||||||
|
|
||||||
|
h.Write(data)
|
||||||
|
sum, ok := h.Sum64()
|
||||||
|
assert.BoolT(t, ok, algo + " should be able to return a Sum64")
|
||||||
|
assert.BoolT(t, expected == sum, fmt.Sprintf("%s returned the %d but expected %d", algo, sum, expected))
|
||||||
|
|
||||||
|
data = []byte("hello, world")
|
||||||
|
expected = 0x16c45c0eb1d9c2ec
|
||||||
|
h.Write(data)
|
||||||
|
sum, ok = h.Sum64()
|
||||||
|
assert.BoolT(t, ok, algo + " should be able to return a Sum64")
|
||||||
|
assert.BoolT(t, expected == sum, fmt.Sprintf("%s returned the %d but expected %d", algo, sum, expected))
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
data = []byte("hello world")
|
||||||
|
h.Write(data)
|
||||||
|
sum, ok = h.Sum64()
|
||||||
|
assert.BoolT(t, ok, algo + " should be able to return a Sum64")
|
||||||
|
assert.BoolT(t, expected != sum, fmt.Sprintf("%s returned %d but shouldn't have", algo, sum, expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListLengthSanity(t *testing.T) {
|
||||||
|
all := HashList()
|
||||||
|
secure := SecureHashList()
|
||||||
|
insecure := InsecureHashList()
|
||||||
|
|
||||||
|
assert.BoolT(t, len(all) == len(secure) + len(insecure))
|
||||||
|
}
|
Loading…
Reference in New Issue