Introduction

RFC4226记述的是HOTP,一个基于HMAC的动态口令的算法。由于本系统支持HOTP,在这里将介绍一下RFC4226的概要或者直接翻译一些章节。对于一些细节请参照RFC 4226原文

Details

4.算法的必要条件

RFC4226的第四节,介绍了HOTP的算法设计要满足的主要的必要条件。重点强调了终端用户的使用
方便性,以及在可能提供最低限的用户界面的低端硬件上的实现能力。特别是具备嵌入到大量的SIM,
Java卡的能力是一个重要的前提。
R1 - 这个算法必须是连续性的-或者基于事件:一个目的是把HOTP算法嵌入到大量的Java智能卡,
USB 保护卡(dongles)和GSM SIM卡。
R2 - 依靠最小化对电池,按钮数,计算能力和LCD界面尺寸的需求,这个算法应该能很经济的在
硬件上实现。
R3 - 这个算法必须能利用到不支持任何数据输入的令牌(token),但也可能用在像Secure PIN-pads
这样更加复杂的硬件上。
R4 - 显示在令牌上的值必须便于用户读取和输入:这要求HOTP的值具有合理的长度。
HOTP的值必须至少6位。为了方便在像手机这样的硬件上的输入,一个值得考虑的办法就是把HOTP
的值设成只是数字。
R5 - 必须具有用户友好的再同期计数器的机制。7.4节和附录E.4将详细介绍本资料建议的计数器
再同期机制。
R6 - 这个算法必须使用牢固的共享密码。共享密码必须至少128位。本资料推荐160的共享密码。

5.2 算法的概述

HOTP算法是基于客户端和服务器端都知道下面两个值: C:一个递增的值,就好比一个计数器一样 K:一个共享的秘匙 每次进行密码验证的时候,双方都利用下面的算法来生成一个动态的值 HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))

在这里HMAC-SHA-1指的是在RFC2104里定义的HASH算法。Truncate代表一个削减函数,以便把一个20位的HMAC-SHA-1算法的结果变成一个便于用户输入的值。(比如6位,8位)

5.3 生成HOTP值

第1步:生成一个HMAC-SHA-1值,让HS = HMAC-SHA-1(K,C) //HS为20位的String
  
第2步:生成一个4位的String(Dynamic Truncation,动态削减)
  让 Sbits = DT(HS)  //DT运算的定义参照下面说明,返回31-bit string

第3步:计算HOTP值 让 Snum = StToNum(Sbits) // 把S转化成一个0...2^{31}-1
  的数 Return D = Snum mod 10Digit // D是一个位于0...10{Digit}-1的数

削减函数Truncate执行第2步和第3步处理。

DT运算的定义

DT(String)  //String = String[0]...String[19],HMAC-SHA-1运算的结果
让 OffsetBits等于String[19]的低位4字节
Offset = StToNum(OffsetBits) // 0 <= OffSet <= 15
让 P = String[OffSet]...String[OffSet+3]

返回P的最后31字节 在这里不使用P的最高位字节的目的是为了避免singed,unsigned代来的计算问题。 不同的处理器对singed,unsigned的计算方式不一样,不使用最高位字节可以避免 这样的问题。

5.4. 计算6位HOTP的例子(Digit = 6)

下面的例子介绍怎样从HMAC-SHA-1运算结果抽取动态的二进制代码。

//String[19]的低位4字节
int offset   =  hmac_result[19] & 0xf ;
//不使用最高位,利用 &0x7f
int bin_code = (hmac_result[offset]  & 0x7f) << 24
  | (hmac_result[offset+1] & 0xff) << 16
  | (hmac_result[offset+2] & 0xff) <<  8
  | (hmac_result[offset+3] & 0xff) ;

7.2. HOTP值的验证

HOTP客户端(硬软件令牌)递增计数器的值,然后计算下一个HOTP值。如果认证服务器得到的值和客户端计算出来的值一样,那么HOTP值就被验证了。在这种情况下,服务器把计数器的值加1。

如果认证服务器得到的值和客户端计算出来的值不一样,服务器在开始寻求另一个途径之前启动再 同步的手续。(look-ahead window,即在接受一定范围内的超前的HOTP值,比如将计数器的值加1, 加2,加3后的值)

如果再同步失败,服务器然后要求协议的另一个认证途径发生,直到达到最大的允许探试次数。如果达到了最大的允许探试次数,服务器应该锁定账户并启动一个通知用户的手续。

7.4. 计数器的再同步

虽然在一次成功的HOTP认证后才增加服务器计数器的值,在令牌上的计数器的值是只要用户请求一个新的HOTP就会被增加。因此,服务器上的计数器和令牌上的计数器的值就可能不同步。

推荐在服务器上设置一个超前(look-ahead)变量s,让它来定义look-ahead window的大小。简单的说,就是服务器能计算接下来的s个服务器端HOTP值,然后用它们来检测接受接受到的HOTP客户端。

在这种情况下,计数器的同步就仅仅是让服务器计算接下来的s个HOTP值并决定是否有一个一致的。 作为一个选择,系统可以要求用户送连续的几个HOTP值(比如2个, 3个)以用来再同步,因为猜测连 续的几个HOTP值比猜测单独的一个HOTP值要困难的多。

变量s设置的上限确保服务器不会一直在检测HOTP值(导致denial-of-service攻击)并且限制攻击者 可能的猜测范围。在确保不显著影响可用性的同时,s应当尽可能的小。

7.5. 共有秘匙的管理

为了减少任何机密信息泄露的风险,用于生成和验证OTP值的共有秘匙的操作必须被安全地进行。 在验证系统里,我们能考虑两种不同的用于生成和保存(安全地)共有秘匙的途径。

  • 定性生成:秘匙派生自一个主属种子(master seed)以用于配置和验证,并能在被要求的时候随机产生
  • 随机生成:秘匙在配置时随机生成,秘匙必须马上被保存并管理在整个生命周期

定性生成:

一个可行的策略就是从主位秘密(master secret)推导出共有秘匙。这个主位秘密将只被保存 在服务器。必须用一个具有耐侵犯性(tamper-resistant)设备来保存和从主位秘密及其它公开 信息来推导出共有秘匙。主要的优点将是避免在任何时候暴露共有秘匙,同时也避免对储存的 特殊需求,因为当在配置和验证处理里需要的时候,共有秘匙能随时生成。

我们区别两种不同的情形: - 单独用一个主位键来推导共有秘匙;每个HOTP设备有一个不同的秘匙,K_i = SHA-1 (MK,i) 这里i代表一个用来识别HOTP设备的公开信息,比如一个序列号,一个令牌ID等。很明显,这 属于程序或服务的范畴--不同的程序或服务的提供者将有不同的秘密和设置。 - 用几个主位键并且每个HOTP设备保存一组不同的推导出来的秘匙,{K_i,j = SHA-1(MK_i,j)} 这里j代表一个用来识别HOTP设备的公开信息。这个主意将是只在验证服务器上保存活跃的 主位键,保存在Hardware Security Module (HSM),并放置在一个安全的地方,同时利用秘密 共享方法,比如[Shamir]。这样,如果一个主位秘密被泄露了,就可以不用更换所有的设备而 切换到另一个秘匙。

定性生成的缺点就是,主位秘密的暴露将可以让一个攻击者在一个确切的共有信息的基础上重建任何共有秘匙。这将需要让所有的秘匙失效,或者在有多个主位键的情况下切换到一组新的秘匙。

另一方面,用于保存主位秘密和生成共有秘匙的设备必须是具有耐侵犯性。而且HSM将不会暴露到 验证系统的安全区域的外面,以便减少泄露的风险。

随机生成:

随机生成共有秘匙。我们推荐按照在[RFC4086]里的建议并选择一个好的,安全的随机数源以 用来生成这些秘匙。一个(真正的)随机生成器需要一个自然发生的随机数源。从实际来看,有 两个可以考虑的用来生成共有秘匙的办法:

  • 基于硬件的生成器: 利用产生于物理现象的随机性。一个好的实装能是基于共振器。
  • 基于软件的生成器: 设计一个好的软件随机生成器不是一件容易的事。一个简单的,但 有效的实装应该基于多种数据源并应用像SHA-1这样的单方向函数。