一.什么是OTP和TOTP
OTP全称叫One-time Password,也称动态口令,是根据专门的算法每隔60秒生成一个与时间相关的、不可预测的随机数字组合,每个口令只能使用一次,每天可以产生1440个密码。
TOTP算法(Time-based One-time Password algorithm)是一种从共享密钥和当前时间计算一次性密码的算法。
二.怎么使用动态口令登录认证
- 1.通过qrcode在web管理台生成一个带有key的二维码,key需要加密保存数据库。
- 2.基于js版的totp开发的跨平台的手机app,通过app扫描二维码进行绑定key,实现一个60秒切换一次口令的手机动态令牌。
- 3.用户使用手机动态口令登录系统,后端使用基于java的totp实现结合之前存储到数据库的key进行动态口令校验。
三.js和java版totp使用hmac-sha1实现
1.totp的js实现,需要自行下载hmac-sha1的js库
- totp.js代码
var totp = {
};
//mtimeStep 60,
totp.generateOtp = function(mSeed,mtimeStep,mOtpLength) {
//0 1 2 3 4 5 6 7 8 9 10
var DIGITS_POWER = [1,10,100,1000,10000,100000,1000000,10000000,100000000, 1000000000, 10000000000];
var counter = new Array(8);
//GMT时间毫秒数
var time = parseInt(new Date().getTime()/1000);
//mtimeStep
var movingFactor = parseInt(time/mtimeStep);
for(var i = counter.length - 1;i >= 0;i --){
counter[i] = movingFactor & 0xff;
movingFactor >>= 8;
}
var tmp2 = totp.stringToHex(counter);
var result = CryptoJS.HmacSHA1(tmp2,mSeed).toString();
var hash = totp.hexToArr(result);
var offset = hash[hash.length - 1] & 0xf;
var otpBinary = ((hash[offset] & 0x7f) << 24)
|((hash[offset + 1] & 0xff) << 16)
|((hash[offset + 2] & 0xff) << 8)
|(hash[offset + 3] & 0xff);
var otp = otpBinary % DIGITS_POWER[mOtpLength];
var result = otp + "";
while(result.length < mOtpLength){
result = "0" + result;
}
return result;
};
totp.hexToArr = function(hex){
var arr = new Array(parseInt(hex.length/2));
for(var i = 0;i < arr.length;i ++){
arr[i] = parseInt(hex.substr(2*i,2),16) & 0xff;
}
return arr;
};
totp.stringToHex = function(arr){
var result = "";
var tmp = "";
for(var i = 0;i < arr.length;i ++){
tmp = arr[i].toString(16);
if(tmp.length == 1){
tmp = "0" + tmp;
}
result += tmp;
}
return result;
};
- totp.js测试代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script type="text/javascript" src="js/hmac-sha1.js"></script>
<script type="text/javascript" src="js/totp.js"></script>
<script type="text/javascript">
//第一个参数密钥,第二个参数时间间隔60秒,第三个参数otp长度6位
var otp = totp.generateOtp("3132333435363738393031323334353637383930",60,6);
console.log(otp);
</script>
</body>
</html>
2.totp的java实现
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* java版totp
* @author admin
*/
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};
/**
* 采用默认值3分钟计算比对(GMT时间),60秒产生一个6位otp值
* @param seed 种子信息
* @param checkVal 校验值
* @return
*/
public static boolean checkOtp(String seed,String checkVal){
return checkOtp(seed, checkVal, 60, 6);
}
/**
* 根据长度创建手机令牌码,长度不要修改
* @param seed
* @param date
* @param length
* @return
*/
public static String createOtp(String seed){
return createOtp(seed, 60, 6);
}
/**
* 根据配置的时间计算比对(Asia/Shanhai上海时区)
* @param seed 种子信息
* @param checkVal 校验值
* @param min 客户端和服务端分钟误差
* @return
*/
static boolean checkOtp(String seed,String checkVal,int timeStep,int min){
String tmp = createOtp(seed, timeStep, min);
if(tmp.equals(checkVal)) return true;
return false;
}
/**
* 根据长度创建手机令牌码,长度不要修改
* @param seed
* @param date
* @param length
* @return
*/
static String createOtp(String seed,int timeStep, int otpLength){
Date date = new Date();
Calendar time = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
time.setTime(date);
long value = time.getTimeInMillis()/1000;
int movingFactor = (int) (value/timeStep);
return generateOtp(seed, movingFactor, otpLength);
}
static byte[] stringToHex(String hexInputString){
byte[] bts = new byte[hexInputString.length() / 2];
for (int i = 0; i < bts.length; i++) {
bts[i] = (byte) Integer.parseInt(hexInputString.substring(2*i, 2*i+2), 16);
}
return bts;
}
static String generateOtp(String mSeed,long time,int mOtpLength) {
byte[] counter = new byte[8];
long movingFactor = time;
for(int i = counter.length - 1; i >= 0; i--){
counter[i] = (byte)(movingFactor & 0xff);
movingFactor >>= 8;
}
byte[] hash = hmacSha(mSeed.getBytes(), byteArrayToHexString(counter).getBytes());
int offset = hash[hash.length - 1] & 0xf;
int otpBinary = ((hash[offset] & 0x7f) << 24)
|((hash[offset + 1] & 0xff) << 16)
|((hash[offset + 2] & 0xff) << 8)
|(hash[offset + 3] & 0xff);
int otp = otpBinary % DIGITS_POWER[mOtpLength];
String result = Integer.toString(otp);
while(result.length() < mOtpLength){
result = "0" + result;
}
return result;
}
static byte[] hmacSha(byte[] seed, byte[] counter) {
try{
Mac hmacSha1;
try{
hmacSha1 = Mac.getInstance("HmacSHA1");
}catch(NoSuchAlgorithmException ex){
hmacSha1 = Mac.getInstance("HMAC-SHA-1");
}
SecretKeySpec macKey = new SecretKeySpec(seed, "RAW");
hmacSha1.init(macKey);
return hmacSha1.doFinal(counter);
}catch(GeneralSecurityException ex){
throw new RuntimeException(ex);
}
}
static String byteArrayToHexString(byte[] digest) {
StringBuffer buffer = new StringBuffer();
for(int i =0; i < digest.length; i++){
String hex = Integer.toHexString(0xff & digest[i]);
if(hex.length() == 1)
buffer.append("0");
buffer.append(hex);
}
return buffer.toString();
}
public static void main(String[] args) throws Exception {
String seed = "3132333435363738393031323334353637383930";
System.out.println(createOtp(seed));
System.out.println(checkOtp(seed,"527078"));
}
}
四.HOTP原理类似
后面会给出hotp
网友评论