车载码测试
diff --git a/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/PbocAlgorithem.java b/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/PbocAlgorithem.java
index 49da114..3c26f8b 100644
--- a/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/PbocAlgorithem.java
+++ b/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/PbocAlgorithem.java
@@ -2,8 +2,8 @@
import java.util.Arrays;
-import static com.supwisdom.dlpay.busQRcode.CryptoUtil.desDecryptECB;
-import static com.supwisdom.dlpay.busQRcode.CryptoUtil.desEncryptECB;
+import static com.supwisdom.dlpay.busqrcode.CryptoUtil.desDecryptECB;
+import static com.supwisdom.dlpay.busqrcode.CryptoUtil.desEncryptECB;
public class PbocAlgorithem {
diff --git a/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/QrCode.java b/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/QrCode.java
index 5f8a7af..93f6b60 100644
--- a/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/QrCode.java
+++ b/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/QrCode.java
@@ -1,12 +1,11 @@
package com.supwisdom.dlpay.busqrcode;
import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator;
-import com.supwisdom.dlpay.util.TOTP;
+
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
@@ -23,7 +22,7 @@
public static final String DELIMITER = ":";
public static final String SCOPE_DELIMITER = ",";
public static final String DEFAULT_SCOPE = "1"; //默认仅支持 1-大理车载消费场景
- public static final int TOTP_OFFSET = 30;
+ public static final int TOTP_OFFSET = 10; //TODO: 前后偏移10步,30*10=300s=5m 约前后5分钟内totp有效
// static final ArrayList<String> DEFAULT_SCOPES = new ArrayList<>();
// static {
@@ -203,10 +202,10 @@
logger.info("scope=" + qrBuilder.scope);
if (qrBuilder.resetSeed) logger.info("seed=" + qrBuilder.seed);
if (qrBuilder.resetIv) logger.info("iv=" + encodeHex(qrBuilder.iv));
- if (null != qrBuilder.sKey) logger.info("sKey=" + encodeHex(qrBuilder.sKey));
+ if (null != qrBuilder.sKey) logger.info("sKey=" + encodeHex(qrBuilder.sKey).toUpperCase());
if (null != qrBuilder.prefix) logger.info("prefix=" + qrBuilder.prefix);
- logger.info("rootkey=" + encodeHex(qrBuilder.rootKey));
+ logger.info("rootkey=" + encodeHex(qrBuilder.rootKey).toUpperCase());
logger.info("=======================================================");
}
@@ -233,7 +232,7 @@
logger.info("appid=" + qrBuilder.appid);
if (qrBuilder.resetSeed) logger.info("seed=" + qrBuilder.seed);
if (qrBuilder.resetIv) logger.info("iv=" + encodeHex(qrBuilder.iv));
- if (null != qrBuilder.sKey) logger.info("sKey=" + encodeHex(qrBuilder.sKey));
+ if (null != qrBuilder.sKey) logger.info("sKey=" + encodeHex(qrBuilder.sKey).toUpperCase());
if (null != qrBuilder.prefix) logger.info("prefix=" + qrBuilder.prefix);
logger.info("=======================================================");
}
@@ -273,7 +272,7 @@
if (null == qrBuilder.sKey) {
throw new RuntimeException("sKey is null");
}
- final String encdata = uid + qrBuilder.appid + amount + transdate + transtime + "{" + encodeHex(qrBuilder.sKey) + "}";
+ final String encdata = uid + qrBuilder.appid + amount + transdate + transtime + "{" + encodeHex(qrBuilder.sKey).toUpperCase() + "}";
return encodeHex(sha256(encdata.getBytes())).toUpperCase();
}
diff --git a/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/TOTP.java b/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/TOTP.java
new file mode 100644
index 0000000..450ca25
--- /dev/null
+++ b/bus-qrcode/src/main/java/com/supwisdom/dlpay/busqrcode/TOTP.java
@@ -0,0 +1,264 @@
+package com.supwisdom.dlpay.busqrcode;
+
+/**
+ 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 javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+
+/**
+ * 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 static final int[] DIGITS_POWER
+ // 0 1 2 3 4 5 6 7 8
+ = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
+
+ 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;
+ }
+
+ /**
+ * 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);
+
+ int otp = binary % DIGITS_POWER[codeDigits];
+
+ result = Integer.toString(otp);
+
+ while (result.length() < codeDigits) {
+ result = "0" + result;
+ }
+
+ return result;
+ }
+
+ public static void main(String[] args) {
+ long X = 30;
+ long T0 = 0;
+ String steps = "0";
+ long time = System.currentTimeMillis() / 1000;
+ System.out.println(time);
+ long T = (time - T0) / X;
+ steps = Long.toHexString(T).toUpperCase();
+ while (steps.length() < 16) {
+ steps = "0" + steps;
+ }
+ String seed = "9e8bb3d2df73741c041aef39e37ee015fc98233720a1350fcd67d5c3027896ac";
+ String totp = TOTP.generateTOTP(seed, steps, "8", "HmacSHA256");
+ System.out.println(totp);
+
+ /*
+
+
+ SecureRandom a = new SecureRandom();
+ byte[] b = a.generateSeed(8);
+ // Seed for HMAC-SHA1 - 20 bytes
+ String seed = "3132333435363738393031323334353637383930";
+
+ // Seed for HMAC-SHA256 - 32 bytes
+ //String seed32 = "3132333435363738393031323334353637383930313233343536373839303132";
+ String seed32 = "e5b72370f61687c8075b8383266f4fbcbb3b55da178eed76329666600c134093";
+
+ // Seed for HMAC-SHA512 - 64 bytes
+ String seed64 = "3132333435363738393031323334353637383930"
+ + "3132333435363738393031323334353637383930"
+ + "3132333435363738393031323334353637383930" + "31323334";
+ long T0 = 0;
+ long X = 30;
+ long[] testTime = { 59L, 1111111109L, 1111111111L, 1234567890L,
+ 2000000000L, 20000000000L };
+
+ 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, "8",
+ "HmacSHA512") + "| SHA512 |");
+
+ System.out.println("+---------------+-----------------------+"
+ + "------------------+--------+--------+");
+ }
+ } catch (final Exception e) {
+ System.out.println("Error : " + e);
+ }
+
+ */
+ }
+}
\ No newline at end of file
diff --git a/bus-qrcode/src/test/java/com/supwisdom/dlpay/busqrcode/QrcodeTest.java b/bus-qrcode/src/test/java/com/supwisdom/dlpay/busqrcode/QrcodeTest.java
index e69de29..9e8bae1 100644
--- a/bus-qrcode/src/test/java/com/supwisdom/dlpay/busqrcode/QrcodeTest.java
+++ b/bus-qrcode/src/test/java/com/supwisdom/dlpay/busqrcode/QrcodeTest.java
@@ -0,0 +1,61 @@
+package com.supwisdom.dlpay.busqrcode;
+
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.supwisdom.dlpay.busqrcode.BinUtil.*;
+import static com.supwisdom.dlpay.busqrcode.CryptoUtil.*;
+import static org.junit.Assert.*;
+
+public class QrcodeTest {
+ private Logger logger = LoggerFactory.getLogger(QrcodeTest.class);
+
+ @Test
+ public void testBusQrcode() {
+ String prefix = "dlsmk_";
+ String appid = "10001";
+ String uid = "ff8080816cf13e79016cf15d63f40011";
+ String scope = "1,2";
+ String rkey = "0A5DE6CE985D43989B7EBE64AD8EB9C3";
+ Integer amount = 100;
+ String transdate = "20200220";
+ String transtime = "090921";
+
+ String skey = encodeHex(desDecryptECB(md5(appid.getBytes()), decodeHex(rkey))).toUpperCase();
+ logger.info("skey=[" + skey + "]");
+ String rootKey = encodeHex(desEncryptECB(md5(appid.getBytes()), decodeHex(skey))).toUpperCase();
+ assertEquals("rootkey与skey互为3des加密关系错误!", rkey, rootKey);
+
+ QrCode encQr = QrCode.builder()
+ .appid(appid)
+ .rootkey(rkey)
+ .uid(uid)
+ .scope(scope)
+ .prefix(prefix)
+ .debug(true)
+ .create();
+ String qrcode = encQr.qrcode();
+ logger.info("qrcode=[" + qrcode + "]");
+
+ QrCode decQr = QrCode.builder()
+ .rootkey(appid, skey)
+ .prefix(prefix)
+ .debug(true)
+ .create();
+
+// try {
+// Thread.sleep(6*60*1000); //totp测试
+// } catch (InterruptedException e) {
+// e.printStackTrace();
+// }
+
+ String decUid = decQr.decodeUid(qrcode);
+ assertEquals("解码uid错误!", uid, decUid);
+
+ String tac = decQr.tac(uid, amount, transdate, transtime);
+ String tacData = uid + appid + amount + transdate + transtime + "{" + skey + "}";
+ String testTac = encodeHex(sha256(tacData.getBytes())).toUpperCase();
+ assertEquals("tac计算错误", tac, testTac);
+ }
+}
\ No newline at end of file