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; } } }
资料