作者:徐小花
链接:https://www.zhihu.com/question/20462696/answer/18731073
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。

实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。

目前客户端有:
android版: Google 身份验证器
iOS版:https://itunes.apple.com/cn/app/google-authenticator/id388497605

实现原理:

一、用户需要开启Google Authenticator服务时,
1.服务器随机生成一个类似于『DPI45HKISEXU6HG7』的密钥,并且把这个密钥保存在数据库中。
2.在页面上显示一个二维码,内容是一个URI地址(otpauth://totp/账号?secret=密钥),如『otpauth://totp/kisexu@gmail.com?secret=DPI45HCEBCJK6HG7』,下图:

otpauth://totp/kisexu@gmail.com?secret=DPI45HCEBCJK6HG7 (二维码自动识别)

3.客户端扫描二维码,把密钥『DPI45HKISEXU6HG7』保存在客户端。

二、用户需要登陆时
1.客户端每30秒使用密钥『DPI45HKISEXU6HG7』和时间戳通过一种『算法』生成一个6位数字的一次性密码,如『684060』。如下图android版界面:
<img src="https://pic1.zhimg.com/c2056261a0b106af19517697887c0b38_b.jpg" data-rawwidth="281" data-rawheight="398" class="content_image" width="281">
2.用户登陆时输入一次性密码『684060』。
3.服务器端使用保存在数据库中的密钥『DPI45HKISEXU6HG7』和时间戳通过同一种『算法』生成一个6位数字的一次性密码。大家都懂控制变量法,如果算法相同、密钥相同,又是同一个时间(时间戳相同),那么客户端和服务器计算出的一次性密码是一样的。服务器验证时如果一样,就登录成功了。

Tips:
1.这种『算法』是公开的,所以服务器端也有很多开源的实现,比如php版的:https://github.com/PHPGangsta/GoogleAuthenticator 。上github搜索『Google Authenticator』可以找到更多语言版的Google Authenticator。
2.所以,你在自己的项目可以轻松加入对Google Authenticator的支持,在一个客户端上显示多个账户的效果可以看上面android版界面的截图。目前dropbox、lastpass、wordpress,甚至vps等第三方应用都支持Google Authenticator登陆,请自行搜索。
3.现实生活中,网银、网络游戏的实体动态口令牌其实原理也差不多,大家可以自行脑补下,谢谢。

示例url:
https://www.google.com/chart?chs=200x200&chld=M%7C0&cht=qr&chl=otpauth://totp/testuser@testhost%3Fsecret%3DIV5H5FGFJS6K4N2Y
-------------------------------------------
链接:http://blog.csdn.net/a623397674a/article/details/38336461
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
[java] view plaincopy print?
  1. //Google  Authenticator
  2. // 只从google出了双重身份验证后,就方便了大家,等同于有了google一个级别的安全,但是我们该怎么使用google authenticator (双重身份验证),
  3. //下面是java的算法,这样大家都可以得到根据key得到公共的秘钥了,直接复制,记得导入JAR包:
  4. //
  5. //commons-codec-1.8.jar
  6. //
  7. //junit-4.10.jar
  8. //测试方法:
  9. //
  10. //1、执行测试代码中的“genSecret”方法,将生成一个KEY(用户为testuser),URL打开是一张二维码图片。
  11. //
  12. //2、在手机中下载“GOOGLE身份验证器”。
  13. //
  14. //3、在身份验证器中配置账户,输入账户名(第一步中的用户testuser)、密钥(第一步生成的KEY),选择基于时间。
  15. //
  16. //4、运行authcode方法将key和要测试的验证码带进去(codes,key),就可以知道是不是正确的秘钥了!返回值布尔
  17. //main我就不写了大家~~因为这个可以当做util工具直接调用就行了
  18. //
  19. package coin.util;
  20. import java.security.InvalidKeyException;
  21. import java.security.NoSuchAlgorithmException;
  22. import java.security.SecureRandom;
  23. import javax.crypto.Mac;
  24. import javax.crypto.spec.SecretKeySpec;
  25. import org.apache.commons.codec.binary.Base32;
  26. import org.apache.commons.codec.binary.Base64;
  27. public class GoogleAuthenticator {
  28. // taken from Google pam docs - we probably don't need to mess with these
  29. public static final int SECRET_SIZE = 10;
  30. public static final String SEED = "g8GjEvTbW5oVSV7avLBdwIHqGlUYNzKFI7izOF8GwLDVKs2m0QN7vxRs2im5MDaNCWGmcD2rvcZx";
  31. public static final String RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";
  32. int window_size = 3; // default 3 - max 17 (from google docs)最多可偏移的时间
  33. public void setWindowSize(int s) {
  34. if (s >= 1 && s <= 17)
  35. window_size = s;
  36. }
  37. public static Boolean authcode(String codes, String savedSecret) {
  38. // enter the code shown on device. Edit this and run it fast before the
  39. // code expires!
  40. long code = Long.parseLong(codes);
  41. long t = System.currentTimeMillis();
  42. GoogleAuthenticator ga = new GoogleAuthenticator();
  43. ga.setWindowSize(15); // should give 5 * 30 seconds of grace...
  44. boolean r = ga.check_code(savedSecret, code, t);
  45. return r;
  46. }
  47. public static String genSecret() {
  48. String secret = GoogleAuthenticator.generateSecretKey();
  49. GoogleAuthenticator.getQRBarcodeURL("testuser",
  50. "testhost", secret);
  51. return secret;
  52. }
  53. public static String generateSecretKey() {
  54. SecureRandom sr = null;
  55. try {
  56. sr = SecureRandom.getInstance(RANDOM_NUMBER_ALGORITHM);
  57. sr.setSeed(Base64.decodeBase64(SEED));
  58. byte[] buffer = sr.generateSeed(SECRET_SIZE);
  59. Base32 codec = new Base32();
  60. byte[] bEncodedKey = codec.encode(buffer);
  61. String encodedKey = new String(bEncodedKey);
  62. return encodedKey;
  63. }catch (NoSuchAlgorithmException e) {
  64. // should never occur... configuration error
  65. }
  66. return null;
  67. }
  68. public static String getQRBarcodeURL(String user, String host, String secret) {
  69. String format = "https://www.google.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl=otpauth://totp/%s@%s%%3Fsecret%%3D%s";
  70. return String.format(format, user, host, secret);
  71. }
  72. public boolean check_code(String secret, long code, long timeMsec) {
  73. Base32 codec = new Base32();
  74. byte[] decodedKey = codec.decode(secret);
  75. // convert unix msec time into a 30 second "window"
  76. // this is per the TOTP spec (see the RFC for details)
  77. long t = (timeMsec / 1000L) / 30L;
  78. // Window is used to check codes generated in the near past.
  79. // You can use this value to tune how far you're willing to go.
  80. for (int i = -window_size; i <= window_size; ++i) {
  81. long hash;
  82. try {
  83. hash = verify_code(decodedKey, t + i);
  84. }catch (Exception e) {
  85. // Yes, this is bad form - but
  86. // the exceptions thrown would be rare and a static configuration problem
  87. e.printStackTrace();
  88. throw new RuntimeException(e.getMessage());
  89. //return false;
  90. }
  91. if (hash == code) {
  92. return true;
  93. }
  94. }
  95. // The validation code is invalid.
  96. return false;
  97. }
  98. private static int verify_code(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
  99. byte[] data = new byte[8];
  100. long value = t;
  101. for (int i = 8; i-- > 0; value >>>= 8) {
  102. data[i] = (byte) value;
  103. }
  104. SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1");
  105. Mac mac = Mac.getInstance("HmacSHA1");
  106. mac.init(signKey);
  107. byte[] hash = mac.doFinal(data);
  108. int offset = hash[20 - 1] & 0xF;
  109. // We're using a long because Java hasn't got unsigned int.
  110. long truncatedHash = 0;
  111. for (int i = 0; i < 4; ++i) {
  112. truncatedHash <<= 8;
  113. // We are dealing with signed bytes:
  114. // we just keep the first byte.
  115. truncatedHash |= (hash[offset + i] & 0xFF);
  116. }
  117. truncatedHash &= 0x7FFFFFFF;
  118. truncatedHash %= 1000000;
  119. return (int) truncatedHash;
  120. }
  121. }
//Google  Authenticator// 只从google出了双重身份验证后,就方便了大家,等同于有了google一个级别的安全,但是我们该怎么使用google authenticator (双重身份验证),//下面是java的算法,这样大家都可以得到根据key得到公共的秘钥了,直接复制,记得导入JAR包:
//
//commons-codec-1.8.jar
//
//junit-4.10.jar//测试方法:
//
//1、执行测试代码中的“genSecret”方法,将生成一个KEY(用户为testuser),URL打开是一张二维码图片。
//
//2、在手机中下载“GOOGLE身份验证器”。
//
//3、在身份验证器中配置账户,输入账户名(第一步中的用户testuser)、密钥(第一步生成的KEY),选择基于时间。
//
//4、运行authcode方法将key和要测试的验证码带进去(codes,key),就可以知道是不是正确的秘钥了!返回值布尔//main我就不写了大家~~因为这个可以当做util工具直接调用就行了
//package coin.util;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 GoogleAuthenticator {// taken from Google pam docs - we probably don't need to mess with thesepublic 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;}public static Boolean authcode(String codes, String savedSecret) {// enter the code shown on device. Edit this and run it fast before the// code expires!long code = Long.parseLong(codes);long t = System.currentTimeMillis();GoogleAuthenticator ga = new GoogleAuthenticator();ga.setWindowSize(15); // should give 5 * 30 seconds of grace...boolean r = ga.check_code(savedSecret, code, t);return r;}public static String genSecret() {String secret = GoogleAuthenticator.generateSecretKey();GoogleAuthenticator.getQRBarcodeURL("testuser","testhost", secret);return secret;}public 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;}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);}public boolean check_code(String secret, long code, long timeMsec) {Base32 codec = new Base32();byte[] decodedKey = codec.decode(secret);// convert unix msec time into a 30 second "window"// this is per the TOTP spec (see the RFC for details)long t = (timeMsec / 1000L) / 30L;// Window is used to check codes generated in the near past.// You can use this value to tune how far you're willing to go.for (int i = -window_size; i <= window_size; ++i) {long hash;try {hash = verify_code(decodedKey, t + i);}catch (Exception e) {// Yes, this is bad form - but// the exceptions thrown would be rare and a static configuration probleme.printStackTrace();throw new RuntimeException(e.getMessage());//return false;}if (hash == code) {return true;}}// The validation code is invalid.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;// We're using a long because Java hasn't got unsigned int.long truncatedHash = 0;for (int i = 0; i < 4; ++i) {truncatedHash <<= 8;// We are dealing with signed bytes:// we just keep the first byte.truncatedHash |= (hash[offset + i] & 0xFF);}truncatedHash &= 0x7FFFFFFF;truncatedHash %= 1000000;return (int) truncatedHash;}
}

工程源码分享:

链接: http://pan.baidu.com/s/1eRUelqi 密码: xz25

Google Authenticator 原理及Java实现相关推荐

  1. 谷歌Google authenticator 整合到JAVA项目

    前言: 最近项目中,需要使用到谷歌的验证码,就采用了这种.....      其实还可以使用reCaptcha来做,不过移动端还是采用authenticator 会方便点, 如果想了解reCaptch ...

  2. google authenticator 工作原理

    Google authenticator 介绍 Google authenticator是一个基于TOTP原理实现的一个生成一次性密码的工具,用来做双因素登录,市面上已经有很多这些比较成熟的东西存在, ...

  3. 谷歌验证 (Google Authenticator) 的实现原理是什么?

    著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 作者:徐小花 链接:http://www.zhihu.com/question/20462696/answer/18731073 ...

  4. Google Authenticator:将其与您自己的Java身份验证服务器配合使用

    用于移动设备的Google Authenticator应用程序是一个非常方便的应用程序,它实现了TOTP算法(在RFC 6238中指定). 使用Google Authenticator,您可以生成时间 ...

  5. google authenticator python_Google Authenticator TOTP原理详解(以Python为例)

    http://xsboke.blog.51cto.com 如果有疑问,请点击此处,然后发表评论交流,作者会及时回复(也可以直接在当前文章评论). -------谢谢您的参考,如有疑问,欢迎交流 一. ...

  6. google authenticator python_谷歌验证器 Google Authenticator工作原理

    很多人都听过谷歌验证 (Google Authenticator) 或用过谷歌验证 (Google Authenticator) .尤其是随着比特币等虚拟货币的兴起,各大交易所都要求绑定谷歌验证 (G ...

  7. google authenticator (双重身份验证器)的java使用

    #google authenticator (双重身份验证器)的java使用 //Google Authenticator // 只从google出了双重身份验证后,就方便了大家,等同于有了googl ...

  8. 详解Google Authenticator工作原理

     详解Google Authenticator工作原理 发表于2014-09-23 08:28| 10060次阅读| 来源CSDN| 16 条评论| 作者伍昆 Google二维码Google Au ...

  9. .Net Core使用google authenticator打造用户登录动态口令

    1.google authenticator(谷歌身份验证器) 介绍 谷歌身份验证器,即Google Authenticator(Google身份验证器)v2.33 谷歌推出的一款动态口令工具,解决大 ...

最新文章

  1. 智能车竞赛开启了新的一个周期,让我聚焦十六届赛题吧
  2. Windows 编程[12] - 菜单与菜单资源(一)
  3. 机器学习(数据挖掘十个重要算法)
  4. java四则运算器算法_java写的四则运算器
  5. iOS 第三方登录之 QQ登录
  6. 项目中的模块剥离成项目_使用MCEBuddy 2从电视录制中剥离广告
  7. 服务器虚拟光驱无法加载,Proxmox/创建PVE/安装windows 2012r2系统无法识别硬盘/如何添加virtio驱动/...
  8. 调用gserverdkey dll的飞鸽传书2012
  9. 滚蛋吧,2020的糟心事儿!2021,先“拼”为敬!
  10. 2021年软考VRRP虚拟路由冗余技术
  11. 诺顿5月17日病毒库更新后误杀系统文件导致系统蓝屏(STOP c000021a Unkown hard error)
  12. 商易淘宝全屏海报代码生成工具 使用说明
  13. 从Kaminario谈谈“三大”存储热点技术
  14. 利用NMDS对药物处理下肠道菌群微生物群落多态性分析
  15. C语言利用switch的简单计算器
  16. 我说CMMI2.0之产品集成
  17. 计算机机房装修效果图,机房装修施工流程是什么? 机房装修效果图
  18. 8421码 2421码 5421码和余3码的使用
  19. Linux 系统 pptpd+radius+mysql 安装攻略
  20. C++求1-20的阶乘之和

热门文章

  1. Android系统之ContentObserver和SettingsProvider结合使用(三)
  2. 2022年上半年信息系统项目管理师上午客观题参考答案及解析(四)
  3. Git、Github和GitLab的区别及与SVN的比较
  4. Dirt4 Cross 游戏改装车辆(一)
  5. python中matplotlib绘图中文显示问题
  6. 【AI每日播报】棋圣聂卫平告负 Master取得第54场胜利
  7. app混合开发之微信分享设置
  8. 计算机开始按钮的功能,Windows7系统开始菜单有哪些新增的功能
  9. Python基础教程 | 第三章 字符串
  10. 《Python数据抓取与实战》读书笔记:第2章