Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 1 addition & 21 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
```

Expand All @@ -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)
```

Expand All @@ -36,16 +36,16 @@ 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 otp = onetime.OneTimePassword{Digit: 9, TimeStep: ts, BaseTime: t, Hash: sha256.New}
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
-------
This library is released under a simplified BSD license.
This library is released under a simplified BSD license.
39 changes: 39 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
82 changes: 40 additions & 42 deletions onetime.go
Original file line number Diff line number Diff line change
@@ -1,76 +1,74 @@
/*
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 (
"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("A 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.")
return
}
step, _ := time.ParseDuration("30s")
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(math.Floor(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
}
Loading