Google Authenticator的Java示例

Google身份验证器Google Authenticator是谷歌推出的基于时间的一次性密码(Time-based One-time Password,简称TOTP),只需要在手机上安装该APP,就可以生成一个随着时间变化的一次性密码,用于帐户验证。

相对于手机短信两步验证(Two-step verification),Google Authenticator两步验证有两大好处:一是避免无法接收验证短信的痛苦,尤其是用国内手机接收国外短信,你会发现经常收不到;二是Google Authenticator不用联网也可用,手机欠费停机不影响。

国内外各大网站和应用都基本上支持Google Authenticator两步验证了。这篇文章就简单说明一下,如何在自己的网站上集成Google身份验证器

一、准备工作

需要jar包:commons-codec-1.8

二、Java类

package controller;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;

public class GoogleAuthenticatorSample {

	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; // default 3 - max 17 (from google docs)最多可偏移的时间

	public void setWindowSize(int s) {
		if (s >= 1 && s <= 17)
			window_size = s;
	}

	/**
	 * 验证身份验证码是否正确
	 * 
	 * @param codes
	 *            输入的身份验证码
	 * @param savedSecret
	 *            密钥
	 * @return
	 */
	public static Boolean authcode(String codes, String savedSecret) {
		long code = 0;
		try {
			code = Long.parseLong(codes);
		} catch (Exception e) {
			e.printStackTrace();
		}
		long t = System.currentTimeMillis();
		GoogleAuthenticatorSample ga = new GoogleAuthenticatorSample();
		ga.setWindowSize(3); // should give 5 * 30 seconds of grace...
		boolean r = ga.check_code(savedSecret, code, t);
		return r;
	}

	/**
	 * 获取密钥
	 * 
	 * @param user
	 *            用户
	 * @param host
	 *            域
	 * @return 密钥
	 */
	public static String genSecret(String user, String host) {
		String secret = GoogleAuthenticatorSample.generateSecretKey();
		GoogleAuthenticatorSample.getQRBarcodeURL(user, host, secret);
		return secret;
	}

	private static String generateSecretKey() {
		SecureRandom sr = null;
		try {
			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);
			String encodedKey = new String(bEncodedKey);
			return encodedKey;
		} catch (NoSuchAlgorithmException e) {
			// should never occur... configuration error
		}
		return null;
	}

	/**
	 * 获取二维码图片URL
	 * 
	 * @param user
	 *            用户
	 * @param host
	 *            域
	 * @param secret
	 *            密钥
	 * @return 二维码URL
	 */
	public static String getQRBarcodeURL(String user, String host, String secret) {
		String format = "https://www.google.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl=otpauth://totp/%s@%s%%3Fsecret%%3D%s";
		return String.format(format, user, host, secret);
	}

	private 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) {
				e.printStackTrace();
				throw new RuntimeException(e.getMessage());
			}
			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;
	}

	public static void main(String[] args) {
		/*
		 * 注意:先运行前两步,获取密钥和二维码url。 然后只运行第三步,填写需要验证的验证码,和第一步生成的密钥
		 */
		String user = "testUser";
		String host = "testHost";
		// 第一步:获取密钥
		String secret = genSecret(user, host);
		System.out.println("secret:" + secret);
		// 第二步:根据密钥获取二维码图片url(可忽略)
		String url = getQRBarcodeURL(user, host, secret);
		System.out.println("url:" + url);
		// 第三步:验证(第一个参数是需要验证的验证码,第二个参数是第一步生成的secret运行)
		boolean result = authcode("271239", "OHXU6PLMZMDJIDY6");
		System.out.println("result:" + result);
	}
}


账户为:user@host。上面代码对应的账户为:testUser@testHost。
密钥是第一步生成。选择基于时间。

添加完成后,即可以动态的生成6位数字的验证码。
在项目需要做验证的地方,调用authcode()方法,传入用户输入的验证码和密钥,即可判断用户输入是否正确。

转载于:https://my.oschina.net/martin123/blog/967817

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是 Google AuthenticatorJava 代码: ```java import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base32; public class GoogleAuthenticator { // These fields are required for generating codes. private static final int SECRET_SIZE = 10; private static final int INTERVAL = 30; // These fields are required for validating codes. private static final int WINDOW_SIZE = 3; private static final Base32 base32 = new Base32(); /** * Generate a new secret key. */ public static String generateSecretKey() { SecureRandom random = new SecureRandom(); byte[] bytes = new byte[SECRET_SIZE]; random.nextBytes(bytes); return base32.encodeToString(bytes); } /** * Generate a code for the given secret key and time. */ public static int generateCode(String secret, long time) throws NoSuchAlgorithmException, InvalidKeyException { byte[] key = base32.decode(secret); byte[] data = new byte[8]; long value = time / INTERVAL; for (int i = 7; i >= 0; i--) { data[i] = (byte) (value & 0xff); value >>= 8; } SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA1"); Mac mac = Mac.getInstance("HmacSHA1"); mac.init(signingKey); byte[] hash = mac.doFinal(data); int offset = hash[hash.length - 1] & 0xf; int truncatedHash = 0; for (int i = 0; i < 4; i++) { truncatedHash <<= 8; truncatedHash |= (hash[offset + i] & 0xff); } truncatedHash &= 0x7fffffff; truncatedHash %= 1000000; return truncatedHash; } /** * Validate a code for the given secret key and time. */ public static boolean validateCode(String secret, int code, long time) throws NoSuchAlgorithmException, InvalidKeyException { for (int i = -WINDOW_SIZE; i <= WINDOW_SIZE; i++) { long t = time + i * INTERVAL; int c = generateCode(secret, t); if (c == code) { return true; } } return false; } public static void main(String[] args) throws Exception { // Generate a new secret key. String secret = generateSecretKey(); System.out.println("Secret key: " + secret); // Get the current time. long time = System.currentTimeMillis(); // Generate a code for the current time. int code = generateCode(secret, time); System.out.println("Code: " + code); // Validate the code for the current time. boolean valid = validateCode(secret, code, time); System.out.println("Valid: " + valid); } } ``` 这个 Java 类包含了三个方法: - `generateSecretKey()`:生成一个新的密钥。 - `generateCode(secret, time)`:使用给定的密钥和时间生成一个验证码。 - `validateCode(secret, code, time)`:使用给定的密钥、验证码和时间验证代码是否有效。 这三个方法都使用了 Google Authenticator 协议,可以用于生成和验证 Google Authenticator 代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值