package otp import "strconv" // HOTPKeyOptions represents the settings or values to use when creating a HOTP Key type HOTPKeyOptions struct { KeyOptions Counter uint64 } // NewHOTPKeyOptions returns a HOTPKeyOptions using sane default values and a secret key func NewHOTPKeyOptions() HOTPKeyOptions { return HOTPKeyOptions{ NewKeyOptions(), DefaultCounter, } } // HOTPKey represents the HOTP family of OTP as found in RFC4226 type HOTPKey struct { baseKey counter uint64 } // NewHOTPKey creates and returns a new HOTP key // If no KeyOptions are provided, sane defaults and a random secret will be used func NewHOTPKey(o ...HOTPKeyOptions) Key { var opts HOTPKeyOptions if len(o) == 0 { opts = NewHOTPKeyOptions() } else { opts = o[0] } h := &HOTPKey{} h.baseKey = baseKey{} h.opts = opts.KeyOptions h.keyType = HOTP h.counter = opts.Counter return h } // OTP produces a OTP code func (k *HOTPKey) OTP() string { // need to increment counter so that code is different each time // Google Authenticator appears to increment before generating the code k.counter++ i := getOTP(k.Secret(), k.counter, k.Length()) return k.formatCode(i) } // URL creates a relevant URL to distribute/share the key as detailed at https://github.com/google/google-authenticator/wiki/Key-Uri-Format func (k *HOTPKey) URL() string { u, vals := k.baseKey.URL() vals.Add("counter", strconv.FormatUint(k.counter, 10)) u.RawQuery = vals.Encode() return u.String() } // Verify compares the provided code with different potential codes within the HOTPKeys allowed skew range func (k *HOTPKey) Verify(code string, counter ...uint64) bool { c := k.counter if len(counter) > 0 { c = counter[0] } success := k.verify(c, code) if success { k.counter++ } return success } // IntegrityCheck provides information to verify the key is the same one used in Google Authenticator func (k *HOTPKey) IntegrityCheck() (string, uint64) { i := getOTP(k.Secret(), 0, k.Length()) return k.formatCode(i), k.counter }