美文网首页程序员
动态密码TOTP的Java实现

动态密码TOTP的Java实现

作者: 自在雀 | 来源:发表于2018-04-10 16:52 被阅读0次

    一、HOTP

      HOTP 算法,全称是“An HMAC-Based One-Time Password Algorithm”,是一种基于事件计数的一次性密码生成算法,详细的算法介绍可以查看 RFC 4226。其实算法本身非常简单,算法本身可以用两条简短的表达式描述:

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

    PWD(K,C,digit) = HOTP(K,C) mod 10^Digit

    二、TOTP

      TOTP 算法,全称是 TOTP: Time-Based One-Time Password Algorithm,其基于 HOTP 算法实现,核心是将移动因子从 HOTP 中的事件计数改为时间差。完整的 TOTP 算法的说明可以查看 RFC 6238,其公式描述也非常简单:

    TOTP = HOTP(K, T) // T is an integer

    and represents the number of time steps between the initial counter

    time T0 and the current Unix time

    More specifically, T = (Current Unix time - T0) / X, where the

    default floor function is used in the computation.

    参考《https://segmentfault.com/a/1190000008394200
    这篇文章已经介绍清楚动态密码的原理,本文仅在理论的基础上使用Java实现。

    三、TOTP具体Java实现

      子服务端:启用不含verifyTOTP*()验证方法的TOTP,使用子服务端的账户和密码加密,向验证服务端发送动态口令。
      验证服务端:验证子服务端的口令,根据子服务端标识信息,使用其账户和密码加密得到口令,比对口令是否一致即可。
      前提:因为使用时间作为动态因子加密口令,子服务端的时间应和验证服务端的时间一致,比如使用同一个授时服务器授时。
      推荐使用柔性口令验证,使用柔性验证时请设置时间回溯参数,避免因口令在网络中传输消耗时间,或者服务端时间误差导致口令失效。

    /**
     * <p>ClassName: TOTP</p>
     * <p>Description: TOTP = HOTP(K, T) // T is an integer
     * and represents the number of time steps between the initial counter
     * time T0 and the current Unix time
     * <p>
     * More specifically, T = (Current Unix time - T0) / X, where the
     * default floor function is used in the computation.</p>
     *
     * @author wangqian
     * @date 2018-04-03 11:44
     */
    public class TOTP {
    
        public static void main(String[] args) {
            try {
    
                for (int j = 0; j < 10; j++) {
                    String totp = generateMyTOTP("account01", "12345");
                    System.out.println(String.format("加密后: %s", totp));
                    Thread.sleep(1000);
                }
    
            } catch (final Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 共享密钥
         */
        private static final String SECRET_KEY = "ga35sdia43dhqj6k3f0la";
    
        /**
         * 时间步长 单位:毫秒 作为口令变化的时间周期
         */
        private static final long STEP = 30000;
    
        /**
         * 转码位数 [1-8]
         */
        private static final int CODE_DIGITS = 8;
    
        /**
         * 初始化时间
         */
        private static final long INITIAL_TIME = 0;
    
         /**
         * 柔性时间回溯
         */
        private static final long FLEXIBILIT_TIME = 5000;
    
        /**
         * 数子量级
         */
        private static final int[] DIGITS_POWER = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
    
        private TOTP() {
        }
    
        /**
         * 生成一次性密码
         *
         * @param code 账户
         * @param pass 密码
         * @return String
         */
        public static String generateMyTOTP(String code, String pass) {
            if (EmptyUtil.isEmpty(code) || EmptyUtil.isEmpty(pass)) {
                throw new RuntimeException("账户密码不许为空");
            }
            long now = new Date().getTime();
            String time = Long.toHexString(timeFactor(now)).toUpperCase();
            return generateTOTP(code + pass + SECRET_KEY, time);
        }
    
        /**
         * 刚性口令验证
         *
         * @param code 账户
         * @param pass 密码
         * @param totp 待验证的口令
         * @return boolean
         */
        public static boolean verifyTOTPRigidity(String code, String pass, String totp) {
            return generateMyTOTP(code, pass).equals(totp);
        }
        
        /**
         * 柔性口令验证
         *
         * @param code 账户
         * @param pass 密码
         * @param totp 待验证的口令
         * @return boolean
         */
        public static boolean verifyTOTPFlexibility(String code, String pass, String totp) {
            long now = new Date().getTime();
            String time = Long.toHexString(timeFactor(now)).toUpperCase();
            String tempTotp = generateTOTP(code + pass + SECRET_KEY, time);
            if (tempTotp.equals(totp)) {
                return true;
            }
            String time2 = Long.toHexString(timeFactor(now - FLEXIBILIT_TIME)).toUpperCase();
            String tempTotp2 = generateTOTP(code + pass + SECRET_KEY, time2);
            return tempTotp2.equals(totp);
        }
    
        /**
         * 获取动态因子
         *
         * @param targetTime 指定时间
         * @return long
         */
        private static long timeFactor(long targetTime) {
            return (targetTime - INITIAL_TIME) / STEP;
        }
    
        /**
         * 哈希加密
         *
         * @param crypto   加密算法
         * @param keyBytes 密钥数组
         * @param text     加密内容
         * @return byte[]
         */
        private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) {
            try {
                Mac hmac;
                hmac = Mac.getInstance(crypto);
                SecretKeySpec macKey = new SecretKeySpec(keyBytes, "AES");
                hmac.init(macKey);
                return hmac.doFinal(text);
            } catch (GeneralSecurityException gse) {
                throw new UndeclaredThrowableException(gse);
            }
        }
    
        private static byte[] hexStr2Bytes(String hex) {
            byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
            byte[] ret = new byte[bArray.length - 1];
            System.arraycopy(bArray, 1, ret, 0, ret.length);
            return ret;
        }
    
        private static String generateTOTP(String key, String time) {
            return generateTOTP(key, time, "HmacSHA1");
        }
    
    
        private static String generateTOTP256(String key, String time) {
            return generateTOTP(key, time, "HmacSHA256");
        }
    
        private static String generateTOTP512(String key, String time) {
            return generateTOTP(key, time, "HmacSHA512");
        }
    
        private static String generateTOTP(String key, String time, String crypto) {
            StringBuilder timeBuilder = new StringBuilder(time);
            while (timeBuilder.length() < 16)
                timeBuilder.insert(0, "0");
            time = timeBuilder.toString();
    
            byte[] msg = hexStr2Bytes(time);
            byte[] k = key.getBytes();
            byte[] hash = hmac_sha(crypto, k, msg);
            return truncate(hash);
        }
    
        /**
         * 截断函数
         *
         * @param target 20字节的字符串
         * @return String
         */
        private static String truncate(byte[] target) {
            StringBuilder result;
            int offset = target[target.length - 1] & 0xf;
            int binary = ((target[offset] & 0x7f) << 24)
                    | ((target[offset + 1] & 0xff) << 16)
                    | ((target[offset + 2] & 0xff) << 8) | (target[offset + 3] & 0xff);
    
            int otp = binary % DIGITS_POWER[CODE_DIGITS];
            result = new StringBuilder(Integer.toString(otp));
            while (result.length() < CODE_DIGITS) {
                result.insert(0, "0");
            }
            return result.toString();
        }
    } 
    

    欢迎到我的GitHub交流

    相关文章

      网友评论

        本文标题:动态密码TOTP的Java实现

        本文链接:https://www.haomeiwen.com/subject/qirnhftx.html