理解 HMAC-Based One-Time Password Algorithm(转)

Two-Factor Authentication and One-Time Password

双因子认证(Two-Factor Authentication)认证是指结合密码以及实物(令牌、SMS等)两种条件对用户进行认证的方法,提高了系统的安全性。一次性密码(One-Time Passcode)是最为常用的双因子认证方法之一,如:

  • HOTP: HMAC-Based One-Time Password
  • TOTP: Time-Based One-Time Password

本文主要介绍基于事件的一次性密码的生成算法 HOTP,它由 RFC 4226 定义。


HOTP Aligorithm

HOTP 的生成算法如下:

HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))

其中:

  • C: 8-byte 的移动因子,对于客户端,每次生成一次性密码,C 的值加 1;对于服务端,每次认证客服端产生的一次性密码,C 的值加 1,所以客服端和服务端必须同步该数值。
  • K: 客户端和服务端的共享密钥,不同的客户端的密钥各不相同。
  • HMAC-SHA-1: Hash-based Message Authentication Code, 采用了 SHA1 哈希算法,输出值为 20 Bytes 的消息摘要。
  • Truncate: 将消息摘要缩减为一次性验证码,通常为 6 位数字。

该算法可分为 3 个步骤

  • Step 1: 生成 20 Byte 的消息摘要 HS,其中 HS = HMAC-SHA-1(K,C)。
  • Step 2: 根据 HS 生成 4 Byte 的字符串 Sbits,其中 Sbits = DT(HS),DT 的步骤请见后面。
  • Step 3: 把 Sbits 转换为数字 Snum,再让 D = Snum mod 10^6,D 即为一次性密码。

HMAC-SHA-1

HMAC 是密钥相关的哈希运算消息认证码,由 RFC 2104 定义,hmac 是一个实现该算法的 Python 库,它支持 md5, sha1 等哈希算法,HTOP 采用了 sha1 算法。

import hashlib
import hmac

def get_hmac_sha1(secret, msg):
    result = hmac.new(secret, msg, hashlib.sha1).hexdigest()
    return result

例如,以 C = 0、K=’12345678901234567890’ 为例,其 hmac_sha1 的摘要为:

>>> K = '12345678901234567890'
>>> C = bytearray([0, 0, 0, 0, 0, 0, 0, 1])
>>> print get_hmac_sha1(K, C)
cc93cf18508d94934c64b65d8ba7667fb7cde4b0

以下为 C 从 0 到 9 时的摘要值:

Secret = '12345678901234567890'

+-------+------------------------------------------+
| Count | Hexadecimal HMAC-SHA-1(secret, count)    |
+-------+------------------------------------------+
| 0     | cc93cf18508d94934c64b65d8ba7667fb7cde4b0 |
| 1     | 75a48a19d4cbe100644e8ac1397eea747a2d33ab |
| 2     | 0bacb7fa082fef30782211938bc1c5e70416ff44 |
| 3     | 66c28227d03a2d5529262ff016a1e6ef76557ece |
| 4     | a904c900a64b35909874b33e61c5938a8e15ed1c |
| 5     | a37e783d7b7233c083d4f62926c7a25f238d0316 |
| 6     | bc9cd28561042c83f219324d3c607256c03272ae |
| 7     | a4fb960c0bc06e1eabb804e5b397cdc4b45596fa |
| 8     | 1b3c89f65e6c9e883012052823443f048b4332db |
| 9     | 1637409809a679dc698207310c8c7fc07290d9e5 |
+-------+------------------------------------------+

Truncation

20 Bytes 的消息摘要过于冗长复杂,不利于用户的输入,所以需要将其浓缩为长度合适的验证码。Truncation 就是将 20 Bytes 的消息摘要生成 6 位数字的过程,即上述的 Step 2 和 Step 3。具体步骤如下:

假定用 String 表示消息摘要,String[0] 表示第一个 Byte,依次类推,String[19] 表示第 20 个 Byte。

  • 选取 String[19] 的低位 4 bit,用 Offset 表示,其中 0 <= Offset <= 15。
  • 令 P = String[Offset]…String[Offset + 3]
  • 令 Sbits = P & 0x7fffffff
  • 把 Sbits 转换为十进制数字 Snum,再让 D = Snum mod 10^6,D 即为一次性密码。

以值为 0x1f8698690e02ca16618550ef7f19da8e945b555a 的消息摘要为例:

+-----------------------------------------------------------+
| Byte Number                                               |
+-----------------------------------------------------------+
|00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|
+-----------------------------------------------------------+
| Byte Value                                                |
+-----------------------------------------------------------+
|1f|86|98|69|0e|02|ca|16|61|85|50|ef|7f|19|da|8e|94|5b|55|5a|
+------------------------------***********------------------+

  • String[19] 为 0x5a,低位的 4 bit 为 0xa,所以 Offset = 0xa = 10。
  • P = String[10]…String[10 + 3] = 0x50ef7f19。
  • Sbits = P & 0x7fffffff = 0x50ef7f19
  • Snum = 0x50ef7f19 = 1357872921
  • 最后的验证码为 D = Snum mod 10^6 = 872921

注意到 Sbits = P & 0x7fffffff,之所以不取 P 的最高位,是为了确保 Sbits 为无符号数,避免在不同硬件平台因为符号位而产生的差异。

以下为 C 从 0 到 9 生成的的一次性密码:

Secret = '12345678901234567890'
+-------+-------------+------------+--------+
| Count | Hexadecimal | Decimal    | HOTP   |
+-------+-------------+------------+--------+
| 0     | 4c93cf18    | 1284755224 | 755224 |
| 1     | 41397eea    | 1094287082 | 287082 |
| 2     |  82fef30    |  137359152 | 359152 |
| 3     | 66ef7655    | 1726969429 | 969429 |
| 4     | 61c5938a    | 1640338314 | 338314 |
| 5     | 33c083d4    |  868254676 | 254676 |
| 6     | 7256c032    | 1918287922 | 287922 |
| 7     |  4e5b397    |   82162583 | 162583 |
| 8     | 2823443f    |  673399871 | 399871 |
| 9     | 2679dc69    |  645520489 | 520489 |
+-------+-------------+------------+--------+

附:Python Demo

import hashlib
import hmac

def get_hotp_passcode(secret, count):
    data = bytearray([int(hex(count >> i & 0xff), 16) for i in (56, 48, 40, 32 ,24, 16, 8, 0)])
    hs = hmac.new(secret, data, hashlib.sha1).hexdigest()
    offset = int(hs[-1], 16)
    sbits = int(hs[offset * 2: offset * 2 + 8], 16) & 0x7fffffff
    return sbits % 1000000

Memo

本文转载至 http://wsfdl.com/algorithm/2016/04/05/%E7%90%86%E8%A7%A3HOTP.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容