Google Authenticator的Java示例

Stella981
• 阅读 1318

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);
    }
}

Google Authenticator的Java示例

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

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

点赞
收藏
评论区
推荐文章
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
DevOpSec DevOpSec
3年前
PAM和账户安全配置
PAM(可插入认证模块)是UNIX系统上一个实现模块化的身份验证模块服务当程序需要对用户进行身份验证时加载并执行的。PAM文件通常位于/etc/pam.d目录中。配置文件/etc/pam.d/passwordauth/etc/pam.d/systemauth/etc/security/pwquality.conf配置密码创建要
莎利亚 莎利亚
3年前
JWT 认证方案学习
定义JWT(JSONWEBTOKEN)是一种安全通讯标准,它定义了一种紧凑自包含的方式,用于在各方之间安全的传输JSON对象。常见应用场景是API之间的认证通讯。一般的用户认证流程1、请求方发送账户密码到服务器,验证账户可用性。2、验证成功,生成session,保存在服务端。3、服务端返回一个s
Easter79 Easter79
3年前
spring整合Google Kaptcha验证码的使用
Kaptcha是什么?kaptcha是谷歌开源的非常实用的验证码生成工具,基于SimpleCaptcha的开源项目。使用Kaptcha生成验证码十分简单并且参数可以进行自定义。只需添加jar包配置下就可以使用,通过配置,可以自己定义验证码大小、颜色、显示的字符等等。下面就来讲一下如何使用kaptcha生成验证码以及在服务器端取
Stella981 Stella981
3年前
APP Inventor 基于网络微服务器的即时通信APP
APPInventor基于网络微服务器的即时通信APP一、总结一句话总结:(超低配版的QQ,逃~)1、APPInventor是什么?google傻瓜式编程手机appAppInventor是一款谷歌公
Stella981 Stella981
3年前
Shiro要点概览与SpringBoot整合实例
1\.简介概念说明Subject主体,简化点说就是用户实体PrincipalSubject的唯一标识,如id、用户名、手机号、邮箱等Credential凭证信息,主体证明自己的东西,如密码、证书等Authenticator认证器,对Subject身份进行认证,例如验证用户的用户名和密码是否匹配Aut
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Stella981 Stella981
3年前
Kubernetes 两步验证
作者:CODING王炜1\.背景如果对Kubernetes集群安全特别关注,那么我们可能想要实现这些需求:如何实现Kubernetes集群的两步验证,除了集群凭据,还需要提供一次性的Token校验?如何验证部署的镜像是否安全合规,使得仅允许部署公司内部镜像仓库的Docker镜像?
Wesley13 Wesley13
3年前
2.权限管理准备工作:你应该知道的ASP.NET网站最基本的安全措施!
一.ASP.NET与 IIS一起使用的身份验证方法来验证用户凭据(如用户名和密码):1.Windows:基本、摘要式或集成Windows身份验证(NTLM或Kerberos)。2.Forms身份验证,您可以通过该身份验证在您的应用程序中创建登录页并管理身份验证。3.客户证书身份验证二.ASP.NET网站最基本的安全措施:
Stella981 Stella981
3年前
Google Kaptcha验证码的使用 Kaptcha是什么? 怎么使用Kaptcha? 更详细的Kaptcha验证码配置? 效果图如下
Kaptcha是什么?kaptcha是谷歌开源的非常实用的验证码生成工具,基于SimpleCaptcha的开源项目。使用Kaptcha生成验证码十分简单并且参数可以进行自定义。只需添加jar包配置下就可以使用,通过配置,可以自己定义验证码大小、颜色、显示的字符等等。下面就来讲一下如何使用kaptcha生成验证码以及在服务器端取