• 双因子认证解决方案


    什么叫双因子认证?

    通俗的讲,一般的认证方式都是用户名/密码的方式,也就是只有密码这一个因子来作认证,双因子无非是增加一个因子,增强认证的安全性。

    常见解决方案

    • 短信方式
    • 邮件方式
    • 电话语音方式
    • TOTP解决方案

    前三种方案,其实都大同小异。Server端通过某种算法生成一段随机密码,通过短信、邮件或者电话的方式传递给用户,用户把随机密码作为登录的凭证传递给Server,Server验证通过之后,就完成了一次双因子认证。但是短信和电话语音对于运营公司是有一定的成本的,除此之外有些非互联网的应用可能并不通公网,这种情况下,TOTP不失为一种好的双因子认证的解决方案。

    什么是TOTP?

    是Time-based One-Time Password的简写,表示基于时间戳算法的一次性密码。

    如果大家玩过梦幻西游的话,那么对将军令应该不陌生,这个就是基于TOTP的一个产物。

    OTP

    介绍TOTP之前,先介绍下OTP

    One-Time Password的简写,表示一次性密码。

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

    其中,K代表密钥串;C是一个数字,表示随机数;HMAC-SHA-1表示用SHA-1做HMAC;

    Truncate是一个函数,用于截取加密后的串,并取加密后串的一些字段组成一个数字。

    对HMAC-SHA-1方式加密来说,Truncate实现如下:

    • HMAC-SHA-1加密后的长度得到一个20字节的密串;
    • 取这个20字节的密串的最后一个字节,取这字节的低4位,作为截取加密串的下标偏移量;
    • 按照下标偏移量开始,获取4个字节,以大端(把高位字节放在低位地址)的方式组成一个整数;
    • 截取这个整数的后6位或者8位转成字符串返回。
     public static String generateOTP(String K,
                                          String C,
                                          String returnDigits,
                                          String crypto){
            int codeDigits = Integer.decode(returnDigits).intValue();
            String result = null;
     
            // K是密码
            // C是产生的随机数
            // crypto是加密算法 HMAC-SHA-1
            byte[] hash = hmac_sha(crypto, K, C);
            // hash为20字节的字符串
     
            // put selected bytes into result int
            // 获取hash最后一个字节的低4位,作为选择结果的开始下标偏移
            int offset = hash[hash.length - 1] & 0xf;
     
            // 获取4个字节组成一个整数,其中第一个字节最高位为符号位,不获取,使用0x7f
            int binary =
                    ((hash[offset] & 0x7f) << 24) |
                    ((hash[offset + 1] & 0xff) << 16) |
                    ((hash[offset + 2] & 0xff) << 8) |
                    (hash[offset + 3] & 0xff);
            // 获取这个整数的后6位(可以根据需要取后8位)
            int otp = binary % 1000000;
            // 将数字转成字符串,不够6位前面补0
            result = Integer.toString(otp);
            while (result.length() < codeDigits) {
                result = "0" + result;
            }
            return result;
        }
    

    返回的结果就是看到一个数字的动态密码。

    HOTP

    知道了OTP的基本原理,HOTP只是将其中的参数C变成了随机数

    公式修改一下

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

    HOTP: Generates the OTP for the given count

    即:C作为一个参数,获取动态密码。

    一般规定HOTP的散列函数使用SHA2,即:基于SHA-256 or SHA-512 [SHA2] 的散列函数做事件同步验证;

    TOTP详解

    TOTP只是将其中的参数C变成了由时间戳产生的数字。

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

    不同点是TOTP中的C是时间戳计算得出。

    C = (T - T0) / X;

    T 表示当前Unix时间戳

    T0一般取值为 0.

    X 表示时间步数,也就是说多长时间产生一个动态密码,这个时间间隔就是时间步数X,系统默认是30秒;

    例如:

    T0 = 0;

    X = 30;

    T = 30 ~ 59, C = 1; 表示30 ~ 59 这30秒内的动态密码一致。

    T = 60 ~ 89, C = 2; 表示30 ~ 59 这30秒内的动态密码一致。

    不同厂家使用的时间步数不同;

    • 阿里巴巴的身份宝使用的时间步数是60秒;
    • 宁盾令牌使用的时间步数是60秒;
    • Google的 身份验证器的时间步数是30秒;
    • 腾讯的Token时间步数是60秒;

    应用

    客户端的实现有很多,上面已经列出来了。而服务端的实现库比较少,貌似也都是非官方的实现。这里推荐一个JAVA的实现库,这是一个私人的库,介意的朋友只能自己撸轮子了。

    这里基于上述的实现库,给出一段demo代码,仅供参考。

    package com.github.chenqimiao.util;
    
    import java.text.MessageFormat;
    
    import com.warrenstrange.googleauth.GoogleAuthenticator;
    import com.warrenstrange.googleauth.GoogleAuthenticatorConfig;
    import com.warrenstrange.googleauth.GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder;
    import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
    
    import lombok.SneakyThrows;
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * @Auther: chenqimiao
     * @Date: 2019/8/26 22:58
     * @Description: refer https://github.com/wstrange/GoogleAuth
     */
    @Slf4j
    public class GoogleAuthenticatorUtils {
        // 前缀
        private static final String DEFAULT_USER_PREFIX = "TOTP_USER:";
        // 用户名|密钥|发行者
        public static final String QRCODE_TEMPLATE = "otpauth://totp/" + DEFAULT_USER_PREFIX + "{0}?secret={1}&issuer={2}";
        // 默认的发行者
        public static final String DEFAULT_ISSUER = "DAS_TOTP";
    
        private static final GoogleAuthenticatorConfig DEFAULT_CONFIG;
    
        static {
            GoogleAuthenticatorConfigBuilder builder = new GoogleAuthenticatorConfigBuilder();
    
            // Do something here if you want to set config for GoogleAuthenticator
    
            DEFAULT_CONFIG = builder.build();
        }
    
    
        public static String createQrCodeContent(String username, String secret) {
            return createQrCodeContent(username, secret, DEFAULT_ISSUER);
        }
    
        public static String createQrCodeContent(String username, String secret, String issuer) {
            return MessageFormat.format(QRCODE_TEMPLATE, username, secret, issuer);
        }
    
        public static String createSecret() {
            return createSecret(DEFAULT_CONFIG);
        }
    
        public static String createSecret(GoogleAuthenticatorConfig config) {
            GoogleAuthenticator gAuth = new GoogleAuthenticator(config);
            final GoogleAuthenticatorKey key = gAuth.createCredentials();
            return key.getKey();
        }
    
        public static boolean verify(Integer totpPwd, String secret) {
    
            return verify(totpPwd, secret, DEFAULT_CONFIG);
        }
    
        public static boolean verify(Integer totpPwd, String secret, GoogleAuthenticatorConfig config) {
            GoogleAuthenticator gAuth = new GoogleAuthenticator(config);
            return gAuth.authorize(secret, totpPwd);
        }
    
        public static Integer getTotpPassword(String secret) {
            return getTotpPassword(secret, DEFAULT_CONFIG);
        }
    
        public static Integer getTotpPassword(String secret, GoogleAuthenticatorConfig config) {
            GoogleAuthenticator gAuth = new GoogleAuthenticator(config);
            return gAuth.getTotpPassword(secret);
        }
    
        @SneakyThrows
        public static void main(String args[]) {
            String secret = createSecret();
            String qrcodeContent = createQrCodeContent("chenqimiao", secret);
            System.out.println("qrcodeContent is " + qrcodeContent);
    
            Integer totpPwd = getTotpPassword(secret);
            System.out.println("Current totp password is " + totpPwd);
    
            boolean result = verify(totpPwd, secret);
            System.out.println("result is " + result);
    
        }
    

    qrcodeContent可以通过二维码工具生成二维码,使用Google Authenticator扫描该二维码之后,就相当于为用户绑定了一个认证器。

  • 相关阅读:
    android四大组件之contentprovider
    android自定义控件及自定义组合控件
    android四大组件之activity
    监听鼠标滚动事件,如滚动鼠标出现返回顶部按钮
    滚动鼠标出现某一元素
    checked 选择框选中
    原生JS一些操作
    闭合浮动的方法css
    合理提升WEB前端性能
    JS数组操作
  • 原文地址:https://www.cnblogs.com/think-in-java/p/11443014.html
Copyright © 2020-2023  润新知