Skip to content
This repository has been archived by the owner on Feb 8, 2023. It is now read-only.

Commit

Permalink
improve with SHA-3
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Mar 17, 2017
1 parent fce4a79 commit 2b9acce
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 269 deletions.
6 changes: 1 addition & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ go:
- 1.8
before_install:
- go get -t -v ./...
- go get github.com/modocache/gover
- go get github.com/mattn/goveralls
script:
- go test -coverprofile=crypto.coverprofile
- go test -coverprofile=pbkdf2.coverprofile ./pbkdf2
- gover
- go tool cover -html=gover.coverprofile
- goveralls -coverprofile=gover.coverprofile -service=travis-ci
- goveralls -coverprofile=crypto.coverprofile -service=travis-ci
5 changes: 1 addition & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
test:
go test --race
go test --race ./pbkdf2

cover:
rm -f *.coverprofile
go test -coverprofile=crypto.coverprofile
go test -coverprofile=pbkdf2.coverprofile ./pbkdf2
gover
go tool cover -html=gover.coverprofile
go tool cover -html=crypto.coverprofile
rm -f *.coverprofile

.PHONY: test cover
49 changes: 25 additions & 24 deletions crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import (
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"hash"
"io"
"strconv"
"strings"
"time"

"github.com/teambition/crypto-go/pbkdf2"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/sha3"
)

// Equal compares two []byte for equality without leaking timing information.
Expand Down Expand Up @@ -86,7 +86,7 @@ func SHA256Hmac(key, data []byte) []byte {
// fmt.Println(crypto.AESEncrypt(nil, []byte("my key"), []byte("hello"))) // no salt
//
func AESEncrypt(salt, key, data []byte) ([]byte, error) {
block, err := aes.NewCipher(SHA256Hmac(salt, key))
block, err := aes.NewCipher(SHA256Hmac(key, salt))
if err != nil {
return nil, err
}
Expand All @@ -111,7 +111,7 @@ func AESDecrypt(salt, key, cipherData []byte) ([]byte, error) {
if len(cipherData) < aes.BlockSize+sha1.Size {
return nil, errors.New("invalid cipher data")
}
block, err := aes.NewCipher(SHA256Hmac(salt, key))
block, err := aes.NewCipher(SHA256Hmac(key, salt))
if err != nil {
return nil, err
}
Expand All @@ -138,7 +138,7 @@ func AESEncryptStr(salt []byte, key, plainText string) (string, error) {
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(data), nil
return base64.RawURLEncoding.EncodeToString(data), nil
}

// AESDecryptStr decrypt data that encrypted by AESDecryptStr
Expand All @@ -147,7 +147,7 @@ func AESEncryptStr(salt []byte, key, plainText string) (string, error) {
// fmt.Println(crypto.AESDecryptStr(nil, "my key", cipherData)) // no salt
//
func AESDecryptStr(salt []byte, key, cipherText string) (string, error) {
cipherData, err := base64.StdEncoding.DecodeString(cipherText)
cipherData, err := base64.RawURLEncoding.DecodeString(cipherText)
if err != nil {
return "", err
}
Expand All @@ -159,7 +159,7 @@ func AESDecryptStr(salt []byte, key, cipherText string) (string, error) {
return string(data), nil
}

// SignPass generates a string checkPass with PBKDF2 by the user' id and pass.
// SignPass generates a string checkPass with PBKDF2 & SHA-3 by the user' id and pass.
// http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf
// recommended salt length >= 16 bytes
// default iter count is 12480
Expand All @@ -170,8 +170,8 @@ func AESDecryptStr(salt []byte, key, cipherText string) (string, error) {
// fmt.Println(crypto.SignPass([]byte("salt..."), "user_id", "user_password"), 1024, 32)
//
func SignPass(salt []byte, id, pass string, args ...int) (checkPass string) {
b := signPass(salt, RandN(8), SHA256Hmac([]byte(id), []byte(pass)), args...)
return base64.StdEncoding.EncodeToString(b)
b := signPass(salt, RandN(8), HmacSum(sha3.New256, []byte(pass), []byte(id)), args...)
return base64.RawURLEncoding.EncodeToString(b)
}

func signPass(salt, iv, pass []byte, args ...int) []byte {
Expand All @@ -180,10 +180,10 @@ func signPass(salt, iv, pass []byte, args ...int) []byte {
if len(args) > 0 && args[0] >= 1000 {
iterCount = args[0]
}
if len(args) > 1 && args[1] >= 14 {
if len(args) > 1 && args[1] >= 14 { // recommended minimum length
keylen = args[1]
}
b := pbkdf2.Key(append(pass, iv...), salt, iterCount, keylen, sha512.New)
b := pbkdf2.Key(append(pass, iv...), salt, iterCount, keylen, sha3.New512)
return append(b, iv...)
}

Expand All @@ -192,44 +192,45 @@ func signPass(salt, iv, pass []byte, args ...int) []byte {
// fmt.Println(crypto.VerifyPass([]byte("salt..."), "user_id", "user_password", checkPass))
//
func VerifyPass(salt []byte, id, pass, checkPass string, args ...int) bool {
a, err := base64.StdEncoding.DecodeString(checkPass)
if err != nil || len(a) < 22 {
a, err := base64.RawURLEncoding.DecodeString(checkPass)
if err != nil || len(a) < 22 { // l4 + 8
return false
}
return Equal(a, signPass(salt, a[len(a)-8:], SHA256Hmac([]byte(id), []byte(pass)), args...))
return Equal(a, signPass(salt, a[len(a)-8:], HmacSum(sha3.New256, []byte(pass), []byte(id)), args...))
}

// SignState generates a state string signed by SHA1.
// SignState generates a state string signed with SHA1.
// It is useful for OAUTH2 or Web hook callback.
//
// fmt.Println(SignState([]byte("my key"), "")
// // 1489326422.3ee088ac07612458db566fd94609a1c3
// // 2932b947b464b598643b46ab11ebb3deb3f45dcb78e107a4.1489725353
// fmt.Println(SignState([]byte("my key"), "admin")
// // cb374f9a1d2c3e8e56fe76c7e0770531eed27c89beae9de4.1489725353
//
func SignState(key []byte, uid string) string {
ts := time.Now().Unix()
iv := RandN(4)
prefix := fmt.Sprintf(`%d.%x`, ts, iv)
return prefix + signState(key, prefix+uid)
suffix := fmt.Sprintf(`%x.%d`, iv, ts)
return signState(key, uid+suffix) + suffix
}

func signState(key []byte, str string) string {
return fmt.Sprintf(`%x`, HmacSum(sha1.New, key, []byte(str))[0:12])
return hex.EncodeToString(HmacSum(sha1.New, key, []byte(str)))
}

// VerifyState Verify state that generated by SignState.
//
// fmt.Println(VerifyState([]byte("my key"), "", state, 60* time.Second)
//
func VerifyState(key []byte, uid, state string, expire time.Duration) bool {
s := strings.Split(state, ".")
if len(s) != 2 {
if len(state) < 50 {
return false
}
i, err := strconv.ParseInt(s[0], 10, 64)
i, err := strconv.ParseInt(state[49:], 10, 64)
if err != nil || (i+int64(expire)/1e9) < time.Now().Unix() {
return false
}
return s[1][8:] == signState(key, fmt.Sprintf(`%s.%s%s`, s[0], s[1][0:8], uid))
return state[0:40] == signState(key, uid+state[40:])
}

// Rotating is used to verify data through a rotating credential system,
Expand Down
42 changes: 42 additions & 0 deletions crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"github.com/stretchr/testify/assert"
"golang.org/x/crypto/sha3"
)

func TestCrypto(t *testing.T) {
Expand Down Expand Up @@ -54,6 +55,11 @@ func TestCrypto(t *testing.T) {
hex.EncodeToString(HashSum(sha256.New, []byte{})))
assert.Equal("72726d8818f693066ceb69afa364218b692e62ea92b385782363780f47529c21",
hex.EncodeToString(HashSum(sha256.New, []byte("中文"))))

assert.Equal("a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a",
hex.EncodeToString(HashSum(sha3.New256, []byte{})))
assert.Equal("ac5305da3d18be1aed44aa7c70ea548da243a59a5fd546f489348fd5718fb1a0",
hex.EncodeToString(HashSum(sha3.New256, []byte("中文"))))
})

t.Run("HmacSum", func(t *testing.T) {
Expand All @@ -80,6 +86,17 @@ func TestCrypto(t *testing.T) {
hex.EncodeToString(HmacSum(sha256.New, []byte("abc"), []byte{})))
assert.Equal("e61f53e7c7932bf37ecf8b704866549c03fc7250cd1d3708bef92c02d09cd9fe",
hex.EncodeToString(HmacSum(sha256.New, []byte("abc"), []byte("中文"))))

assert.Equal("e841c164e5b4f10c9f3985587962af72fd607a951196fc92fb3a5251941784ea",
hex.EncodeToString(HmacSum(sha3.New256, nil, []byte{})))
assert.Equal("e841c164e5b4f10c9f3985587962af72fd607a951196fc92fb3a5251941784ea",
hex.EncodeToString(HmacSum(sha3.New256, []byte{}, []byte{})))
assert.Equal("f316ce909c86a8f51c0df568a9782c2b934ba406da8646026c22977b7273a5c9",
hex.EncodeToString(HmacSum(sha3.New256, nil, []byte("中文"))))
assert.Equal("c9b2ba2847b0057387fe8949261677346dbd319c2148947cbe81bc681b0f81db",
hex.EncodeToString(HmacSum(sha3.New256, []byte("abc"), []byte{})))
assert.Equal("7eeab728f66a2fe8c2df8059e0929e3eaf12fdad43210a299d008086b7bc116e",
hex.EncodeToString(HmacSum(sha3.New256, []byte("abc"), []byte("中文"))))
})

t.Run("SHA256Hmac", func(t *testing.T) {
Expand Down Expand Up @@ -156,6 +173,7 @@ func TestCrypto(t *testing.T) {
assert.False(VerifyPass(salt, "admin1", "test pass", checkPass))
assert.False(VerifyPass(salt, "admin", "test pass1", checkPass))
assert.False(VerifyPass(salt, "admin", "test pass", checkPass[1:]))
assert.False(VerifyPass(salt, "admin", "test pass", checkPass[:10]))

checkPass = SignPass(nil, "admin", "test pass")
assert.True(VerifyPass(nil, "admin", "test pass", checkPass))
Expand All @@ -178,6 +196,9 @@ func TestCrypto(t *testing.T) {
assert.False(VerifyState(RandN(12), "", state, time.Second))
time.Sleep(2 * time.Second)
assert.False(VerifyState(key, "", state, time.Second))

state = SignState(key, "cb374f9a1d2c3e8e56fe76c7e0770531eed27c89beae9de4")
assert.True(VerifyState(key, "cb374f9a1d2c3e8e56fe76c7e0770531eed27c89beae9de4", state, time.Second))
})

t.Run("Rotating", func(t *testing.T) {
Expand Down Expand Up @@ -237,3 +258,24 @@ func TestCrypto(t *testing.T) {
}))
})
}

// go test -bench=.
func BenchmarkSHA2(b *testing.B) {
b.N = 1000000
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
HashSum(sha256.New, []byte{})
}
}

func BenchmarkSHA3(b *testing.B) {
b.N = 100000
b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
HashSum(sha3.New256, []byte{})
}
}
79 changes: 0 additions & 79 deletions pbkdf2/pbkdf2.go

This file was deleted.

Loading

0 comments on commit 2b9acce

Please sign in to comment.