英文:
How to Handle 26-Byte Secret for Time-based One Time Password?
问题
26字节的密钥是使用Base32编码的字符串,通常以小写字母和空格的形式出现。要处理这样的密钥,你可以使用以下步骤:
- 将密钥转换为Base32编码。
- 使用HMAC-SHA1算法对值进行签名。
- 从哈希中获取32位的块,从偏移量开始。
- 对截断的值进行处理,确保它是正整数。
- 将截断的值取模1000000,得到一个6位数的验证码。
你可以使用下面的代码片段来处理26字节的密钥:
import (
"encoding/base32"
"crypto/hmac"
"crypto/sha1"
"encoding/binary"
"fmt"
)
func getHOTPToken(secret string, interval int64) (string, error) {
// Converts secret to base32 Encoding
key, err := base32.StdEncoding.DecodeString(secret)
if err != nil {
return "", err
}
// Signing the value using HMAC-SHA1 Algorithm
hash := hmac.New(sha1.New, key)
err = binary.Write(hash, binary.BigEndian, uint64(interval))
if err != nil {
return "", err
}
h := hash.Sum(nil)
// Get 32 bit chunk from hash starting at the offset
offset := h[19] & 0x0f
truncated := binary.BigEndian.Uint32(h[offset : offset+4])
truncated &= 0x7fffffff
code := truncated % 1000000
return fmt.Sprintf("%06d", code), nil
}
func main() {
secret := "VEV2QJEAUN453SR4Q4H3AIS4CI"
interval := int64(1234567890) // Replace with your desired interval
token, err := getHOTPToken(secret, interval)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Token:", token)
}
你可以将密钥和时间间隔替换为你自己的值,然后调用getHOTPToken
函数来获取验证码。
英文:
Secret of Time-based One Time Password are usually 16-byte base32 encoded string. e.g. GitHub 2FA.
But for some scenario, it has 26 bytes long. e.g. Tutanota OTP. Often in lower case with whitespaces, like: vev2 qjea un45 3sr4 q4h3 ais4 ci
I tried with the TOTP algorithm implemented in dgryski/dgoogauth and tilaklodha/google-authenticator. Both can handle 16-byte secret well, but got error for 26-byte secret.
e.g. for 16-byte secret VEV2QJEAUN453SR4
:
Time: 2021-12-17 14:31:46
Got: 079119
for 26-byte secret VEV2QJEAUN453SR4Q4H3AIS4CI
:
Error: "illegal base32 data at input byte 24"
Here's the code snippet:
func getHOTPToken(secret string, interval int64) (string, error) {
// Converts secret to base32 Encoding
key, err := base32.StdEncoding.DecodeString(secret)
if err != nil {
return "", err
}
// Signing the value using HMAC-SHA1 Algorithm
hash := hmac.New(sha1.New, key)
err = binary.Write(hash, binary.BigEndian, uint64(interval))
if err != nil {
return "", err
}
h := hash.Sum(nil)
// Get 32 bit chunk from hash starting at the offset
offset := h[19] & 0x0f
truncated := binary.BigEndian.Uint32(h[offset : offset+4])
truncated &= 0x7fffffff
code := truncated % 1000000
return fmt.Sprintf("%06d", code), nil
}
Can you please tell me how to handle 26-byte secret?
答案1
得分: 2
每5位输入字节,base32编码为一个base32字符,go base32使用RFC 4648 Base 32字母表(A-Z,2-7)。解码字符串为字节时,每个base32字符输入将映射到一个5位索引,然后重新组合为字节。
在你的例子中,"VEV2QJEAUN453SR4Q4H3AIS4CI"之前的"VEV2QJEAUN453SR4"已经是有效的输入,它是一个16个字符的输入,5位 * 16是80位,因此可以解析为10个字节的输出。现在让我们看看剩下的"Q4H3AIS4CI",10个字符-> 5 * 10 = 50位,前面的40位可以解码为5个字节,但最后的2个字符"CI"导致2位余数。
你需要添加6个填充字符。
5的倍数的余数位% 8是:
其位数每八个字符可被整除
按位操作 按字节操作 填充字符
1个字符:5 % 8 = 5位 | 1 % 8 (字符) = 1 -> 7个字符
2个字符:10 % 8 = 2位 | 2 % 8 (字符) = 2 -> 6个字符(这种情况下是"CI")
3个字符:15 % 8 = 7位 | 3 % 8 (字符) = 3 -> 5个字符
4个字符:20 % 8 = 4位 | 4 % 8 (字符) = 4 -> 4个字符
5个字符:25 % 8 = 1位 | 5 % 8 (字符) = 5 -> 3个字符
6个字符:30 % 8 = 6位 | 6 % 8 (字符) = 6 -> 2个字符
7个字符:35 % 8 = 3位 | 7 % 8 (字符) = 7 -> 2个字符
8个字符:40 % 8 = 0位 | 8 % 8 (字符) = 8 -> 0个字符
我已经修改了你的代码,输入"Q4H3AIS4CI"并添加了6个填充字符是可以的。
func Base32Test() {
// 8个字符:5 * 8位 -> 解码为5个字节
key, err := base32.StdEncoding.DecodeString("Q4H3AIS4")
fmt.Println(key)
if err != nil {
fmt.Println("test 1, ", err)
} else {
fmt.Println("test 1 ok", key)
}
// 10个字符:5 * 10位 -> 解码为5个字节和余数(2位,但最后的10位无法解码)
key, err = base32.StdEncoding.DecodeString("Q4H3AIS4CI")
fmt.Println(key)
if err != nil {
fmt.Println("test 2, ", err)
} else {
fmt.Println("test 2 ok", key)
}
// 填充
key, err = base32.StdEncoding.DecodeString("Q4H3AIS4CI======")
fmt.Println(key)
if err != nil {
fmt.Println("test 3, ", err)
} else {
fmt.Println("test 3 ok", key)
}
}
英文:
A base32 encodes every 5 bits of input bytes into base32 character, go base32 use
The RFC 4648 Base 32 alphabet (A-Z, 2-7). When decode a string to bytes, each base32
character input will be mapped to a 5 bit index then recompose to bytes.
In your example "VEV2QJEAUN453SR4Q4H3AIS4CI", the previous "VEV2QJEAUN453SR4"
was already valid input, it is a 16 char input, and 5 bit * 16 is 80 bit so it can be resolved into 10 bytes output. Now let us just look at the rest "Q4H3AIS4CI",
10 char -> 5 * 10 = 50 bits, the previous 40 bits can be decode to 5 bytes, but the last 2 char "CI" leads 2 bit remainder
Q | 4 | H | 3 | A | I | S | 4 | C | I
1 0 0 0 0|1 1 1 0 0|0 0 1 1 1|1 1 0 1 1|0 0 0 0 0|0 1 0 0 0|1 0 0 1 0|1 1 1 0 0|0 0 0 1 0|0 1 0 0 0
1 0 0 0 0 1 1 1|0 0 0 0 1 1 1 1|1 0 1 1 0 0 0 0|0 0 1 0 0 0 1 0|0 1 0 1 1 1 0 0|0 0 0 1 0 0 1 0|0 0
135 | 15 | 176 | 34 | 92 | 18 |
C | I | = | = | = | = | = | = |
0 0 0 1 0|0 1 0 0 0|0 0 0 0 0|0 0 0 0 0|0 0 0 0 0|0 0 0 0 0|0 0 0 0 0|0 0 0 0 0|
0 0 0 1 0 0 1 0|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|
18 |
You need to add 6 paddings
the remainder bit of multiples of 5 % 8 is:
Its bits are divisible every eight chars
bitwise opinion byte opinion padding chars
1 char: 5 % 8 = 5 bit | 1 % 8 (char) = 1 -> 7 char
2 char: 10 % 8 = 2 bit | 2 % 8 (char) = 2 -> 6 char (this case "CI")
3 char: 15 % 8 = 7 bit | 3 % 8 (char) = 3 -> 5 char
4 char: 20 % 8 = 4 bit | 4 % 8 (char) = 4 -> 4 char
5 char: 25 % 8 = 1 bit | 5 % 8 (char) = 5 -> 3 char
6 char: 30 % 8 = 6 bit | 6 % 8 (char) = 6 -> 2 char
7 char: 35 % 8 = 3 bit | 7 % 8 (char) = 7 -> 2 char
8 char: 40 % 8 = 0 bit | 8 % 8 (char) = 8 -> 0 char
I have modified your code, the inputs "Q4H3AIS4CI" with 6 padding is ok
func Base32Test() {
// 8 char: 5 * 8 bits -> decodes to 5 bytes
key, err := base32.StdEncoding.DecodeString("Q4H3AIS4")
fmt.Println(key)
if err != nil {
fmt.Println("test 1, ", err)
} else {
fmt.Println("test 1 ok", key)
}
// 10 char: 5 * 10 bits -> decodes to 5 bytes and remaider (2 bits but the last 10 bits can not be decode)
key, err = base32.StdEncoding.DecodeString("Q4H3AIS4CI")
fmt.Println(key)
if err != nil {
fmt.Println("test 2, ", err)
} else {
fmt.Println("test 2 ok", key)
}
// padding
key, err = base32.StdEncoding.DecodeString("Q4H3AIS4CI======")
fmt.Println(key)
if err != nil {
fmt.Println("test 3, ", err)
} else {
fmt.Println("test 3 ok", key)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论