动态密码算法(待续)

1. wiki

动态密码,也叫一次性密码 (OTP, One-Time Password),本身最大优点是防止重放攻击(replay attack),常见的动态密码有 HOTPTOPT 两种。

1. HOTP

HOTP(HMAC-based One-Time Password):表示基于基于事件计数的一次性密码生成算法,通过某一特定的事件次序及相同的种子值作为输入,通过 HASH 算法运算出一致的密码。详见 RFC 4226

算法本身可以用两条简短的表达式描述:

  • HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
  • PWD(K,C,digit) = HOTP(K,C) mod 10^Digit

其中:

  • K 表示共享密钥,一般与设备绑定,使相同设备才能生成有效的 HOTP
  • C 表示事件计数的值,在每次验证成功后加一,因此在一次性密码被使用后 C 就会变化,从而下一个 HOTP 的值也会发生改变。使用 8 字节的整数,称为移动因子(moving factor)。
  • HMAC-SHA-1 表示对共享密钥和移动因子进行 HMAC 的 SHA1 算法加密,得到 160 位长度(20字节)的加密结果
  • Truncate 表示截断函数,取加密后串的哪些字段组成一个数字
  • Digit 表示产生的验证码的长度,常见的是 6 位长度的动态密码

1. 知识点补充

1. MAC

MAC(Message Authentication Code) ,消息认证码,它兼容了 MD 和 SHA 的特性,并且在它们的基础上加入了密钥,因此也叫含密钥散列函数算法,即 HMAC(keyed-Hash Message Authentication Code) ,是通信实体双方使用的一种验证机制。

使用时,双方约定好散列函数并共享密钥,发送方使用该散列函数计算数据的摘要值,并用密钥加密,和数据一起发送。接收方收到报文后,会用密钥解密摘要值,同时用同一散列函数在本地计算所收到数据的摘要值,并比对两个摘要是否相同,从而确认报文是否被篡改。

2. 实战

计算步骤为:

  1. SHA-1 算法加密得到的 20 字节长度的 key;
  2. 取最后一个字节的低字节位的 4 位,作为偏移量;
  3. 以上述偏移量连续截取 4 个字节(32 位),最后返回 32 位中的后面 31 位;
  4. 根据验证码长度 digit ,并对 10digit 取模,不足的补零。

其中,利用 JDK8 自带的 MAC 算法以及事件计数,来生成新的密文:

1
2
3
4
5
// 获取实例
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(key); // 使用给定的密钥计算 MAC 值
mac.update(array, 0, 8); // 添加数据到指定的偏移量和长度
mac.doFinal(array, 0); // 完成计算,并使用偏移量输出到指定数组

使用 ByteBuffer 来操作字节数组:

1
2
3
4
5
6
// 按照 Mac 长度(一般20字节)分配空间
ByteBuffer buffer = ByteBuffer.allocate(mac.getMacLength());
// 获取最后一个字节,并取 4 位置的值,作为偏移量
final int offset = buffer.get(buffer.capacity() - 1) & 0x0f;
// 从偏移量,向前获取32位,取它的前31位,取模(密钥长度是6位)
(buffer.getInt(offset) & 0x7fffffff) % 1_000_000;

详见 一次性密码算法

2. TOTP

TOTP(Time-based One-Time Password):表示基于基于时间戳的一次性密码生成算法,其也是基于 HOTP 算法实现,但将移动因子从事件计数改为时间差,基于客户端的动态口令和动态口令验证服务器的时间比对,两者的时钟一致时,计算的动态口令才能一致。详见 RFC 6238

1. 实战

可以直接调用 HOTP 的实现,将 count 值替换成相应时间变量。