此问题与RFC6238中此处指定的TOTP有关:https://tools.ietf.org/html/rfc6238#section-1.2
我要实现RFC6238,以生成 10位 TOTP密码,稍后将在POST请求中使用该密码。 TOTP的示例输入和输出应该是这样的:
样本输入:

  • 共享 key :“[email protected]”(不带双引号)
  • 使用的哈希函数:HMAC-SHA-512
  • T0 = 0,时间步长= 30秒(按照RFC6238中的规定)
  • 预期的TOTP为10位数字

  • 示例输出:



    尝试根据rfc6238规范制作自己的脚本后,无法获得与上述示例输入相同的输出。我尝试使用其他在线可用的在线TOTP模块(主要在Python中),发现它们生成的输出与我创建的脚本相同。最后,我尝试了RFC6238示例中给出的Java代码,并得出了与脚本相同的结果,即:
    尝试输入:


    尝试的结果(自定义脚本,其他Python模块和RFC6238文档中给出的Java实现的输出相同):

    这是我第一次尝试在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 "[email protected]", the token shared secret is "[email protected]".
        # *** For example, if the userid is "[email protected]", the token shared secret is "[email protected]"
        #
    
    import hmac
    import hashlib
    import time
    import sys
    import struct
    
    userid = "[email protected]"
    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("[email protected]@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 "[email protected]"
             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,发现它未生成与任务中给定的输出相同的输出。我联系了任务提供者,询问是否存在错误,但他们说这是正确的。
    也许我在这里缺少某些东西,例如任务提供者实际加密共享 key 的方式?

    最佳答案

    您确定TOTP 1773133250是正确的吗?由于您的 secret 只有32个字节,因此您是否确定返回1773133250的提供程序正在构建与您相同的64字节 secret ?

    在您的代码中,您将32字节的 secret 并串联在一起以获得64字节。

    我正在使用FusionAuth-2FA Java库,如果将32字节的密码连接在一起以获得64字节的密码,我将得到与您相同的结果。

    我已经阅读了RFC,但对于我来说,尚不清楚实现者是否需要将 secret 扩展到特定字节大小。

    可能是您的代码正确,而1773133250是一个令人不快的听证会。

    这是我的测试代码:

    @Test
    public void stackOverflow_42546493() {
      // Mon, 17 Mar 2014 15:20:51 GMT
      ZonedDateTime date = ZonedDateTime.of(2014, 3, 17, 15, 20, 51, 0, ZoneId.of("GMT"));
      long seconds = date.toEpochSecond();
      assert seconds == 1395069651L;
      long timeStep = seconds / 30;
    
      // Your shared key in a 32 byte string
      String rawSecret = "[email protected]";
      String rawSecret64 = rawSecret + rawSecret; // 64 bytes
    
      // Using 32 byte secret
      String code = TwoFactor.calculateVerificationCode(rawSecret, timeStep, Algorithm.HmacSHA512, 10);
      assert code.equals("1264436375");
    
      // Using 64 byte secret
      String code = TwoFactor.calculateVerificationCode(rawSecret64, timeStep, Algorithm.HmacSHA512, 10);
      assert code.equals("0490867067");
    }
    

    关于java - 生成具有特定 key 的10位TOTP密码,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/42546493/

    10-12 04:49