簡單解析 TOTP 算法#
什麼是 TOTP?#
TOTP,全稱:基於時間的一次性密碼(Time-based one-time password),該算法遵循RFC 6238標準,在遵循 RFC 4226 標準的 HOTP 算法的基礎上,附帶了時間作為消息的增量計數器。
TOTP 的應用在哪裡?#
最直觀的體現就是Microsoft Authenticator和Google 推出的 Authenticator應用,可以通過掃描二維碼或者輸入密鑰(Secret)來添加對應的帳號。
如何實現的?#
首先,假設我們已經擁有了一串可用於生成密鑰的 BASE32 字符串:4FCDTLHR446DPFCKUA46UFIAYTQIDSZ2
將這個字符串轉換為字節碼:
34 46 43 44 54
4c 48 52 34 34
36 44 50 46 43
4b 55 41 34 36
55 46 49 41 59
54 51 49 44 53
5a 32
然後對其進行 BASE32 解碼,就可以得到 20 個十六進制序列:
0xE1 0x44 0x39 0xAC 0xF1
0xE7 0x3C 0x37 0x94 0x4A
0xA0 0x39 0xEA 0x15 0x00
0xC4 0xE0 0x81 0xCB 0x3A
然後以今天中午 12 時作為時間因素:2023 年 06 月 26 日 12:00:00(時間戳:1687752000(秒))
有了以上 2 個因素,我們就可以開始計算了
第二步,我們將時間因素的時間戳除以 30 作為冗餘(30 秒窗口期):1687752000 / 30 = 56258400,然後再將其轉換為十六進制:0x035A6F60
注意:在此需要給補 0 到 8 個字節的長度:00 00 00 00 03 5A 6F 60
第三步,將上文的私鑰作為 HMAC-SHA-1 的私鑰,將得到的時間因素的結果作為消息進行加密,然後得到了:
ee 91 c2 ea 63
f2 e4 9c 65 5d
c5 2e c4 9f b0
dd 45 f6 09 09
第四步,將得到的哈希的最後一位並按位與上 0x0F 作為偏移量(OFFSET):0x09 & 0x0F = 0x09
將偏移量帶入如下算式中:
哈希結果為HASH
偏移量為OFFSET = 0x09
(HASH[OFFSET] & 0x7F) << 24
= (0x5D & 0x7F) << 24
= 0x5D << 24
= 0x5D000000
(HASH[OFFSET + 1] & 0xFF) << 16
= (0xC5 & 0xFF) << 16
= 0xC5 << 16
= 0xC50000
(HASH[OFFSET + 2] & 0xFF) << 8
= (0x2E & 0xFF) << 8
= 0x2E << 8
= 0x2E00
HASH[OFFSET + 3] & 0xFF
= 0xC4 & 0xFF
= 0xC4
將上述結果進行按位或運算,並取後6位(十進制)
(0x5D000000 | 0xC50000 | 0x2E00 | 0xC4) % 0xF4240 = 0x5DC52EC4 % 0xF4240
= 1573203652 % 1000000 = 203652
至此,203652 就是我們生成的 TOTP 驗證碼。
代碼(C 語言)#
注:需要引入 OpenSSL
#include <stdio.h>
#include "string.h"
#include "time.h"
#include "math.h"
#include "openssl/hmac.h"
void generate_totp_code(const uint8_t * secret, uint32_t counter, int digits) {
uint8_t counter_bytes[8];
for (int i = 1; i <= 8; ++i) {
counter_bytes[8 - i] = (u_int8_t) (counter % 256) & 0xff;
counter /= 256;
}
unsigned char hmac_result[20];
HMAC(EVP_sha1(), secret, 20, counter_bytes,
sizeof(counter_bytes), hmac_result, NULL);
int offset = hmac_result[19] & 0x0f;
int truncated_hash = (hmac_result[offset] & 0x7f) << 24 |
(hmac_result[offset + 1] & 0xff) << 16 |
(hmac_result[offset + 2] & 0xff) << 8 |
(hmac_result[offset + 3] & 0xff);
int otp = truncated_hash % 0xF4240;
printf("TOTP: %0*d\n", digits, otp);
}
int main() {
// 4FCDTLHR446DPFCKUA46UFIAYTQIDSZ2
// to bytes
// 34 46 43 44 54 4c 48 52 34 34 36 44 50 46 43 4b 55 41 34 36 55 46 49 41 59 54 51 49 44 53 5a 32
// base32 decode
// e1 44 39 ac f1 e7 3c 37 94 4a a0 39 ea 15 00 c4 e0 81 cb 3a
uint8_t secret[] = {0xe1, 0x44, 0x39, 0xac,
0xf1, 0xe7, 0x3c, 0x37,
0x94, 0x4a, 0xa0, 0x39,
0xea, 0x15, 0x00, 0xc4,
0xe0, 0x81, 0xcb, 0x3a};
uint32_t counter = time(NULL) / 30;
int digits = 6;
generate_totp_code(secret, counter, digits);
return 0;
}