一次性密码 One Time Password, 简称 OTP。
动态密码优点:
- 由服务器和工具生成密码,无须记录。
- 随机生产,一次有效性。
- 根据时间计算,有时效性。
动态密码的产生方式,主要是以时间差做为服务器与密码产生器的同步条件。在需要登录的时候,就利用密码产生器产生动态密码,OTP一般分为计次使用以及计时使用两种,计次使用的OTP产出后,可在不限时间内使用;计时使用的OTP则可设置密码有效时间,从30秒到两分钟不等,而OTP在进行认证之后即废弃不用,下次认证必须使用新的密码,增加了试图不经授权访问有限制资源的难度。
国内一些令牌也是基于动态密码算法生成。 steam,itch, ali,google, 坚果云, 等等都采取了 OTP 来进行二次验证。
动态密码的分类
常见的动态密码有两类:
- 计次使用:计次使用的OTP产出后,可在不限时间内使用,知道下次成功使用后,计数器加 1,生成新的密码。用于实现计次使用动态密码的算法叫 HOTP,接下来会对这个算法展开介绍;
- 计时使用:计时使用的OTP则可设定密码有效时间,从30秒到两分钟不等,而OTP在进行认证之后即废弃不用,下次认证必须使用新的密码。用于实现计时使用动态密码的算法叫 TOTP,接下来会对这个算法展开介绍。
动态密码的基本认证原理是在认证双方共享密钥,也称种子密钥,并使用的同一个种子密钥对某一个事件计数、或时间值进行密码算法计算,使用的算法有对称算法、HASH、HMAC等,这个是所有动态密码算法实现的基础。
HOTP
HOTP 算法,全称是“An HMAC-Based One-Time Password Algorithm”,是一种基于事件计数的一次性密码生成算法,详细的算法介绍可以查看 RFC 4226。
算法本身可以用两条简短的表达式描述:
HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
PWD(K,C,digit) = HOTP(K,C) mod 10^Digit
公式中
- K 代表我们在认证服务器端以及密码生成端(客户设备)之间共享的密钥,在 RFC 4226 中,作者要求共享密钥最小长度是 128 位,而作者本身推荐使用 160 位长度的密钥
- C 表示事件计数的值,8 字节的整数,称为移动因子(moving factor),需要注意的是,这里的 C 的整数值需要用二进制的字符串表达,比如某个事件计数为 3,则C是 "11"(此处省略了前面的二进制的数字0)
- HMAC-SHA-1 表示对共享密钥以及移动因子进行 HMAC 的 SHA1 算法加密,得到 160 位长度(20字节)的加密结果,当然也可以使用HMAC-MD5等算法。
- Truncate 即截断函数,后面会详述
- digit 指定动态密码长度,比如我们常见的都是 6 位长度的动态密码
客户端和服务器事先协商好一个密钥K,用于一次性密码的生成过程,此密钥不被任何第三方所知道。此外,客户端和服务器各有一个计数器C,并且事先将计数值同步。
Truncate 截断函数
由于 SHA-1 算法是既有算法,不是我们讨论重点,故而 Truncate 函数就是整个算法中最为关键的部分了。以下引用 Truncate 函数的步骤说明:
DT(String) // String = String[0]…String[19]
Let OffsetBits be the low-order 4 bits of String[19]
Offset = StToNum(OffsetBits) // 0 <= OffSet <= 15
Let P = String[OffSet]...String[OffSet+3]
Return the Last 31 bits of P
结合上面的公式理解,大概的描述就是:
先从第一步通过 SHA-1 算法加密得到的 20 字节长度的结果中选取最后一个字节的低字节位的 4 位(注意:动态密码算法中采用的大端(big-endian)存储);
将这 4 位的二进制值转换为无标点数的整数值,得到 0 到 15(包含 0 和 15)之间的一个数,这个数字作为 20 个字节中从 0 开始的偏移量;
接着从指定偏移位开始,连续截取 4 个字节(32 位),最后返回 32 位中的后面 31 位。
回到算法本身,在获得 31 位的截断结果之后,我们将其又转换为无标点的大端表示的整数值,这个值的取值范围是 0 ~ $2^{31}$,也即 0 ~ 2.147483648E9,最后我们将这个数对10的乘方(digit 指数范围 1-10)取模,得到一个余值,对其前面补0得到指定位数的字符串。
TOTP
TOTP 算法,全称是 TOTP: Time-Based One-Time Password Algorithm,其基于 HOTP 算法实现,核心是将移动因子从 HOTP 中的事件计数改为时间差。完整的 TOTP 算法的说明可以查看 RFC 6238,其公式描述也非常简单:
TOTP = HOTP(K, T)
// T is an integer and represents the number of time steps between the initial counter time T0 and the current Unix time More specifically,
T = (Current Unix time – T0) / X, where the default floor function is used in the computation.
通常来说,TOTP 中所使用的时间差都是当前时间戳,TOTP 将时间差除以时间窗口(密码有效期,默认 30 秒)得到时间窗口计数,以此作为动态密码算法的移动因子,这样基于 HOTP 算法就能方便得到基于时间的动态密码了。
注意: RFC 6238 提到,在 TOTP 算法中,可以指定不同的键控哈希算法,比如在 HOTP 中使用的是 HMAC-SHA1 算法,而在 TOTP,除此之外,还可以使用 HMAC-SHA256 或者 HMAC-SHA512。
{# /images/otp.jpg #}
APP
- IOS Authenticator 开源(支持加密保存)
- IOS/ANDROID APP Google 身份验证器
code by golang
package otp
// For more info, please visit https://tools.ietf.org/html/rfc4226
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base32"
"encoding/binary"
"fmt"
"net/url"
"time"
)
//
// OTP(K,C) = Truncate(HMAC-SHA-1(K,C))
//
func OTP(secret string, counter int64) int {
key, err := base32.StdEncoding.DecodeString(secret)
if err != nil {
return -1
}
hash := hmac.New(sha1.New, key)
err = binary.Write(hash, binary.BigEndian, counter)
if err != nil {
return -1
}
hs := hash.Sum(nil)
offset := hs[19] & 0x0f
truncated := binary.BigEndian.Uint32(hs[offset : offset+4])
truncated &= 0x7fffffff
code := truncated % 1000000
return int(code)
}
// HOTP ...
type HOTP struct {
secret string
digits int
size int // 3-5 验证范围 totp 时候追加验证的范围
}
// At 生成
func (h HOTP) At(counter int64) int {
return OTP(h.secret, counter)
}
// Verify 验证 OTP code
func (h HOTP) Verify(code int, counter int64) bool {
return h.At(counter) == code
}
// NewHOTP 生成 HOTP instance
func NewHOTP(secret string, digits int) (h *HOTP) {
h = new(HOTP)
h.secret = secret
h.digits = digits
return
}
//-------------------
// TOTP 基于时间戳的双因子验证
type TOTP struct {
secret string
digits int
x int64 // 设定有效期,建议 30 ,30秒
UTC bool
}
// NewTOTP generate new TOTP instance
// digits 位数
// x 时间有效期 秒
func NewTOTP(secret string, digits, x int) (t *TOTP) {
t = new(TOTP)
t.secret = secret
t.digits = digits
t.x = int64(x)
return t
}
// At 获取时间因子 OTP code
func (t *TOTP) At(timestamp int64) int {
// 用时间戳当做算法的移动因子, 除去有效实际
counter := int64(timestamp / t.x)
return OTP(t.secret, counter)
}
// Now 获取当前时间 TOTP
func (t *TOTP) Now() int {
now := time.Now()
timestamp := now.Unix()
return t.At(timestamp)
}
// VerifyNow 验证当前时间 OTP code
func (t *TOTP) VerifyNow(code int) bool {
return t.Now() == code
}
// Verify 验证 OTP code
func (t *TOTP) Verify(code int, timestamp int64) bool {
return t.At(timestamp) == code
}
// URI 生成 Google Authenticator 字符串
// 用于二维码生成
// https://github.com/google/google-authenticator/wiki/Key-Uri-Format
func (t *TOTP) URI(user string, issuer string) string {
auth := "totp/"
q := make(url.Values)
// if HotpCounter > 0 {
// auth = "hotp/"
// q.Add("counter", strconv.Itoa(HotpCounter))
// }
q.Add("secret", t.secret)
q.Add("period", fmt.Sprintf("%d", t.x))
q.Add("digits", fmt.Sprintf("%d", t.digits))
q.Add("algorithm", "SHA1")
if issuer != "" {
q.Add("issuer", issuer)
auth += issuer + ":"
}
return "otpauth://" + auth + user + "?" + q.Encode()
}
// ScratchCode 救援码验证
func (t *TOTP) ScratchCode(Scratch ScratchCode, code int) bool {
return Scratch.Verify(code)
}
// ScratchCode 救援码
type ScratchCode interface {
Verify(code int) bool // 救援码验证
Generate() []string // 救援吗生成
}
// Scratch 救援码
type Scratch struct {
ScratchCodes []int
}
// Generate 救援吗生成
func (s *Scratch) Generate(code ...int) {
s.ScratchCodes = append(s.ScratchCodes, code...)
}
// Verify 救援码 ?? 修改为数据。。。
func (s *Scratch) Verify(code int) bool {
for i, v := range s.ScratchCodes {
if code == v {
// 存在则删除
l := len(s.ScratchCodes) - 1
s.ScratchCodes[i] = s.ScratchCodes[l]
s.ScratchCodes = s.ScratchCodes[0:l] // 复制(重置长度)
return true
}
}
return false
}
引用
- TOTP: Time-based One-time Password Algorithm, RFC Draft
- HOTP: An HMAC-Based One-Time Password Algorithm, RFC 4226
- [Time-based_One-time_Password_algorithm]https://en.wikipedia.org/wiki/Time-based_One-time_Password_algorithm)
- HMAC-based_One-time_Password_algorithm
- Google Authenticator project
- https://github.com/dgryski/dgoogauth
- https://www.youtube.com/watch?v=zMabEyrtPRg
- https://www.csdn.net/article/2014-09-23/2821808-Google-Authenticator
- https://www.cnblogs.com/voipman/p/6216328.html
- https://github.com/gitchs/gootp