【IoT】加密与安全:双因素认证(2FA):TOTP

认证(authentication)就是确认用户的身份,是网站登录必不可少的步骤。密码是最常见的认证方法,但是不安全,容易泄露和冒充。

基于安全认证,很多场景要求启用双因素认证(Two-factor authentication,简称 2FA)。

【IoT】加密与安全:双因素认证(2FA):TOTP_第1张图片

1、什么是双因素认证

一般有三种不同类型的证据可以证明一个人的身份。

1) 秘密信息

只有该用户知道、其他人不知道的某种信息,比如密码。

2) 个人物品

该用户的私人物品,比如身份证、钥匙。

3) 生理特征

该用户的遗传特征,比如指纹、相貌、虹膜等等。

这些证据就称为三种"因素"(factor)。因素越多,证明力就越强,身份就越可靠。

双因素认证是指,通过认证同时需要两个因素的证据。

银行卡就是最常见的双因素认证。用户必须同时提供银行卡和密码,才能取到现金。

2、双因素认证方案

常用的双因素组合是密码 + 某种个人物品,比如网上银行的 U 盾,用户插上 U 盾,再输入密码,才能登录网上银行。

但是,携带 U 盾不太方便,手机才是最好的替代品。

密码 + 手机就成了最佳的双因素认证方案。

【IoT】加密与安全:双因素认证(2FA):TOTP_第2张图片

国内的很多网站要求,用户输入密码时,还要提供短消息发送的验证码,以证明用户确实拥有该手机。

【IoT】加密与安全:双因素认证(2FA):TOTP_第3张图片

但是,短消息是不安全的,容易被拦截和伪造,SIM 卡也可以克隆。已经有案例,先伪造身份证,再申请一模一样的手机号码,把钱转走。
因此,安全的双因素认证不是密码 + 短消息,而是下面要介绍的 TOTP。

3、TOTP 的概念

TOTP 的全称是"基于时间的一次性密码"(Time-based One-time Password)。

它是公认的可靠解决方案,已经写入国际标准 RFC6238。

步骤如下:

第一步,用户开启双因素认证后,服务器生成一个密钥。

第二步:服务器提示用户扫描二维码(或者使用其他方式),把密钥保存到用户的手机。也就是说,服务器和用户的手机,现在都有了同一把密钥。

【IoT】加密与安全:双因素认证(2FA):TOTP_第4张图片

注意,密钥必须跟手机绑定。一旦用户更换手机,就必须生成全新的密钥。

第三步,用户登录时,手机客户端使用这个密钥和当前时间戳,生成一个哈希,有效期默认为30秒。用户在有效期内,把这个哈希提交给服务器。

【IoT】加密与安全:双因素认证(2FA):TOTP_第5张图片

第四步,服务器也使用密钥和当前时间戳,生成一个哈希,跟用户提交的哈希比对。只要两者不一致,就拒绝登录。

4、TOTP 的算法

手机客户端和服务器,如何保证 30 秒期间都得到同一个哈希呢?

TC = floor((unixtime(now) − unixtime(T0)) / TS)

上面的公式中,TC 表示一个时间计数器,unixtime(now)是当前 Unix 时间戳,unixtime(T0) 是约定的起始时间点的时间戳,默认是 0,也就是1970年1月1日。

TS 则是哈希有效期的时间长度,默认是30秒。因此,上面的公式就变成下面的形式。

TC = floor(unixtime(now) / 30)

所以,只要在 30 秒以内,TC 的值都是一样的。

前提是服务器和手机的时间必须同步。

接下来,就可以算出哈希了。

TOTP = HASH(SecretKey, TC)

上面代码中,HASH就是约定的哈希函数,默认是 SHA-1。

TOTP 有硬件生成器和软件生成器之分,都是采用上面的算法。

【IoT】加密与安全:双因素认证(2FA):TOTP_第6张图片

(说明:TOTP 硬件生成器)

【IoT】加密与安全:双因素认证(2FA):TOTP_第7张图片

(说明:Google Authenticator 是一个生成 TOTP 的手机 App)

5、TOTP 的实现

TOTP 很容易写,各个语言都有实现。

以 JavaScript 实现 2fa 来演示一下真实代码。

首先,安装这个模块。

$ npm install --save 2fa

然后,生成一个32位字符的密钥。

var tfa = require('2fa');

tfa.generateKey(32, function(err, key) {
  console.log(key);
});
// b5jjo0cz87d66mhwa9azplhxiao18zlx

现在就可以生成哈希了。

var tc = Math.floor(Date.now() / 1000 / 30);
var totp = tfa.generateCode(key, tc);
console.log(totp); // 683464

Java 实现示例:

package totp;

import java.security.MessageDigest;
import java.util.Date;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TOTP {

    // TC = floor((unixtime(now) − unixtime(T0)) / TS)
    // TC = floor(unixtime(now) / 30)
    // TOTP = HASH(SecretKey, TC)
    private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();

    public static void main(String[] args) {

        Pattern pattern = Pattern.compile("\\d");
        String key = UUID.randomUUID().toString().replace("-", "");

        for (int i = 0; i < 70; i++) {

            String TC = String.valueOf((int) Math.floor(new Date().getTime() / 1000 / 30));
            String TOTP = sha1(TC + key);

            Matcher matcher = pattern.matcher(TOTP);
            String result = "";
            while (matcher.find()) {
                result += matcher.group();
            }
            result = result.substring(result.length() - 6);
            System.out.println(i + "  --  " + result);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public static String sha1(String srcStr) {
        return hash("SHA-1", srcStr);
    }

    public static String hash(String algorithm, String srcStr) {
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            byte[] bytes = md.digest(srcStr.getBytes("utf-8"));
            return toHex(bytes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String toHex(byte[] bytes) {
        StringBuilder ret = new StringBuilder(bytes.length * 2);
        for (int i = 0; i < bytes.length; i++) {
            ret.append(HEX_DIGITS[(bytes[i] >> 4) & 0x0f]);
            ret.append(HEX_DIGITS[bytes[i] & 0x0f]);
        }
        return ret.toString();
    }
}

6、总结

双因素认证的优点在于,比单纯的密码登录安全得多。

就算密码泄露,只要手机还在,账户就是安全的。

各种密码破解方法,都对双因素认证无效。

缺点在于,登录多了一步,费时且麻烦,用户会感到不耐烦。而且,它也不意味着账户的绝对安全,入侵者依然可以通过盗取 cookie 或 token,劫持整个对话(session)。

双因素认证还有一个最大的问题,那就是帐户的恢复。

一旦忘记密码或者遗失手机,想要恢复登录,势必就要绕过双因素认证,这就形成了一个安全漏洞。除非准备两套双因素认证,一套用来登录,另一套用来恢复账户。

 


refer:

http://www.ruanyifeng.com/blog/2017/11/2fa-tutorial.html


 

你可能感兴趣的:(IoT产品之加密技术,IoT产品安全与加密)