前言
動態密碼,也可以稱為一次性密碼(One Time Password,OTP),大部分情況下都是可以隨著一個變量(如時間,使用次數等等因素)的變化而重新生成的密碼,在我們的工作生活中經常可以碰到。下面我們對動態密碼的簡單生成做一個說明。
一、基本算法介紹
動態密碼的基本認證原理
動態密碼的基本認證原理是認證的雙方共享種子秘鑰。
認證的雙方使用同一個種子密碼對一個事件選定指定的算法進行計算,這樣才能夠完成正常的認證工作。
常見的動態密碼算法
一般來說,常見的動態密碼有兩類:
計次使用(HOTP):計次使用的OTP生成之后,可在不限時間內使用,每次成功的使用OTP之后,計數器加1,生成新的密碼。用于實現計次使用動態密碼的算法叫 HOTP。
計時使用(TOTP):計時使用的OTP則可設定密碼有效時間,超時之后則舊OTP廢棄不用,需要再次生成新密碼。用于實現計時使用動態密碼的算法叫TOTP。
HOTP
HOTP 算法,全稱是“An HMAC-Based One-Time Password Algorithm”,是一種基于事件計數的一次性密碼生成算法。
算法本身可以用兩條簡短的表達式描述:
HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
PWD(K,C,digit) = HOTP(K,C) mod 10^Digit
詳細的算法原理可以參考:動態密碼算法介紹與實現,本文不再贅述。
TOTP
TOTP 算法,全稱是 TOTP: Time-Based One-Time Password Algorithm,其基于 HOTP 算法實現,核心是將移動因子從 HOTP 中的事件計數改為時間差。完整的 TOTP 算法的說明可以查看 RFC 6238,其公式描述也非常簡單:
TOTP = HOTP(K, T) // T is an integer
and represents the number of time steps between the initial counter
time T0 and the current Unix time
More specifically, T = (Current Unix time - T0) / X, where the
default floor function is used in the computation.
詳細的算法原理可以參考:動態密碼算法介紹與實現,本文不再贅述。
二、代碼實現
代碼如下:
public class TOTPUtil {
private static final String SECRET_KEY = "def9494ba29d2ccf021cc08003300e4c";
/**
* 時間步長 單位:毫秒 作為口令變化的時間周期
*/
private static final long STEP = 24 * 60 * 60 * 1000;
/**
* 轉碼位數 [1-8]
*/
private static final int CODE_DIGITS = 6;
/**
* 初始化時間
*/
private static final long INITIAL_TIME = 0;
/**
* 柔性時間回溯
*/
private static final long FLEXIBILIT_TIME = 5000;
/**
* 數子量級
*/
private static final int[] DIGITS_POWER = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
private static final String code = "Admin";
private static final String pass = "PASS";
/**
* 生成默認的一次性密碼
*
* @return
*/
public static String generateMyTOTP() {
return generateMyTOTP(code, pass);
}
/**
* 生成一次性密碼
*
* @param code 賬戶
* @param pass 密碼
* @return String
*/
public static String generateMyTOTP(String code, String pass) {
if (TextUtils.isEmpty(code) || TextUtils.isEmpty(pass)) {
Logger.get().e("password is null");
}
long now = new Date().getTime();
//返回為無符號整數基數為16的整數參數的字符串
String time = Long.toHexString(timeFactor(now)).toUpperCase();
return generateTOTP(code + pass + SECRET_KEY, time);
}
/**
* 獲取動態因子
*
* @param targetTime 指定時間
* @return long
*/
private static long timeFactor(long targetTime) {
return (targetTime - INITIAL_TIME) / STEP;
}
/**
* 哈希加密
*
* @param crypto 加密算法
* @param keyBytes 密鑰數組
* @param text 加密內容
* @return byte[]
*/
private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) {
try {
Mac hmac;
hmac = Mac.getInstance(crypto);
SecretKeySpec macKey = new SecretKeySpec(keyBytes, "AES");
hmac.init(macKey);
return hmac.doFinal(text);
} catch (GeneralSecurityException gse) {
throw new UndeclaredThrowableException(gse);
}
}
private static byte[] hexStr2Bytes(String hex) {
byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
byte[] ret = new byte[bArray.length - 1];
System.arraycopy(bArray, 1, ret, 0, ret.length);
return ret;
}
private static String generateTOTP(String key, String time) {
return generateTOTP(key, time, "HmacSHA1");
}
private static String generateTOTP256(String key, String time) {
return generateTOTP(key, time, "HmacSHA256");
}
private static String generateTOTP512(String key, String time) {
return generateTOTP(key, time, "HmacSHA512");
}
private static String generateTOTP(String key, String time, String crypto) {
StringBuilder timeBuilder = new StringBuilder(time);
while (timeBuilder.length() < 16)
timeBuilder.insert(0, "0");
time = timeBuilder.toString();
byte[] msg = hexStr2Bytes(time);
byte[] k = key.getBytes();
byte[] hash = hmac_sha(crypto, k, msg);
return truncate(hash);
}
/**
* 截斷函數
*
* @param target 20字節的字符串
* @return String
*/
private static String truncate(byte[] target) {
StringBuilder result;
int offset = target[target.length - 1] & 0xf;
int binary = ((target[offset] & 0x7f) << 24)
| ((target[offset + 1] & 0xff) << 16)
| ((target[offset + 2] & 0xff) << 8) | (target[offset + 3] & 0xff);
int otp = binary % DIGITS_POWER[CODE_DIGITS];
result = new StringBuilder(Integer.toString(otp));
while (result.length() < CODE_DIGITS) {
result.insert(0, "0");
}
return result.toString();
}
/**
* 剛性口令驗證
*
* @param code 賬戶
* @param pass 密碼
* @param totp 待驗證的口令
* @return boolean
*/
public static boolean verifyTOTPRigidity(String code, String pass, String totp) {
return generateMyTOTP(code, pass).equals(totp);
}
/**
* 柔性口令驗證
*
* @param code 賬戶
* @param pass 密碼
* @param totp 待驗證的口令
* @return boolean
*/
public static boolean verifyTOTPFlexibility(String code, String pass, String totp) {
long now = new Date().getTime();
String time = Long.toHexString(timeFactor(now)).toUpperCase();
String tempTotp = generateTOTP(code + pass + SECRET_KEY, time);
if (tempTotp.equals(totp)) {
return true;
}
String time2 = Long.toHexString(timeFactor(now - FLEXIBILIT_TIME)).toUpperCase();
String tempTotp2 = generateTOTP(code + pass + SECRET_KEY, time2);
return tempTotp2.equals(totp);
}
}