Google Authenticator 算法

Survive by day and develop by night.
talk for import biz , show your perfect code,full busy,skip hardness,make a better result,wait for change,challenge Survive.
happy for hardess to solve denpendies.

目录

概述

此项服务所使用的算法已列于RFC 6238和RFC 4226中。谷歌验证器上的动态密码按照时间或使用次数不断动态变化(默认30秒变更一次)。

需求:

设计思路

此项服务所使用的算法已列于RFC 6238和RFC 4226中。谷歌验证器上的动态密码按照时间或使用次数不断动态变化(默认30秒变更一次)。
RFC 6238 is a standard that specifies the Time-Based One-Time Password (TOTP) algorithm, which is used for two-factor authentication. The TOTP algorithm is based on a shared secret between the user and the service provider, and a time-based counter that generates a unique, one-time password that is valid for a short period of time. This mechanism provides an additional layer of security to protect against unauthorized access to sensitive data or systems. The RFC outlines the requirements and specifications for implementing TOTP, including the format of the shared secret, the algorithm for generating the one-time password, and the synchronization of the system’s clock. The standard has been widely adopted by service providers and is used in many popular two-factor authentication schemes.

实现思路分析

1.Shared Secret(共享密钥)

用户开启双因子(2FA)验证后,服务器会生成一个密钥(secret);
服务器提示用户扫描二维码或者手动输入的方式,将密钥保存在用户的手机上。此时,用户和服务器现在都拥有同一把钥匙。
用户登录时,手机客户端Authy使用这个密钥和当前的Unix时间戳,生成一个hash value(h1),有效期默认为30s。用户在有效期内,将这个哈希值提交给服务器;
服务器也使用密钥和当前时间戳,生成一个hash value(h2),将h2和用户提交的h1进行比较,如果两者一致,就能够正常登陆,否则,拒绝登陆。

2.2. Input(Current Time)

因为从算法原理来讲,身份验证服务器会基于同样的时间来重复进行用户手机的运算。进一步来说,服务器会计算当前时间前后几分钟内的令牌,跟用户提交的令牌比较。所以如果时间上相差太多,身份验证过程就会失败。

3. Signing Function(签名函数)

签名函数使用的是HMAC-SHA1。HMAC是基于哈希的消息验证码,能够用安全的单向哈希函数(SHA1)来生成签名。
验证算法的原理:只有共享密钥拥有者和服务器才能根据同样的输入(基于时间的)得到同样的输出签名。
hmac = SHA1(secret + SHA1(secret + input))

  1. 首先,使用base32的解码密钥
    secret = BASE32_DECODE(TO_UPPERCASE(REMOVE_SPACES(original_secret)))

input = CURRENT_UNIX_TIME()
input = CURRENT_UNIX_TIME() / 30
original_secret = xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
secret = BASE32_DECODE(TO_UPPERCASE(REMOVE_SPACES(original_secret)))
input = CURRENT_UNIX_TIME() / 30
hmac = SHA1(secret + SHA1(secret + input))

三、具体实现(Java)

解决的代码:

	<!--二维码的生成工具类-->
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.3.3</version>
</dependency>
	<!--谷歌身份验证器-->
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.12</version>
</dependency>
 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;
 
 
  /**
   * @Description: 谷歌身份验证器工具类
   */
  public class GoogleAuthenticator {
 
      /**
       * 生成秘钥的长度
       */
      public static final int SECRET_SIZE = 10;
 
      public static final String SEED = "g8GjEvTbW5oVSV7avL47357438reyhreyuryetredLDVKs2m0QN7vxRs2im5MDaNCWGmcD2rvcZx";
      /**
       * 实现随机数算法
       */
      public static final String RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";
 
      /**
       * 时间前后偏移量
       * 用于防止客户端时间不精确导致生成的TOTP与服务器端的TOTP一直不一致
       * 如果为0,当前时间为 10:10:15
       * 则表明在 10:10:00-10:10:30 之间生成的TOTP 能校验通过
       * 如果为1,则表明在
       * 10:09:30-10:10:00
       * 10:10:00-10:10:30
       * 10:10:30-10:11:00 之间生成的TOTP 能校验通过
       * 以此类推
       */
        int window_size = 10; // default 3 - max 17
 
      /**
       * set the windows size. This is an integer value representing the number of
       * 30 second windows we allow The bigger the window, the more tolerant of
       * clock skew we are.
       *
       * @param s
       *            window size - must be >=1 and <=17. Other values are ignored
       */
      public void setWindowSize(int s) {
          if (s >= 1 && s <= 17)
              window_size = s;
      }
 
      /**
       * 生成随机密钥,每个用户独享一份密钥
       * @return secret key
       */
      public static String generateSecretKey() {
          SecureRandom sr = null;
          try {
              sr = SecureRandom.getInstance(RANDOM_NUMBER_ALGORITHM);
              sr.setSeed(Base64.decodeBase64(SEED.getBytes()));
              byte[] buffer = sr.generateSeed(SECRET_SIZE);
              Base32 codec = new Base32();
              byte[] bEncodedKey = codec.encode(buffer);
              return new String(bEncodedKey);
          } catch (NoSuchAlgorithmException e) {
              // should never occur... configuration error
          }
          return null;
      }
 
 
      /**
       * 生成一个google身份验证器,识别的字符串,只需要把该方法返回值生成二维码扫描就可以了。
       * 最后展示的账户名称将会是 label:user
       * @param label 标签
       * @param user 账号
       * @param secret 密钥
       * @return
       */
      public static String getQRBarcode(String label, String user, String secret) {
          String format = "otpauth://totp/%s:%s?secret=%s";
          return String.format(format, label, user, secret);
      }
 
      /**
       * 生成一个google身份验证器,识别的字符串,只需要把该方法返回值生成二维码扫描就可以了。
       *最后展示的账户名称将会是 user
       * @param user 账号
       * @param secret 密钥
       * @return
       */
      public static String getQRBarcode(String user, String secret) {
          String format = "otpauth://totp/%s?secret=%s";
          return String.format(format, user, secret);
      }
 
      /**
       * 验证code是否合法
       * @param secret 秘钥
       * @param code 验证码
       * @param timeMses 时间戳
       * @return true表示正确 false 表示错误
       */
      public  boolean check_code(String secret, long code, long timeMses) {
          if(secret == null || "".equals(secret))
          {
              return false;
          }
          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 = (timeMses / 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 problem
                  e.printStackTrace();
                  throw new RuntimeException(e.getMessage());
                  // return false;
              }
              if (hash == code) {
                  return true;
              }
          }
          // The validation code is invalid.
          return false;
      }
 
      /**
       * 根据时间偏移量计算
       *
       * @param key
       * @param t
       * @return
       * @throws NoSuchAlgorithmException
       * @throws InvalidKeyException
       */
      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;
      }
  }

参考资料和推荐阅读

参考资料
官方文档
开源社区
博客文章
书籍推荐

  1. https://www.cnblogs.com/Fogram-c/p/16978939.html

欢迎阅读,各位老铁,如果对你有帮助,点个赞加个关注呗!同时,期望各位大佬的批评指正~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
对于Google Authenticator(谷歌验证)的密码输入,实际上并没有一个固定的密码。Google Authenticator通过基于时间的一次性密码(TOTP)生成密码来进行账户验证。这种密码是根据事先与服务器约定好的密钥和当前的时间戳来生成的。当用户登录时,他们需要在Google Authenticator应用程序中输入由此算法生成的一次性密码,并将其发送到服务器进行验证。如果生成的一次性密码与服务器计算的密码一致,登录将成功。这种基于时间的算法确保了每个一次性密码只有在短时间内有效,提高了账户的安全性。所以实际上,你需要根据服务器提供的密钥和当前的时间戳,在Google Authenticator应用程序中生成对应的一次性密码,并输入该密码进行验证。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [谷歌验证器 Google Authenticator工作原理](https://blog.csdn.net/weixin_39732991/article/details/110784762)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [谷歌Google authenticator 整合到JAVA项目](https://blog.csdn.net/baidu_38990811/article/details/106002098)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

执于代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值