From 4d99e64b0e1b197f63e5a2fc986e14c58c88e0fc Mon Sep 17 00:00:00 2001 From: Dave Chapeskie Date: Sun, 21 Sep 2014 16:24:07 -0400 Subject: [PATCH 1/7] Use const instead of ParseDuration, drop math.Floor call. Also add a simple benchmark, and fix one of the test messages. --- onetime.go | 4 ++-- onetime_test.go | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/onetime.go b/onetime.go index 90af1f7..68cc85d 100644 --- a/onetime.go +++ b/onetime.go @@ -53,7 +53,7 @@ func Simple(digit int) (otp OneTimePassword, err error) { err = errors.New("An HTOP code cannot be longer than 9 digits.") return } - step, _ := time.ParseDuration("30s") + const step = 30 * time.Second otp = OneTimePassword{digit, step, time.Unix(0, 0), sha1.New} return } @@ -65,7 +65,7 @@ func (otp *OneTimePassword) TOTP(secret []byte) uint { func (otp *OneTimePassword) steps(now time.Time) uint64 { elapsed := now.Unix() - otp.BaseTime.Unix() - return uint64(math.Floor(float64(elapsed) / otp.TimeStep.Seconds())) + return uint64(float64(elapsed) / otp.TimeStep.Seconds()) } func dt(hs []byte) []byte { diff --git a/onetime_test.go b/onetime_test.go index df00d67..93096fe 100644 --- a/onetime_test.go +++ b/onetime_test.go @@ -69,7 +69,7 @@ func TestTOTP(t *testing.T) { } digit := 8 - step, _ := time.ParseDuration("30s") + const step = 30 * time.Second otps := []OneTimePassword{ OneTimePassword{digit, step, time.Unix(0, 0), sha1.New}, OneTimePassword{digit, step, time.Unix(0, 0), sha256.New}, @@ -88,8 +88,20 @@ func TestTOTP(t *testing.T) { secret := secrets[i%3] now := times[i/3] if v := otp.HOTP(secret, otp.steps(now)); v != exp { - t.Errorf("%s", uint64(now.Unix()-otp.BaseTime.Unix())) + t.Error("time:", uint64(now.Unix()-otp.BaseTime.Unix())) t.Errorf("TOTP(secret) = %v, want %v (time: %v, hash: %v)", v, exp, now, otp.Hash) } } } + +func BenchmarkHOTPsha256(b *testing.B) { + epoch := time.Unix(0, 0) + otp := OneTimePassword{8, 30 * time.Second, epoch, sha256.New} + keyPart := "1234567890" + secret := []byte(strings.Repeat(keyPart, 4)) + now := time.Unix(20000000000, 0) + b.ResetTimer() + for i := 0;i Date: Sun, 21 Sep 2014 16:28:32 -0400 Subject: [PATCH 2/7] Remove old unneeded ignore entries. None of those files are produced by a modern go test/build/install with a reasonable environment. --- .gitignore | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 0026861..379c093 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,2 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - +*.test *.exe From f9fe8b97cb853ad8db43de71715492732fc2f9a9 Mon Sep 17 00:00:00 2001 From: Dave Chapeskie Date: Sun, 21 Sep 2014 16:33:39 -0400 Subject: [PATCH 3/7] Silence golint by removing indenting in package comment and making errors non-sentences. --- onetime.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/onetime.go b/onetime.go index 68cc85d..762286c 100644 --- a/onetime.go +++ b/onetime.go @@ -1,8 +1,6 @@ -/* - Package onetime provides a library for one-time password generation, - implementing the HOTP and TOTP algorithms as specified by IETF RFC-4226 - and RFC-6238. -*/ +// Package onetime provides a library for one-time password generation, +// implementing the HOTP and TOTP algorithms as specified by IETF RFC-4226 +// and RFC-6238. package onetime import ( @@ -47,10 +45,10 @@ func (otp *OneTimePassword) truncate(hs []byte) uint { // 30 seconds as the step length. func Simple(digit int) (otp OneTimePassword, err error) { if digit < 6 { - err = errors.New("A minimum of 6 digits is required for a valid HTOP code.") + err = errors.New("minimum of 6 digits is required for a valid HTOP code") return } else if digit > 9 { - err = errors.New("An HTOP code cannot be longer than 9 digits.") + err = errors.New("HTOP code cannot be longer than 9 digits") return } const step = 30 * time.Second From 9fd3d2876ab2f50467bbacff5edad0ee7a9fd016 Mon Sep 17 00:00:00 2001 From: Dave Chapeskie Date: Sun, 21 Sep 2014 16:34:35 -0400 Subject: [PATCH 4/7] go fmt --- onetime.go | 74 ++++++++++----------- onetime_test.go | 172 ++++++++++++++++++++++++------------------------ 2 files changed, 123 insertions(+), 123 deletions(-) diff --git a/onetime.go b/onetime.go index 762286c..06e9fad 100644 --- a/onetime.go +++ b/onetime.go @@ -4,71 +4,71 @@ package onetime import ( - "crypto/hmac" - "crypto/sha1" - "encoding/binary" - "errors" - "hash" - "math" - "time" + "crypto/hmac" + "crypto/sha1" + "encoding/binary" + "errors" + "hash" + "math" + "time" ) // OneTimePassword stores the configuration values relevant to HOTP/TOTP calculations. type OneTimePassword struct { - Digit int // Length of code generated - TimeStep time.Duration // Length of each time step for TOTP - BaseTime time.Time // The start time for TOTP step calculation - Hash func() hash.Hash // Hash algorithm used with HMAC + Digit int // Length of code generated + TimeStep time.Duration // Length of each time step for TOTP + BaseTime time.Time // The start time for TOTP step calculation + Hash func() hash.Hash // Hash algorithm used with HMAC } // HOTP returns a HOTP code with the given secret and counter. func (otp *OneTimePassword) HOTP(secret []byte, count uint64) uint { - hs := otp.hmacSum(secret, count) - return otp.truncate(hs) + hs := otp.hmacSum(secret, count) + return otp.truncate(hs) } func (otp *OneTimePassword) hmacSum(secret []byte, count uint64) []byte { - mac := hmac.New(otp.Hash, secret) - binary.Write(mac, binary.BigEndian, count) - return mac.Sum(nil) + mac := hmac.New(otp.Hash, secret) + binary.Write(mac, binary.BigEndian, count) + return mac.Sum(nil) } func (otp *OneTimePassword) truncate(hs []byte) uint { - sbits := dt(hs) - snum := uint(sbits[3]) | uint(sbits[2])<<8 - snum |= uint(sbits[1])<<16 | uint(sbits[0])<<24 - return snum % uint(math.Pow(10, float64(otp.Digit))) + sbits := dt(hs) + snum := uint(sbits[3]) | uint(sbits[2])<<8 + snum |= uint(sbits[1])<<16 | uint(sbits[0])<<24 + return snum % uint(math.Pow(10, float64(otp.Digit))) } // Simple returns a new OneTimePassword with the specified HTOP code length, // SHA-1 as the HMAC hash algorithm, the Unix epoch as the base time, and // 30 seconds as the step length. func Simple(digit int) (otp OneTimePassword, err error) { - if digit < 6 { - err = errors.New("minimum of 6 digits is required for a valid HTOP code") - return - } else if digit > 9 { - err = errors.New("HTOP code cannot be longer than 9 digits") - return - } - const step = 30 * time.Second - otp = OneTimePassword{digit, step, time.Unix(0, 0), sha1.New} - return + if digit < 6 { + err = errors.New("minimum of 6 digits is required for a valid HTOP code") + return + } else if digit > 9 { + err = errors.New("HTOP code cannot be longer than 9 digits") + return + } + const step = 30 * time.Second + otp = OneTimePassword{digit, step, time.Unix(0, 0), sha1.New} + return } // TOTP returns a TOTP code calculated with the current time and the given secret. func (otp *OneTimePassword) TOTP(secret []byte) uint { - return otp.HOTP(secret, otp.steps(time.Now())) + return otp.HOTP(secret, otp.steps(time.Now())) } func (otp *OneTimePassword) steps(now time.Time) uint64 { - elapsed := now.Unix() - otp.BaseTime.Unix() - return uint64(float64(elapsed) / otp.TimeStep.Seconds()) + elapsed := now.Unix() - otp.BaseTime.Unix() + return uint64(float64(elapsed) / otp.TimeStep.Seconds()) } func dt(hs []byte) []byte { - offset := int(hs[len(hs)-1] & 0xf) - p := hs[offset : offset+4] - p[0] &= 0x7f - return p + offset := int(hs[len(hs)-1] & 0xf) + p := hs[offset : offset+4] + p[0] &= 0x7f + return p } diff --git a/onetime_test.go b/onetime_test.go index 93096fe..aa7b899 100644 --- a/onetime_test.go +++ b/onetime_test.go @@ -1,107 +1,107 @@ package onetime import ( - "crypto/sha1" - "crypto/sha256" - "crypto/sha512" - "strings" - "testing" - "time" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "strings" + "testing" + "time" ) -// TestHOTP validates the HOTP implementation against test values provided in +// TestHOTP validates the HOTP implementation against test values provided in // Appendix D of RFC-4226. func TestHOTP(t *testing.T) { - expected := []uint{ - 755224, - 287082, - 359152, - 969429, - 338314, - 254676, - 287922, - 162583, - 399871, - 520489, - } - secret := []byte("12345678901234567890") + expected := []uint{ + 755224, + 287082, + 359152, + 969429, + 338314, + 254676, + 287922, + 162583, + 399871, + 520489, + } + secret := []byte("12345678901234567890") - otp, _ := Simple(6) + otp, _ := Simple(6) - for i, exp := range expected { - if v := otp.HOTP(secret, uint64(i)); v != exp { - t.Errorf("HOTP(secret, %v) = %v, want %v", i, v, exp) - } - } + for i, exp := range expected { + if v := otp.HOTP(secret, uint64(i)); v != exp { + t.Errorf("HOTP(secret, %v) = %v, want %v", i, v, exp) + } + } } -// TestTOTP validates the TOTP implementation against test values provided in +// TestTOTP validates the TOTP implementation against test values provided in // Appendix B of RFC-6238. func TestTOTP(t *testing.T) { - expected := []uint{ - 94287082, - 46119246, - 90693936, - 7081804, - 68084774, - 25091201, - 14050471, - 67062674, - 99943326, - 89005924, - 91819424, - 93441116, - 69279037, - 90698825, - 38618901, - 65353130, - 77737706, - 47863826, - } + expected := []uint{ + 94287082, + 46119246, + 90693936, + 7081804, + 68084774, + 25091201, + 14050471, + 67062674, + 99943326, + 89005924, + 91819424, + 93441116, + 69279037, + 90698825, + 38618901, + 65353130, + 77737706, + 47863826, + } - times := []time.Time{ - time.Unix(59, 0), - time.Unix(1111111109, 0), - time.Unix(1111111111, 0), - time.Unix(1234567890, 0), - time.Unix(2000000000, 0), - time.Unix(20000000000, 0), - } + times := []time.Time{ + time.Unix(59, 0), + time.Unix(1111111109, 0), + time.Unix(1111111111, 0), + time.Unix(1234567890, 0), + time.Unix(2000000000, 0), + time.Unix(20000000000, 0), + } - digit := 8 - const step = 30 * time.Second - otps := []OneTimePassword{ - OneTimePassword{digit, step, time.Unix(0, 0), sha1.New}, - OneTimePassword{digit, step, time.Unix(0, 0), sha256.New}, - OneTimePassword{digit, step, time.Unix(0, 0), sha512.New}, - } + digit := 8 + const step = 30 * time.Second + otps := []OneTimePassword{ + OneTimePassword{digit, step, time.Unix(0, 0), sha1.New}, + OneTimePassword{digit, step, time.Unix(0, 0), sha256.New}, + OneTimePassword{digit, step, time.Unix(0, 0), sha512.New}, + } - keyPart := "1234567890" - secrets := [][]byte{ - []byte(strings.Repeat(keyPart, 2)), - []byte(strings.Repeat(keyPart, 4)[:32]), - []byte(strings.Repeat(keyPart, 8)[:64]), - } + keyPart := "1234567890" + secrets := [][]byte{ + []byte(strings.Repeat(keyPart, 2)), + []byte(strings.Repeat(keyPart, 4)[:32]), + []byte(strings.Repeat(keyPart, 8)[:64]), + } - for i, exp := range expected { - otp := otps[i%3] - secret := secrets[i%3] - now := times[i/3] - if v := otp.HOTP(secret, otp.steps(now)); v != exp { - t.Error("time:", uint64(now.Unix()-otp.BaseTime.Unix())) - t.Errorf("TOTP(secret) = %v, want %v (time: %v, hash: %v)", v, exp, now, otp.Hash) - } - } + for i, exp := range expected { + otp := otps[i%3] + secret := secrets[i%3] + now := times[i/3] + if v := otp.HOTP(secret, otp.steps(now)); v != exp { + t.Error("time:", uint64(now.Unix()-otp.BaseTime.Unix())) + t.Errorf("TOTP(secret) = %v, want %v (time: %v, hash: %v)", v, exp, now, otp.Hash) + } + } } func BenchmarkHOTPsha256(b *testing.B) { - epoch := time.Unix(0, 0) - otp := OneTimePassword{8, 30 * time.Second, epoch, sha256.New} - keyPart := "1234567890" - secret := []byte(strings.Repeat(keyPart, 4)) - now := time.Unix(20000000000, 0) - b.ResetTimer() - for i := 0;i Date: Sun, 21 Sep 2014 16:36:16 -0400 Subject: [PATCH 5/7] Use const rather than ParseDuration --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5607e99..464d3c6 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ import ( ) var secret = []byte("SOME_SECRET") -var ts, _ = time.ParseDuration("5s") +const ts = 5 * time.Second var t = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) var otp = onetime.OneTimePassword{Digit: 9, TimeStep: ts, BaseTime: t, Hash: sha256.New} var code = otp.TOTP(secret) @@ -48,4 +48,4 @@ Package doc can be found [at pkgdoc.org](http://go.pkgdoc.org/github.com/gwwfps/ License ------- -This library is released under a simplified BSD license. \ No newline at end of file +This library is released under a simplified BSD license. From 1170007609ae4642f0e6ebb56c4f49cf7ca8e90e Mon Sep 17 00:00:00 2001 From: Dave Chapeskie Date: Sun, 21 Sep 2014 16:54:39 -0400 Subject: [PATCH 6/7] Fix examples in README and add them as test examples. --- README.md | 6 +++--- example_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 example_test.go diff --git a/README.md b/README.md index 464d3c6..9af5085 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ import "onetime" var secret = []byte("SOME_SECRET") var counter = 123456 -var otp = onetime.Simple(6) +var otp, _ = onetime.Simple(6) var code = otp.HOTP(secret, counter) ``` @@ -23,7 +23,7 @@ Google authenticator style 8-digit TOTP code: import "onetime" var secret = []byte("SOME_SECRET") -var otp = onetime.Simple(8) +var otp, _ = onetime.Simple(8) var code = otp.TOTP(secret) ``` @@ -38,7 +38,7 @@ import ( var secret = []byte("SOME_SECRET") const ts = 5 * time.Second var t = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) -var otp = onetime.OneTimePassword{Digit: 9, TimeStep: ts, BaseTime: t, Hash: sha256.New} +var otp = onetime.OneTimePassword{Digit: 9, TimeStep: ts, BaseTime: t, Hash: sha256.New} var code = otp.TOTP(secret) ``` diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..fe8ac24 --- /dev/null +++ b/example_test.go @@ -0,0 +1,39 @@ +package onetime + +import ( + "crypto/sha256" + "fmt" + "time" + + "github.com/gwwfps/onetime" +) + +func Example_simple() { + // Simple 6-digit HOTP code: + var secret = []byte("SOME_SECRET") + var counter uint64 = 123456 + var otp, _ = onetime.Simple(6) + var code = otp.HOTP(secret, counter) + fmt.Println(code) + // Output: + // 260040 +} + +func Example_authenticator() { + // Google authenticator style 8-digit TOTP code: + var secret = []byte("SOME_SECRET") + var otp, _ = onetime.Simple(8) + var code = otp.TOTP(secret) + fmt.Println(code) +} + +func Example_custom() { + // 9-digit 5-second-step TOTP starting on midnight 2000-01-01 UTC, using SHA-256: + + var secret = []byte("SOME_SECRET") + const ts = 5 * time.Second + var t = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) + var otp = onetime.OneTimePassword{Digit: 9, TimeStep: ts, BaseTime: t, Hash: sha256.New} + var code = otp.TOTP(secret) + fmt.Println(code) +} From 7dc45271e0509e0062d7e2deb5126fde5bc5177e Mon Sep 17 00:00:00 2001 From: Dave Chapeskie Date: Sun, 21 Sep 2014 16:55:57 -0400 Subject: [PATCH 7/7] Update go doc link. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9af5085..d7efdb4 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ var code = otp.TOTP(secret) Documentation ------------- -Package doc can be found [at pkgdoc.org](http://go.pkgdoc.org/github.com/gwwfps/onetime). +Package doc can be found [at godoc.org](http://godoc.org/github.com/gwwfps/onetime). License -------