JAVA-基于RFC6238的TOTP算法的简单实现

复制粘贴即可使用,如有需要,即可修改两个地方:

  1. main里的seed;
  2. poststring;

改以上两处,即可基于时间生成30秒一变的10位数码。

import java.io.UnsupportedEncodingException;
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.nio.ByteBuffer;
// import java.util.*;
import java.math.BigInteger;
// import java.util.TimeZone;
import java.util.Base64;


public class DTTEST {
    private DTTEST(){
        System.out.println("Start:");
    }

    /**
     * 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 = time
     */

    private static byte[] hmac_sha(String crypto, byte[] key, byte[] text){
        try {
            // byte[] keyBytes = Base64.getDecoder().decode(key);
            // byte[] keyBytes = Base64.getDecoder().decode(key);
            Mac hmac;
            hmac = Mac.getInstance(crypto);
            SecretKeySpec macKey =
                new SecretKeySpec(key, crypto);
            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 = 
    { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000L };
    // 0  1   2   3       4     5        6         7        8          9          10

     /**
      * 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 = ByteBuffer.allocate(8).putLong(time).array();
  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 String stringToBase64(String str) {

        try{
            String reusult = Base64.getEncoder().encodeToString(str.getBytes("utf-8"));
            return reusult;
        }catch (UnsupportedEncodingException e){
            String msg = "error";
            return msg;
        }
        
        
    }
    

    public static String stringToHexAscii(String value)  
    {  
        StringBuffer sbu = new StringBuffer();  
        char[] chars = value.toCharArray();   
        for (int i = 0; i < chars.length; i++) {  
                sbu.append(Integer.toHexString(Integer.valueOf(chars[i]).intValue()));  

        }  
        return sbu.toString();  
    }  

    public static String asciiToString(String value) {
        StringBuffer sbu = new StringBuffer();  
        char[] chars = value.toCharArray();   
        for (int i = 0; i < chars.length; i++) {  
                try{
                    sbu.append( (char) Integer.parseInt(String.valueOf(chars[i])));
                }  catch(NumberFormatException ex){

                }

        }  
        return sbu.toString();  
    }

    /*
        整体思路:
        总结:TOTP算法对userid和passwd加密,然后post到指定host;
            1.原始字符串转换成ASCII码的数字表示;
            2.ASCII码的数字转换为16进制;
            3.取原子时的时钟戳,去掉时区。在转换时,考虑int和long int转换的小数舍弃情况;
            4.设置30S动态时间口令;
            5.HmacSHA512加密;
            6.返回结果标准化处理(userid:passwd-timebasedResult);
            7.对标准化结果进行base64编码;
            8.Postman的headers增加内容——Authorization: Basic base64编码标准结果;
            9.Content-Type: application/json;
            10.Body->raw->content:
                {
                    "contact_email": "",
                    "github_url": ""
                }
    */
     public static void main(String[] args) {
        while(true){
        long T0 = 0;
        int X = 30;
        // String steps = "0";
        String seed = "[email protected]";
        String postString = "HENNGECHALLENGE003";
        // 通常编码,要将字符串转换编码,然后换成16进制编码;
        // 一般编程里面,没有特殊要求,编码都是十六进制的
        String asciiResult = stringToHexAscii(seed + postString);
        // String asciiResult = "3530363136393837344071712e636f6d48454e4e47454348414c4c454e4745303033";
        // String asciiResult = stringToBase64(seed + postString);
        String stringResult = asciiToString(asciiResult);

        DateFormat df = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
        // df.setTimeZone(TimeZone.getTimeZone("JST"));

        // 此处除以1000,是因为float和int转换会丢弃小数位
        // 多数编程语言都有一个缺点,两个整数相除。如果结果是小数,最后得出的结果就是一个整数。
        // 小数位会被丢弃,所以利用这一点,除以30之前,根本不需要做整除的处理。
        // 浮点的长度普遍比整型长。
        // 这个计算结果这样,其实是c语言导致的,有浮点数参与计算,结果才可以为浮点。
        // 数值计算你可以记一个经典例子。1/2=0,1/2.0=0.5,1.0/2=0.5
        int testTime = (int) (System.currentTimeMillis()/1000L);
        int reminderTime =X - (int) (System.currentTimeMillis() / 1000 % X);
        
        long T = (testTime - T0) / X ;
        
        String steps = Long.toHexString(T).toUpperCase();
        System.out.println("除以30后的时间:"+T);
        while (steps.length() < 16) steps = "0" + steps;


        System.out.println(df.format(new Date(System.currentTimeMillis())));
        System.out.println("unix time(正确):"+testTime);
        System.out.println("unix time2(原始时间):"+(System.currentTimeMillis()));
        System.out.println("unix time2(Int转型截取数据):"+(int) (System.currentTimeMillis()));
        System.out.println("Ascii Result:" + asciiResult);
        System.out.println("stringResult:" + stringResult);
        System.out.println("HmacSHA512:"+generateTOTP(asciiResult, steps, "10", "HmacSHA512"));

        String str = seed + ":" +generateTOTP(asciiResult, steps, "10", "HmacSHA512");
        System.out.println("str:" + str);
        String result = stringToBase64(str);

        System.out.println("result:" + "Basic " + result);
        while(reminderTime > 0){
            try {
                
                System.out.print(reminderTime+",");
                Thread.sleep(1000);
                reminderTime--;
            }catch (Exception ex){
                System.out.println("catch a error");
            }

        }
        
    }
}


}

猜你喜欢

转载自blog.csdn.net/u013362192/article/details/107128670