什么是2fa?
2fa,即 two-factor authentication,双因素认证。在登录网站及计算机系统时,使用两个不同的认证因素来证明自己。双因素认证依赖用户提供的密码以及第二个因素,通常是一个token手机验证码,邮箱验证码或生物特征,例如指纹和面部。
为什么要用2fa?
平时使用密码对有些人来说已经是个挑战,我们经常忘记密码,不得不点击“忘记密码”,然后使用手机或邮箱进行重置。为何还要再添加新的验证方式?因为:
- 数据泄露使许密码的安全性降低。
- 多个站点使用同样的密码,黑客可以使用一个站点的密码进行撞库
- 弱密码。
认证因素是什么?
很多网站提供“安全性问题”,来辅助验证或作为密码找回的手段!例如“第一所学校是什么”,“喜欢的宠物叫什么“,”最喜欢的电影是什么“,… 这些是否可以作为第二因素进行验证?通常这些内容对于熟知我们的人来说,并不是秘密,可以通过社会工程学的方式绕开。所以这些并不能作为第二因素。
平时使用的过程中,我们常常会在填入用户名和密码后,会让选择发送”验证码“,或输入指纹等。刚开始玩游戏的(应该是梦幻西游),会用一个带一小块显示屏的设备,每按一次会产生一个新的数字串作为第二因素。那这个产生数字串的设备的工作原理是什么?当时对计算机没有什么概念,就知道另一个名字:电脑,以为产生数字串的设备是一个联网设备,与服务端共享同一串数字!
其实,它是使用了 TOTP(Time-based One-time Password)的概念,全称是基于时间的一次性密码。在客户端与服务端共享密码的前提下,根据时间生成了一串数字。
TOTP算法
使用下面的时间计数器生成一串根据时间的数字:
TC = floor((unit.Time() - unix.Time(T0))/TS
- TC: 时间计数器(Time Counter)
- T0: 约定的起始时间,可以使用有默认值0
- TS: 有效时间
Golang 实现
下面是 mojotv 的实现
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
|
package main
import (
"crypto/hmac"
"crypto/sha1"
"encoding/binary"
"fmt"
"time"
)
func main() {
key := []byte("MOJOTV_CN_IS_AWESOME_AND_AWESOME_SECRET_KEY")
number := totp(key, time.Now(), 6)
fmt.Println("2FA code: ",number)
}
func hotp(key []byte, counter uint64, digits int) int {
//RFC 6238
h := hmac.New(sha1.New, key)
binary.Write(h, binary.BigEndian, counter)
sum := h.Sum(nil)
//取sha1的最后4byte
//0x7FFFFFFF 是long int的最大值
//math.MaxUint32 == 2^32-1
//& 0x7FFFFFFF == 2^31 Set the first bit of truncatedHash to zero //remove the most significant bit
// len(sum)-1]&0x0F 最后 像登陆 (bytes.len-4)
//取sha1 bytes的最后4byte 转换成 uint32
v := binary.BigEndian.Uint32(sum[sum[len(sum)-1]&0x0F:]) & 0x7FFFFFFF
d := uint32(1)
//取十进制的余数
for i := 0; i < digits && i < 8; i++ {
d *= 10
}
return int(v % d)
}
func totp(key []byte, t time.Time, digits int) int {
return hotp(key, uint64(t.Unix())/30, digits)
//return hotp(key, uint64(t.UnixNano())/30e9, digits)
}
|
上面的 main
函数中,改写如下:
1
2
3
4
5
6
7
8
9
|
func main() {
key := []byte("MOJOTV_CN_IS_AWESOME_AND_AWESOME_SECRET_KEY")
number := totp(key, time.Now(), 6)
fmt.Println("2FA code: ",number)
time.Sleep(10*time.Second)
number := totp(key, time.Now(), 6)
fmt.Println("2FA code: ",number)
}
|
会出现两次密码不一致的情况,应该是是除去30的时间数值位于临界点,即类型:11/3
和 12/3
的值不一样。重试一次,结果正确。
个人实现,将 totp
的实现修改如下:
1
2
3
4
|
func totp(key []byte, t time.Time, digits int) int {
timeStr := uint64(t.Unix())
return hotp(key, timeStr & (math.MaxUint64-30), digits)
}
|
使用 &
操作,将数字 timeStr
对于30s的位置为0
其他实现:
参考:
延伸阅读
- 2fa explained: How to enable it and how it works
- two-factor authentication (2FA)