有java、js、php、python、c版本,可以加入自己程序里,用来做登录验证。
代码放在https://gitee.com/superzlc/otp
另外微信小程序“动态口令”能够提供otp验证
java:
package com.superzlc.utils;
import java.util.HashMap;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
public abstract class OTP {
private static final String MAC_NAME = "HmacSHA1";
public static void main(String[] args) {
HOTP p1 = new HOTP("123456", 0);
System.out.println(p1.gen()); // 186818
System.out.println();
System.out.println(p1.gen(1234)); // 263197
System.out.println(p1.verify("263197", 1234));
System.out.println();
TOTP p2 = new TOTP("123456", 3, 30);
System.out.println(p2.gen(1614054214456L)); // 330925
System.out.println(p2.verify("330925", 1614054214456L));
TOTP p3 = new TOTP("helloworld", 3, 30);
p3.setSecret("helloworld", "string");
p3.setSecret("68656c6c6f776f726c64", "hex");
p3.setSecret("aGVsbG93b3JsZA==", "base64");
System.out.println(p3.gen(1614054214456L)); // 759440
System.out.println(p3.verify("759440", 1614054214456L));
}
// ----------------
public static class HOTP {
private byte[] secret = null;
private long counter = 0;
public HOTP() {
this.secret = null;
this.counter = 0;
}
public HOTP(String secret, long counter) {
this.secret = secret == null ? null : secret.getBytes();
this.counter = counter;
}
public void setSecret(byte[] secret) {
this.secret = secret;
}
public void setSecret(String secret, String format) {
this.secret = _convertSecret(secret, format);
}
//
public String gen() {
return gen(this.counter);
}
public boolean verify(String token) {
if (verify(token, this.counter)) {
this.counter++;
return true;
}
return false;
}
public String gen(long counter) {
return _gen(secret, counter);
}
public boolean verify(String token, long counter) {
return token.equals(gen(counter));
}
// ------------
private static String _gen(byte[] secret, long counter) {
byte[] data = new byte[8];
for (int i = 7; i >= 0; i--) {
data[i] = (byte) (counter & 0xFF);
counter >>>= 8;
}
byte[] hash = _hmacSHA1Encrypt(secret, data);
int offset = hash[19] & 0xf;
int num = (hash[offset] & 0x7f) << 24 |
(hash[offset + 1] & 0xff) << 16 |
(hash[offset + 2] & 0xff) << 8 |
(hash[offset + 3] & 0xff);
num = num % 1000000;
return String.format("%06d", num);
}
//
private static byte[] _hmacSHA1Encrypt(byte[] secret, byte[] data) {
try {
Mac mac = Mac.getInstance(MAC_NAME);
SecretKey secretKey = new SecretKeySpec(secret, MAC_NAME);
mac.init(secretKey);
return mac.doFinal(data);
} catch(Exception e) {
throw new RuntimeException("hmacSHA1加密失败", e);
}
}
//
private static byte[] _convertSecret(String secret, String format) {
if ("hex".equals(format)) {
try {
return Hex.decodeHex(secret);
} catch(Exception e) {
throw new RuntimeException("参数错误");
}
} else if ("base64".equals(format)) {
return Base64.decodeBase64(secret);
} else if ("string".equals(format)) {
return secret.getBytes();
} else {
throw new RuntimeException("参数错误");
}
}
}
public static class TOTP {
private HOTP hotp = null;
private int window = 3;
private int interval = 30;
public TOTP() {
this.hotp = new HOTP();
this.window = 3;
this.interval = 30;
}
public TOTP(String secret, int window, int interval) {
this.hotp = new HOTP(secret, 0);
this.window = window;
this.interval = interval;
}
public void setSecret(byte[] secret) {
this.hotp.setSecret(secret);
}
public void setSecret(String secret, String format) {
this.hotp.setSecret(secret, format);
}
//
public String gen() {
long timestamp = System.currentTimeMillis();
return gen(timestamp);
}
public String gen(long timestamp) {
long counter = (long) (timestamp / 1000 / this.interval);
return this.hotp.gen(counter);
}
public HashMap<String, Object> gen2() {
long timestamp = System.currentTimeMillis();
return gen2(timestamp);
}
public HashMap<String, Object> gen2(long timestamp) {
long timestamp_second = timestamp / 1000;
long counter = (long) (timestamp_second / this.interval);
String token = this.hotp.gen(counter);
long step = timestamp_second % this.interval;
long refresh_time = counter * this.interval;
HashMap<String, Object> map = new HashMap<>();
map.put("token", token);
map.put("loop_length", this.interval);
map.put("loop_step", step);
map.put("refresh_interval", this.interval * 1000);
map.put("refresh_time", refresh_time * 1000);
map.put("timestamp", timestamp);
return map;
}
public boolean verify(String token) {
return this.verify(token, System.currentTimeMillis());
}
public boolean verify(String token, long timestamp) {
long counter = (long) (timestamp / 1000 / interval);
for (int i = 0, w = 0; ; i++) {
if (token.equals(this.hotp.gen(counter - i)))
return true;
if (++w >= window)
break;
if (i > 0) {
if (token.equals(this.hotp.gen(counter + i)))
return true;
if (++w > window)
break;
}
}
return false;
}
}
}
php:
<?php
class HOTP {
private $secret = null;
private $counter = 0;
public function __construct($secret = null, $counter = 0) {
$this->secret = $secret;
$this->counter = $counter;
}
public function setSecret($secret, $format = null) {
$this->secret = self::_convertSecret($secret, $format);
}
public function gen($counter = null) {
if ($counter === null) {
if ($this->counter === null) {
$this->counter = 0;
}
$counter = $this->counter;
}
return self::_gen($this->secret, $counter);
}
public function verify($token, $counter = null) {
if ($counter === null) {
if ($this->counter === null) {
$this->counter = 0;
}
$counter = $this->counter;
if ($token === $this->gen(counter)) {
$this->counter += 1;
return true;
}
return false;
}
return $token === $this->gen($counter);
}
private static function _gen($secret, $counter) {
$data = pack('NN', $counter / 4294967296, $counter & 0xFFFFFFFF);
$hash = hash_hmac('sha1', $data, $secret, true);
$offset = ord($hash[19]) &0xf;
$num = (ord($hash[$offset]) & 0x7F) << 24 |
(ord($hash[$offset + 1]) & 0xFF) << 16 |
(ord($hash[$offset + 2]) & 0xFF) << 8 |
(ord($hash[$offset + 3]) & 0xFF);
$num = $num % 1000000;
return sprintf("%06d", $num);
}
private static function _convertSecret($secret, $format) {
if (!is_string($secret))
throw Exception('参数错误');
if ('hex' === $format)
return hex2bin($secret);
else if ('base64' === $format)
return base64_decode($secret);
else if ('string' === $format || null === $format)
return $secret;
else
throw Exception('参数错误');
}
}
class TOTP {
private $hotp;
private $window = 3;
private $interval = 30;
public function __construct($secret = null, $window = 3, $interval = 30) {
$this->hotp = new HOTP($secret, 0);
$this->window = $window;
$this->interval = $interval;
}
public function setSecret($secret, $format = null) {
$this->hotp->setSecret($secret, $format);
}
public function gen($timestamp = null) {
$timestamp = empty($timestamp) ? (time() * 1000) : $timestamp;
$counter = floor($timestamp / 1000 / $this->interval);
return $this->hotp->gen($counter);
}
public function gen2($timestamp = null) {
$timestamp = empty($timestamp) ? (time() * 1000) : $timestamp;
$timestamp_second = $timestamp / 1000;
$counter = floor($timestamp_second / $this->interval);
$token = $this->hotp->gen($counter);
$step = $timestamp_second % $this->interval;
$refresh_time = $counter * $this->interval;
return array("token" => $token,
"loop_length" => $this->interval,
"loop_step" => $step,
"refresh_interval" => $this->interval * 1000,
"refresh_time" => $refresh_time * 1000,
"timestamp" => $timestamp
);
}
public function verify($token, $timestamp = null) {
$$timestamp = empty($timestamp) ? (time() * 1000) : $timestamp;
$counter = floor($timestamp / 1000 / $this->interval);
for ($i = 0, $w = 0; ; $i++) {
if ($token === $this->hotp->gen($counter - $i))
return true;
if (++$w >= $this->window)
break;
if ($i > 0) {
if ($token === $this->hotp->gen($counter + $i))
return true;
if (++$w >= $this->window)
break;
}
}
return false;
}
}
/**
function test() {
echo "\n<pre>\n";
$p1 = new HOTP("123456", 0);
echo $p1->gen() . "\n"; # 186818
echo $p1->gen(1234) . "\n"; # 263197
echo ($p1->verify('263197', 1234) ? "TRUE" : "FALSE") . "\n";
$p2 = new TOTP("123456", 3, 30);
echo $p2->gen(1614054214456) . "\n"; # 330925
echo ($p2->verify('330925', 1614054214456) ? "TRUE" : "FALSE") . "\n";
$p3 = new TOTP('helloworld', 3, 30);
$p3->setSecret('helloworld', 'string');
$p3->setSecret('68656c6c6f776f726c64', 'hex');
$p3->setSecret('aGVsbG93b3JsZA==', 'base64');
echo $p3->gen(1614054214456) . "\n"; # 759440
echo ($p3->verify('759440', 1614054214456) ? "TRUE" : "FALSE") . "\n";
echo "\n</pre>\n";
}
test();
**/
js:
// 需要CryptoJS
const CryptoJS = require('./crypto-js.js')
// ==================================================================================
var HOTP = function(secret = null, counter = 0) {
this.secret = secret == null ? null : CryptoJS.enc.Utf8.parse(secret);
this.counter = counter;
};
HOTP.prototype.setSecret = function(secret, format = null) {
this.secret = this._convertSecret(secret, format);
}
HOTP.prototype.gen = function(counter = null) {
if (counter === null) {
if (this.counter === undefined || this.counter === null) {
this.counter = 0;
}
counter = this.counter;
}
return this._gen(this.secret, counter);
}
HOTP.prototype.verify = function(token, counter = null) {
if (counter === null) {
if (this.counter === undefined || this.counter === null) {
this.counter = 0;
}
counter = this.counter;
if (token === this.gen(counter)) {
this.counter += 1;
return true;
}
return false;
}
return token === this.gen(counter);
}
HOTP.prototype._gen = function(secret, counter) {
var data = CryptoJS.lib.WordArray.create([counter / 4294967296, counter & 0xffffffff]);
var hash = CryptoJS.HmacSHA1(data, secret);
var hash2 = [];
for (var i = 0, j = 0; i < 5; i++) {
var v = hash.words[i];
hash2[j++] = ((v >> 24) & 0xFF);
hash2[j++] = ((v >> 16) & 0xFF);
hash2[j++] = ((v >> 8) & 0xFF);
hash2[j++] = ((v >> 0) & 0xFF);
}
var offset = hash2[19] & 0xf;
var num = (hash2[offset] & 0x7F) << 24 |
(hash2[offset + 1] & 0xFF) << 16 |
(hash2[offset + 2] & 0xFF) << 8 |
(hash2[offset + 3] & 0xFF);
num = num % 1000000;
if (num >= 100000) {
return String(num);
}
num = "000000" + num;
return num.substring(num.length - 6);
}
HOTP.prototype._convertSecret = function(secret, format) {
if (typeof(secret) === "string") {
if ('hex' === format)
return CryptoJS.enc.Hex.parse(secret);
else if ('base64' === format)
return CryptoJS.enc.Base64.parse(secret);
else if ('string' === format)
return CryptoJS.enc.Utf8.parse(secret);
else
throw Error('参数错误');
} else if (secret instanceof CryptoJS.constructor) { // XXX WordArray
return secret;
} else {
throw Error('参数错误');
}
}
// ==================================================================================
var TOTP = function(secret = null, window = 3, interval = 30) {
this.hotp = new HOTP(secret, 0);
this.window = window;
this.interval = interval;
}
TOTP.prototype.setSecret = function(secret, format = null) {
this.hotp.setSecret(secret, format);
}
TOTP.prototype.gen = function(timestamp = null) {
timestamp = timestamp === null ? new Date().getTime() : timestamp;
var counter = Math.floor(timestamp / 1000 / this.interval);
return this.hotp.gen(counter);
}
TOTP.prototype.gen2 = function(timestamp = null) {
timestamp = timestamp === null ? new Date().getTime() : timestamp;
var timestamp_second = Math.floor(timestamp / 1000);
var counter = Math.floor(timestamp_second / this.interval);
var token = this.hotp.gen(counter);
var step = timestamp_second % this.interval;
var refresh_time = tcounter * this.interval;
return {
token: token, // 生成的令牌
loop_length: this.interval, // 循环角度,循环长度
loop_step: step, // 循环角度,循环到的步骤
refresh_interval: this.interval * 1000, // 刷新周期
refresh_time: refresh_time * 1000, // 刷新时间
timestamp: timestamp, // 当前时间
};
}
TOTP.prototype.verify = function(token, timestamp = null) {
timestamp = timestamp === null ? new Date().getTime() : timestamp;
var counter = Math.floor(timestamp / 1000 / this.interval);
for (var i = 0, w = 0; ; i++) {
if (token === this.hotp.gen(counter - i))
return true;
if (++w >= this.window)
break;
if (i > 0) {
if (token === this.hotp.gen(counter + i))
return true;
if (++w >= this.window)
break;
}
}
return false;
}
module.exports = {
HOTP: HOTP,
TOTP: TOTP,
}
/*
!function() {
var p1 = new HOTP("123456", 0);
console.log(p1.gen()); // 186818
console.log(p1.gen(1234)); // 263197
console.log(p1.verify("263197", 1234));
var p2 = new TOTP("123456", 3, 30);
console.log(p2.gen(1614054214456)); // 330925
console.log(p2.verify("330925", 1614054214456));
var p3 = new TOTP("helloworld", 3, 30);
p3.setSecret("helloworld", "string");
p3.setSecret("68656c6c6f776f726c64", "hex");
p3.setSecret("aGVsbG93b3JsZA==", "base64");
console.log(p3.gen(1614054214456)); // 759440
console.log(p3.verify("759440", 1614054214456));
}();
*/