使用特定密钥生成 10 位数的 TOTP 密码
此问题与 RFC6238 中指定的 TOTP 有关:https://www.rfc-editor.org/rfc/rfc6238#section-1.2。
我将实现RFC6238以生成一个10位数的TOTP密码,该密码将在稍后的POST请求中使用。TOTP 的示例输入和输出应如下所示:
示例输入:
- 共享密钥:“ninja@example.comHDECHALLENGE003”(不带双引号)
- 使用的哈希函数:
HMAC-SHA-512
- T0 = 0,时间步长 = 30 秒(根据 RFC6238 中指定)
- 预期 TOTP 为 10 位数字
示例输出:
成功生成 TOTP: , 对于 星期一, 17 Mar 2014 15:20:51 GMT
1773133250
base64 编码的 POST 授权用户名/密码请求: bmluamFAZXhhbXBsZS5jb206MTc3MzEzMzI1MA==
(我已经将示例POST授权解码为“ninja@example.com:1773133250”,因此为什么我可以说示例TOTP输出1773133250)
在尝试根据 rfc6238 规范创建自己的脚本后,我无法获得与上述相同的示例输入输出。我尝试使用其他可用的在线TOTP模块(主要是在Python中),发现它们生成的输出与我创建的脚本相同。最后,我尝试了RFC6238示例中给出的Java代码,并得出了与我的脚本相同的结果,即:
尝试输入:
HMAC512 的十六进制编码种子: “6E696E6A61406578616D706C652E636F6D4844454348414C4C454E4745303033” + “6E696E6A61406578616D706C652E636F6D484454348414C4C454E4745303033”;
输入时间为1395069651L,表示样本输出中接收的时间
尝试的结果(来自自定义脚本、其他 Python 模块和 RFC6238 文档中给出的 Java 实现的相同输出):
生成的 TOTP: 0490867067
以下是我第一次尝试在Python中生成TOTP的代码:
# Mission/Task Description:
# * For the "password", provide an 10-digit time-based one time password conforming to RFC6238 TOTP.
#
# ** You have to read RFC6238 (and the errata too!) and get a correct one time password by yourself.
# ** TOTP's "Time Step X" is 30 seconds. "T0" is 0.
# ** Use HMAC-SHA-512 for the hash function, instead of the default HMAC-SHA-1.
# ** Token shared secret is the userid followed by ASCII string value "HDECHALLENGE003" (not including double quotations).
#
# *** For example, if the userid is "ninja@example.com", the token shared secret is "ninja@example.comHDECHALLENGE003".
# *** For example, if the userid is "ninjasamuraisumotorishogun@example.com", the token shared secret is "ninjasamuraisumotorishogun@example.comHDECHALLENGE003"
#
import hmac
import hashlib
import time
import sys
import struct
userid = "ninja@example.com"
secret_suffix = "HDECHALLENGE003"
shared_secret = userid+secret_suffix
timestep = 30
T0 = 0
def HOTP(K, C, digits=10):
"""HTOP:
K is the shared key
C is the counter value
digits control the response length
"""
K_bytes = K.encode()
C_bytes = struct.pack(">Q", C)
hmac_sha512 = hmac.new(key = K_bytes, msg=C_bytes, digestmod=hashlib.sha512).hexdigest()
return Truncate(hmac_sha512)[-digits:]
def Truncate(hmac_sha512):
"""truncate sha512 value"""
offset = int(hmac_sha512[-1], 16)
binary = int(hmac_sha512[(offset *2):((offset*2)+8)], 16) & 0x7FFFFFFF
return str(binary)
def TOTP(K, digits=10, timeref = 0, timestep = 30):
"""TOTP, time-based variant of HOTP
digits control the response length
the C in HOTP is replaced by ( (currentTime - timeref) / timestep )
"""
C = int ( 1395069651 - timeref ) // timestep
return HOTP(K, C, digits = digits)
passwd = TOTP("ninja@example.comHDECHALLENGE003ninja@example.comHDECHALLENGE003", 10, T0, timestep).zfill(10)
print passwd
这是Java中的第二个代码,它本质上是RFC6238中发现的Java实现的修改版本:
/**
Copyright (c) 2011 IETF Trust and the persons identified as
authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, is permitted pursuant to, and subject to the license
terms contained in, the Simplified BSD License set forth in Section
4.c of the IETF Trust's Legal Provisions Relating to IETF Documents
(http://trustee.ietf.org/license-info).
*/
import java.lang.reflect.UndeclaredThrowableException;
import java.security.GeneralSecurityException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.util.TimeZone;
import java.util.Calendar;
/**
* This is an example implementation of the OATH
* TOTP algorithm.
* Visit www.openauthentication.org for more information.
*
* @author Johan Rydell, PortWise, Inc.
*/
public class TOTP {
private TOTP() {}
/**
* This method uses the JCE to provide the crypto algorithm.
* HMAC computes a Hashed Message Authentication Code with the
* crypto hash algorithm as a parameter.
*
* @param crypto: the crypto algorithm (HmacSHA1, HmacSHA256,
* HmacSHA512)
* @param keyBytes: the bytes to use for the HMAC key
* @param text: the message or text to be authenticated
*/
private static byte[] hmac_sha(String crypto, byte[] keyBytes,
byte[] text){
try {
Mac hmac;
hmac = Mac.getInstance(crypto);
SecretKeySpec macKey =
new SecretKeySpec(keyBytes, "RAW");
hmac.init(macKey);
return hmac.doFinal(text);
} catch (GeneralSecurityException gse) {
throw new UndeclaredThrowableException(gse);
}
}
/**
* This method converts a HEX string to Byte[]
*
* @param hex: the HEX string
*
* @return: a byte array
*/
private static byte[] hexStr2Bytes(String hex){
// Adding one byte to get the right conversion
// Values starting with "0" can be converted
byte[] bArray = new BigInteger("10" + hex,16).toByteArray();
// Copy all the REAL bytes, not the "first"
byte[] ret = new byte[bArray.length - 1];
for (int i = 0; i < ret.length; i++)
ret[i] = bArray[i+1];
return ret;
}
private static final long[] DIGITS_POWER
// 0 1 2 3 4 5 6 7 8 9 10
= {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,10000000000L};
/**
* 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 = Integer.decode(returnDigits).intValue();
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);
long otp = binary % DIGITS_POWER[codeDigits];
result = Long.toString(otp);
while (result.length() < codeDigits) {
result = "0" + result;
}
return result;
}
public 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 = "6E696E6A61406578616D706C652E636F6D4844454348414C4C454E4745303033";
//NOTE: this is the 16-bit/hex encoded representation of "ninja@example.comHDECHALLENGE003"
String seednew = "6E696E6A61406578616D706C652E636F6D4844454348414C4C454E4745303033" +
"6E696E6A61406578616D706C652E636F6D4844454348414C4C454E4745303033";
long T0 = 0;
long X = 30;
long current = System.currentTimeMillis()/1000;
System.out.println(current);
long testTime[] = {59L, 1234567890L,1395069651L};
String steps = "0";
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
try {
System.out.println(
"+---------------+-----------------------+" +
"------------------+--------+--------+");
System.out.println(
"| Time(sec) | Time (UTC format) " +
"| Value of T(Hex) | TOTP | Mode |");
System.out.println(
"+---------------+-----------------------+" +
"------------------+--------+--------+");
for (int i=0; i<testTime.length; i++) {
long T = (testTime[i] - T0)/X;
steps = Long.toHexString(T).toUpperCase();
while (steps.length() < 16) steps = "0" + steps;
String fmtTime = String.format("%1$-11s", testTime[i]);
String utcTime = df.format(new Date(testTime[i]*1000));
System.out.print("| " + fmtTime + " | " + utcTime +
" | " + steps + " |");
System.out.println(generateTOTP(seed, steps, "8",
"HmacSHA1") + "| SHA1 |");
System.out.print("| " + fmtTime + " | " + utcTime +
" | " + steps + " |");
System.out.println(generateTOTP(seed32, steps, "8",
"HmacSHA256") + "| SHA256 |");
System.out.print("| " + fmtTime + " | " + utcTime +
" | " + steps + " |");
System.out.println(generateTOTP(seed64, steps, "10",
"HmacSHA256") + "| SHA256 |");
System.out.print("| " + fmtTime + " | " + utcTime +
" | " + steps + " |");
System.out.println(generateTOTP(seednew, steps, "10",
"HmacSHA512") + "| SHA512 |");
System.out.println(
"+---------------+-----------------------+" +
"------------------+--------+--------+");
}
}catch (final Exception e){
System.out.println("Error : " + e);
}
}
}
请注意,对于修改后的 RFC Java 代码,输出将是 testTime[] 数组中列出的几个日期/时间的输出,但是来自任务示例输入的目标 GMT 也包含在此处。在我的Ubuntu中进行测试显示的结果与我的Python脚本的结果相同。
我相信我已经遵循了任务的指示。我使用给定Java代码的实际RFC来发现它没有生成与任务中给定的输出相同的输出。我联系了任务的提供者,询问是否有错误,但他们说这是正确的。
也许我在这里遗漏了一些东西,例如任务提供程序实际加密共享密钥的方式?