OTP原理分析
15 April 2025
OTP 是一次性密码(One-Time Password)的缩写,它是一种用于身份验证的安全机制。本文会介绍其原理和Go语言应用。
OTP 是一次性密码(One-Time Password)的缩写,它是一种用于身份验证的安全机制,下次登录或进行身份验证时需要生成新的密码,从而大大提高了安全性。
生成方式
- 基于时间同步 TOTP(Time - based One - Time Password):服务器和用户的设备(如手机令牌)都有精确的时钟,它们通过网络进行时间同步。服务器根据当前时间以及用户的密钥,按照特定的算法生成一个一次性密码。用户的设备也使用相同的算法和密钥,基于本地时间生成相同的密码。由于双方时间同步,生成的 OTP 在特定时间内是一致的。例如,每隔 30 秒或 60 秒,OTP 就会更新一次。
- 基于事件同步:这种方式是基于特定的事件来生成 OTP,而不是时间。例如,用户每次按下令牌上的按钮,或者进行特定的操作(如在手机应用中进行特定手势操作),令牌会根据一个密钥和当前的事件计数生成一个新的 OTP。服务器端也会记录用户的事件计数,并使用相同的算法和密钥生成相应的密码,以验证用户身份。
常用的是基于时间同步的方式,可以安装 Google 提供的 App Authenticator 来生成 OTP。
原理
- 生成密钥:首先,需要为用户生成一个唯一的密钥(Key),这个密钥通常是一个长度为 160 位的随机数,以十六进制表示。例如:3132333435363738393031323334353637383930。这个密钥会同时存储在服务器端和用户的设备(如手机令牌)中。
- 计算时间戳:计算当前的时间戳,通常以秒为单位。例如,当前时间是 2024 年 1 月 1 日 12:00:00,对应的时间戳(从 1970 年 1 月 1 日 00:00:00 到当前时间的秒数)可能是1704115200。
- 时间戳处理:将时间戳除以一个固定的时间间隔(T),得到一个整数。这个时间间隔通常是 30 秒或 60 秒,这里假设时间间隔为 30 秒。则1704115200 // 30 = 56803840。
- 生成哈希值:将处理后的时间戳和密钥作为输入,使用哈希函数(如 SHA - 1)进行计算,得到一个哈希值。计算得到的哈希值是一个 20 字节的二进制数据,例如:
\x03\xcd>\xa9\x84\x9c\x94\x13\x1b\x80\xcd\x0c\xe6\x18\x1d\x0b\x87\x05\x9b\x93
。 - 生成 OTP:从哈希值中提取特定的字节,根据这些字节计算出一个数字,再将这个数字转换为指定长度的 OTP。通常是取哈希值的最后一个字节的低 4 位作为偏移量,从哈希值中偏移量开始的 4 个字节作为一个 32 位的整数,然后对这个整数取模1000000(得到 6 位数字的 OTP)。例如,哈希值的最后一个字节是\x93,低 4 位是0011,对应的十进制数是 3。从哈希值的第 3 个字节开始取 4 个字节\xcd>\xa9\x84,转换为 32 位整数是3405691780,对1000000取模得到569178,这就是最终生成的 OTP。
- 验证 OTP:当用户进行身份验证时,服务器和用户的设备都会按照相同的算法和密钥计算出一个 OTP。服务器和用户的设备会比较这两个 OTP 是否一致,如果一致,则表示身份验证通过。如果不一致,则表示身份验证失败。
- 配置信息一般是通过二维码同步到App上,二维码的内容如下:
otpauth://totp/cyeam.com:test@cyeam.com?algorithm=SHA1&digits=6&issuer=cyeam.com&period=30&secret=AAAW7EIEWF3U7S5EX4LRU4TVKZSCTOJNNR62UXBGIEJOUDG54W7Q
Go语言实现
生成二维码
package main
import (
"bytes"
"fmt"
"image/png"
"os"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
)
var optKey *otp.Key
func InitOtp() {
var err error
optKey, err = totp.Generate(totp.GenerateOpts{
Issuer: "cyeam.com",
AccountName: "test@cyeam.com",
SecretSize: 32,
})
if err != nil {
panic(err)
}
optLog()
}
func optLog() {
var buf bytes.Buffer
img, err := optKey.Image(200, 200)
if err != nil {
panic(err)
}
png.Encode(&buf, img)
// display the QR code to the user.
fmt.Printf("Issuer: %s\n", optKey.Issuer())
fmt.Printf("Key: %s\n", optKey.String())
fmt.Printf("Account Name: %s\n", optKey.AccountName())
fmt.Printf("Secret: %s\n", optKey.Secret())
fmt.Println("Writing PNG to qr-code.png....")
os.WriteFile("qr-code.png", buf.Bytes(), 0644)
fmt.Println("Please add your TOTP to your OTP Application now!")
}
验证OTP
func OptValidate(passcode string, k *otp.Key) bool {
valid := totp.Validate(passcode, k.Secret())
return valid
}
原文链接:OTP原理分析,转载请注明来源!
–EOF–