1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
|
package gotp
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base32"
"fmt"
"hash"
"math"
"strings"
)
type Hasher struct {
HashName string
Digest func() hash.Hash
}
type OTP struct {
secret string // secret in base32 format
digits int // number of integers in the OTP. Some apps expect this to be 6 digits, others support more.
hasher *Hasher // digest function to use in the HMAC (expected to be sha1)
}
func NewOTP(secret string, digits int, hasher *Hasher) OTP {
if hasher == nil {
hasher = &Hasher{
HashName: "sha1",
Digest: sha1.New,
}
}
return OTP{
secret: secret,
digits: digits,
hasher: hasher,
}
}
/*
params
input: the HMAC counter value to use as the OTP input. Usually either the counter, or the computed integer based on the Unix timestamp
*/
func (o *OTP) generateOTP(input int) string {
if input < 0 {
panic("input must be positive integer")
}
hasher := hmac.New(o.hasher.Digest, o.byteSecret())
hasher.Write(Itob(input))
hmacHash := hasher.Sum(nil)
offset := int(hmacHash[len(hmacHash)-1] & 0xf)
code := ((int(hmacHash[offset]) & 0x7f) << 24) |
((int(hmacHash[offset+1] & 0xff)) << 16) |
((int(hmacHash[offset+2] & 0xff)) << 8) |
(int(hmacHash[offset+3]) & 0xff)
code = code % int(math.Pow10(o.digits))
return fmt.Sprintf(fmt.Sprintf("%%0%dd", o.digits), code)
}
func (o *OTP) byteSecret() []byte {
missingPadding := len(o.secret) % 8
if missingPadding != 0 {
o.secret = o.secret + strings.Repeat("=", 8-missingPadding)
}
bytes, err := base32.StdEncoding.DecodeString(o.secret)
if err != nil {
panic("decode secret failed")
}
return bytes
}
|