Google身份验证器、基于时间的一次性密码 (TOTP)算法

TOTP算法可以应用于很多网络应用,从VPN访问,WiFi登录到面向事务的Web应用。目前用的比较多的是双因子验证,如阿里的MFA,谷歌提供了开源虚拟MFA。通过账号名或密码登录,获取密钥,根据密钥和时间戳计算。即使用TOTP算法,只要满足两个条件:

(1)基于相同的密钥

 (2)   时钟同步

可以保证校验段和被校验端具有相同的输出。用于离线处理的情况,只需要事先约定好密钥。

1.介绍

  1.1 范围

  扩展OTP算法,即基于HMAC的一次性密码算法,来支持基于时间的动态因素。

   1.2 背景

       如RFC4266中所述,HOTP算法基于HMAC-SHA-1 算法,并应用于增长的计数器值来表示HMAC计算的信息。HMAC-SHA-1计算值被转换成用户友好的可被理解的值,可表述如下:

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

    Truncate表示可以将HMAC_SHA_1值转换成HOTP值的函数。

    K表示共享的密钥。

    C表示计数器值。

  TOTP对这个算法加入了基于时间的变量,值T,源于一个参考时间和一个时间步,替换了HOTP计算中的计数器C。

    TOTP实现可以使用HMAC-SHA-256或HMAC-SHA-512(基于SHA-256或SHA-512 哈希函数)函数来实现,而不是HOTP计算中的HMAC-SHA-1函数。

2.符号和技术

   RFC 2119

        MUST,REQUIRED,SHALL-一定要

        NUST NOT, SHALL NOT-一定不要

        SHOULD,RECOMMENDED-建议要(在某些场景下存在理由忽略指定项,但是必须理解整个实现并再选择另一种不同的方式前仔细权衡)。

        SHOULD NOT,NOT RECOMMENDED -建议不要

        MAY,OPTIONAL-可选的

3.算法要求

  这部分概括了设计TOTP算法需要考虑的必要条件。

R1:校验端和被校验端必须能获取到当前Unix时间(或者秒数,从1970年1月1日12:00 UTC开始)。设置的的时间步长会影响需要进行时钟同步的频率。

R2:验证必须共享相同的密钥或通过密钥转换来产生一个共享的密钥。

R3:算法必须使用HOTP作为主要构建块。

R4:必须使用相同的时间步值X。

R5:必须具有唯一的密钥。

R6:密钥应该是随机产生的,或者使用密钥产生算法产生的。

R7:密钥可以被存储在抗干扰的设备中,应该被授权后才能访问和使用。

4.TOTP算法

  4.1符号

        X:以秒位单位的时间步(默认值为30s),是系统参数。

        T0:开始计数时间步的时间(默认值是0,Unix初始时间)也是一个系统参数。

        4.2描述

        基本上,我们将TOTP定义为TOTP=HOTP(K,T)

        T:整数,表示基于初始化时间T0到当前的时间步数。(如经过1分钟即时间步数是2,T=2)

       更具体地,T=(当前时间-T0)/X,在计算中默认使用了向下取整。

       例如,T0=0,时间步长X=30,如果当前Unix时间是59s则T=1,如果当前时间是60s,则T=2。

       实现这个算法,当时间大于2038年时必须支持时间值T比32位最大整数大。

5.安全考虑

  5.1 性能

   算法的安全性和延展性取决于底层构建模块HOTP的特性,HOTP基于HMAC构建,使用SHA-1作为哈希函数。

   在RFC4226中陈述的安全行分析结果,实际上就是:动态Truncate的输出是均匀独立分布的字符串。分析证明对于HOTP函数的可能攻击就是暴力破解。

正如算法要求部分描述的,密钥应该是随机地或使用加密强伪随机产生器(适当随机数作为种子)产生。

       密钥应该和HMAC输出同长来保证互操作性。

  建议遵循RFC4086对所有伪随机和随机数产生器的建议。用于产生密钥的伪随机数应该成功通过[CN]中随机测试,或相似的有识别度的测试。

所有通信应该发生在安全信道,例如:SSL/TLS或IPsec连接。

也建议将密钥安全地存储在验证系统,更具体一点,使用抗干扰硬件加密加密他们,且只在需要时曝光它们。例如:密钥只有在需要验证OTP值的时候解密,并且立即重新加密来限制它们在内存中暴露一小段时间。

密钥必须存储在安全的区域,避免验证对系统和加密数据库的直接攻击。尤其是,应该限制程序和进程访问密钥材料,只需要验证系统可访问。

       5.2 验证和时间步长(Time-step size)

    用相同时间步长产生的OTP应该相同。

  这个算法允许一定的时间范围误差,误差范围与设置的时间步长相关,如果设置的是30s,那么时钟之间允许的时差就是30s。

  时间步长越长,被破解的风险就越高,但考虑到被校验方需要人工输入验证码,应该留下足够的操作时间。推荐使用30秒作为默认时间片段,在安全和易用性之间达到一个平衡。

 6.C#代码实现

  

using System;
using System.Security.Cryptography;
using BaseDll;

namespace TOTP
{
    class Program
    {
        static void Main(string[] args)
        {
            // Seed for HMAC-SHA1 - 20 bytes
            String seed = "3132333435363738393031323334353637383930";
            // Seed for HMAC-SHA256 - 32 bytes
            String seed32 = "3132333435363738393031323334353637383930" +
            "313233343536373839303132";
            // Seed for HMAC-SHA512 - 64 bytes
            String seed64 = "3132333435363738393031323334353637383930" +
            "3132333435363738393031323334353637383930" +
            "3132333435363738393031323334353637383930" +
            "31323334";
            long T0 = 0;
            long X = 30;
            long[] testTime=new long[6]{59, 1111111109, 1111111111,1234567890, 2000000000, 20000000000};

            String steps = "0";
         
            try
            {
               Console.WriteLine(
                        "+---------------+-----------------------+" +
                "------------------+--------+--------+");
                Console.WriteLine(
                        "|  Time(sec)    |   Time (UTC format)   " +
                "| Value of T(Hex)  |  TOTP  | Mode   |");
                Console.WriteLine(
                        "+---------------+-----------------------+" +
                "------------------+--------+--------+");

                for (int i = 0; i < testTime.Length; i++)
                {
                    long T = (testTime[i] - T0) / X;
                    steps = Convert.ToString(T,16).ToUpper();
                    while (steps.Length < 16) steps = "0" + steps;
                    long fmtTime = testTime[i];
                    DateTime dtStart = new DateTime(1970, 1, 1, 0, 0, 0, 0);
                    String utcTime=dtStart.AddSeconds(testTime[i]).ToString("yyyy-MM-dd HH:mm:ss");
                  
                   Console.WriteLine("|  " + fmtTime + "  |  " + utcTime +
                            "  | " + steps + " |");
                   Console.WriteLine(generateTOTP(seed, steps, "8",
                    "HmacSHA1") + "| SHA1   |");
                    Console.WriteLine("|  " + fmtTime + "  |  " + utcTime +
                            "  | " + steps + " |");
                   Console.WriteLine(generateTOTP(seed32, steps, "8",
                    "HmacSHA256") + "| SHA256 |");
                    Console.WriteLine("|  " + fmtTime + "  |  " + utcTime +
                            "  | " + steps + " |");
                   Console.WriteLine(generateTOTP(seed64, steps, "8",
                    "HmacSHA512") + "| SHA512 |");

                    Console.WriteLine(
                            "+---------------+-----------------------+" +
                    "------------------+--------+--------+");
                }
            }
            catch ( Exception ex){
                Console.WriteLine("Error : " + ex.ToString());
            }
        }
        private static byte[] hmac_sha(String crypto, byte[] keyBytes,
          byte[] text)
        {

            switch (crypto)
            {
                case "HmacSHA1":
                    HMACSHA1 hmacsha1 = new HMACSHA1();
                    hmacsha1.Key = keyBytes;
                    return hmacsha1.ComputeHash(text);
                case "HmacSHA256":
                    HMACSHA256 hmacsha256 = new HMACSHA256();
                    hmacsha256.Key = keyBytes;
                    return hmacsha256.ComputeHash(text);
                case "HmacSHA512":
                    HMACSHA512 hmacsha512 = new HMACSHA512();
                    hmacsha512.Key = keyBytes;
                    return hmacsha512.ComputeHash(text);
            }
            return null;
           
        }
        /**
      * This method converts a HEX string to Byte[]
      *
      * @param hex: the HEX string
      *
      * @return: a byte array
      */

        private static byte[] hexStr2Bytes(String hex)
        {
            hex = hex.Replace(" ", "");
            if ((hex.Length % 2) != 0)
                hex += " ";
            byte[] returnBytes = new byte[hex.Length / 2];
            for (int i = 0; i < returnBytes.Length; i++)
                returnBytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
            return returnBytes;
        }

        private static int[] DIGITS_POWER
        // 0    1   2    3    4      5        6       7         8
        = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };

     /**
      * This method generates a TOTP value for the given
      * set of parameters.
      *
      * @param key: the shared secret, HEX encoded
      * @param time: a value that reflects a time
      * @param returnDigits: number of digits to return
      *
      * @return: a numeric String in base 10 that includes
      *              {@link truncationDigits} digits
      */

     public static String generateTOTP(String key, String time, String returnDigits)
    {
        return generateTOTP(key, time, returnDigits, "HmacSHA1");
    }


        /**
         * This method generates a TOTP value for the given
         * set of parameters.
         *
         * @param key: the shared secret, HEX encoded
         * @param time: a value that reflects a time
         * @param returnDigits: number of digits to return
         *
         * @return: a numeric String in base 10 that includes
         *              {@link truncationDigits} digits
         */

        public static String generateTOTP256(String key,String time,String returnDigits)
        {
            return generateTOTP(key, time, returnDigits, "HmacSHA256");
        }
        /**
      * This method generates a TOTP value for the given
      * set of parameters.
      *
      * @param key: the shared secret, HEX encoded
      * @param time: a value that reflects a time
      * @param returnDigits: number of digits to return
      *
      * @return: a numeric String in base 10 that includes
      *              {@link truncationDigits} digits
      */

        public static String generateTOTP512(String key,
                String time,
                String returnDigits)
        {
            return generateTOTP(key, time, returnDigits, "HmacSHA512");
        }


        /**
         * This method generates a TOTP value for the given
         * set of parameters.
         *
         * @param key: the shared secret, HEX encoded
         * @param time: a value that reflects a time
         * @param returnDigits: number of digits to return
         * @param crypto: the crypto function to use
         *
         * @return: a numeric String in base 10 that includes
         *              {@link truncationDigits} digits
         */

        public static String generateTOTP(String key, String time, String returnDigits, String crypto)
        {
            int codeDigits = Convert.ToInt32(returnDigits);
            String result = null;

            // Using the counter
            // First 8 bytes are for the movingFactor
            // Compliant with base RFC 4226 (HOTP)
            while (time.Length< 16)
                time = "0" + time;

            // Get the HEX in a Byte[]
            byte[] msg = hexStr2Bytes(time);
            byte[] k = hexStr2Bytes(key);
            byte[] hash = hmac_sha(crypto, k, msg);

            // put selected bytes into result int
            int offset = hash[hash.Length - 1] & 0xf;

            int binary =
                ((hash[offset] & 0x7f) << 24) |
                ((hash[offset + 1] & 0xff) << 16) |
                ((hash[offset + 2] & 0xff) << 8) |
                (hash[offset + 3] & 0xff);

            int otp = binary % DIGITS_POWER[codeDigits];

            result = otp.ToString();
            while (result.Length < codeDigits)
            {
                result = "0" + result;
            }
            return result;
        }

    }
}

 

资料

1.RFC 6238 :TOTP: Time-Based One-Time Password Algorithm

2.RFC4226:HOTP: An HMAC-Based One-Time Password Algorithm

posted @ 2022-11-18 17:00  头号程序媛  阅读(2572)  评论(0编辑  收藏  举报