背景
最近公司要求所有内网未集成单点的系统做双因子验证,为了节约成本,最终选择Google Authenticator
原理
使用密钥和时间戳通过一种算法生成一个6位数字的一次性验证码
集成流程
1、手机下载 Google Authenticator
IOS:在App Store 搜索 Google Authenticator
Android:
打开连接 https://en.softonic.com/download
右上角搜索 Google Authenticator
选择平台 Platform:Android
点击下方 Google Authenticator
点击 FREE DOWNLOAD
再点击 FREE APK DOWNLOAD 等待下载完成
2、生成Secret
调用代码中的 genSecret() 方法生成,记得保存起来,用户需要使用
3、用户扫描二维化进行绑定(也可以拿到密钥手动绑定)
1、当前用户生成的密钥可以生成二维码,用户用app右下角扫描二维码即可添加
参考代码中的main方法
2、手动添加
这里需要知道密钥,添加上名称和密钥即可
4、输入用户名、密码、动态码
5、登录成功
集成流程图
代码
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
//需要大于1.8 版本 commons-codec-1.8.jar
public class GoogleAuthenticator {
public static final int SECRET_SIZE = 10;
public static final String SEED = "g8GjEvTbW5oVSV7avLBdwIHqGlUYNzKFI7izOF8GwLDVKs2m0QN7vxRs2im5MDaNCWGmcD2rvcZx";
public static final String RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";
int window_size = 3;
public void setWindowSize(int s) {
if (s >= 1 && s <= 17)
window_size = s;
}
//生成密钥
public static String genSecret() {
return GoogleAuthenticator.generateSecretKey();
}
public static void main(String[] args) {
String format = "otpauth://totp/ACCOUNT?secret=%s&issuer=%s";
String barcodeURL= String.format(format, "PO4F2DIF74VR4ARO", "appname");
//生成二维码
System.out.println(rwm);
}
public static String generateSecretKey() {
String encodedKey = null;
try {
SecureRandom sr = SecureRandom.getInstance(RANDOM_NUMBER_ALGORITHM);
sr.setSeed(Base64.decodeBase64(SEED));
byte[] buffer = sr.generateSeed(SECRET_SIZE);
Base32 codec = new Base32();
byte[] bEncodedKey = codec.encode(buffer);
encodedKey = new String(bEncodedKey);
}catch (NoSuchAlgorithmException e) {
}
return encodedKey;
}
//验证密钥
public boolean check_code(String secret, long code, long timeMsec) {
Base32 codec = new Base32();
byte[] decodedKey = codec.decode(secret);
long t = (timeMsec / 1000L) / 30L;
for (int i = -window_size; i <= window_size; ++i) {
long hash;
try {
hash = verify_code(decodedKey, t + i);
}catch (Exception e) {
return false;
}
if (hash == code) {
return true;
}
}
return false;
}
private static int verify_code(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
byte[] data = new byte[8];
long value = t;
for (int i = 8; i-- > 0; value >>>= 8) {
data[i] = (byte) value;
}
SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signKey);
byte[] hash = mac.doFinal(data);
int offset = hash[20 - 1] & 0xF;
long truncatedHash = 0;
for (int i = 0; i < 4; ++i) {
truncatedHash <<= 8;
truncatedHash |= (hash[offset + i] & 0xFF);
}
truncatedHash &= 0x7FFFFFFF;
truncatedHash %= 1000000;
return (int) truncatedHash;
}
}