Google Authenticator 算法

Google Authenticator 算法

  • 目录
    • 概述
      • 需求:
    • 设计思路
    • 实现思路分析
      • 1.Shared Secret(共享密钥)
      • 2.2. Input(Current Time)
      • 3. Signing Function(签名函数)
    • 解决的代码:
  • 参考资料和推荐阅读

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)

解决的代码:

	

    com.google.zxing
    core
    3.3.3

	

    commons-codec
    commons-codec
    1.12

 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

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

你可能感兴趣的:(安全方面架构,算法)