15 April 2025

IMG-THUMBNAIL

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。

原理

  1. 生成密钥:首先,需要为用户生成一个唯一的密钥(Key),这个密钥通常是一个长度为 160 位的随机数,以十六进制表示。例如:3132333435363738393031323334353637383930。这个密钥会同时存储在服务器端和用户的设备(如手机令牌)中。
  2. 计算时间戳:计算当前的时间戳,通常以秒为单位。例如,当前时间是 2024 年 1 月 1 日 12:00:00,对应的时间戳(从 1970 年 1 月 1 日 00:00:00 到当前时间的秒数)可能是1704115200。
  3. 时间戳处理:将时间戳除以一个固定的时间间隔(T),得到一个整数。这个时间间隔通常是 30 秒或 60 秒,这里假设时间间隔为 30 秒。则1704115200 // 30 = 56803840。
  4. 生成哈希值:将处理后的时间戳和密钥作为输入,使用哈希函数(如 SHA - 1)进行计算,得到一个哈希值。计算得到的哈希值是一个 20 字节的二进制数据,例如:\x03\xcd>\xa9\x84\x9c\x94\x13\x1b\x80\xcd\x0c\xe6\x18\x1d\x0b\x87\x05\x9b\x93
  5. 生成 OTP:从哈希值中提取特定的字节,根据这些字节计算出一个数字,再将这个数字转换为指定长度的 OTP。通常是取哈希值的最后一个字节的低 4 位作为偏移量,从哈希值中偏移量开始的 4 个字节作为一个 32 位的整数,然后对这个整数取模1000000(得到 6 位数字的 OTP)。例如,哈希值的最后一个字节是\x93,低 4 位是0011,对应的十进制数是 3。从哈希值的第 3 个字节开始取 4 个字节\xcd>\xa9\x84,转换为 32 位整数是3405691780,对1000000取模得到569178,这就是最终生成的 OTP。
  6. 验证 OTP:当用户进行身份验证时,服务器和用户的设备都会按照相同的算法和密钥计算出一个 OTP。服务器和用户的设备会比较这两个 OTP 是否一致,如果一致,则表示身份验证通过。如果不一致,则表示身份验证失败。
  7. 配置信息一般是通过二维码同步到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