使用aerogear生成totp


声明:本文转载自https://my.oschina.net/go4it/blog/1589757,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

本文主要讲述一下如何使用aerogear-otp生成otp,以及相关源码分析

otp分类

全称是one time password,通常用来支持双因素认证。主要可以分两类

  • HMAC-Based One-time Password (HOTP) 在RFC 4226规范中

  • Time-based One-time Password (TOTP) 在RFC 6238规范中

>这里主要讲TOTP

  • 客户端 其常见的手机客户端有Google Authenticator APP以及阿里云的身份宝。由于google的软件在国内被墙,因此可以使用阿里云的身份宝

  • 服务端 服务端的话,google官方有c的代码,java的话很多第三方都有实现,这里选择jboss提供的aerogear-otp-java,其maven如下

		<dependency> 			<groupId>org.jboss.aerogear</groupId> 			<artifactId>aerogear-otp-java</artifactId> 			<version>1.0.0</version> 		</dependency> 

步骤

主要的步骤如下:

绑定密钥

  • 服务端为每个账户生成一个secret并保存下来
  • 服务端提供该密钥的二维码扫描功能,方便客户端扫描绑定账号
  • 用户手机安装Google Authenticator APP或阿里云的身份宝,扫描二维码绑定该账号的secret

使用otp验证

绑定secret之后,就可以使用one time password进行验证了。

实例

生成客户端密钥的二维码

String secret = Base32.random(); Totp totp = new Totp(secret); String uri = totp.uri(account); 

>将这个uri作为二维码的信息,即可。

	/**      * Prover - To be used only on the client side      * Retrieves the encoded URI to generated the QRCode required by Google Authenticator      *      * @param name Account name      * @return Encoded URI      */     public String uri(String name) {         try {             return String.format("otpauth://totp/%s?secret=%s", URLEncoder.encode(name, "UTF-8"), secret);         } catch (UnsupportedEncodingException e) {             throw new IllegalArgumentException(e.getMessage(), e);         }     } 

>它的格式otpauth://totp/%s?secret=%s,Google Authenticator APP或阿里云的身份宝均支持这种格式的识别。

校验

 boolean isValid = totp.verify(code); 

其源码如下

	/**      * Verifier - To be used only on the server side      * <p/>      * Taken from Google Authenticator with small modifications from      * {@see <a href="http://code.google.com/p/google-authenticator/source/browse/src/com/google/android/apps/authenticator/PasscodeGenerator.java?repo=android#212">PasscodeGenerator.java</a>}      * <p/>      * Verify a timeout code. The timeout code will be valid for a time      * determined by the interval period and the number of adjacent intervals      * checked.      *      * @param otp Timeout code      * @return True if the timeout code is valid      *         <p/>      *         Author: sweis@google.com (Steve Weis)      */     public boolean verify(String otp) {          long code = Long.parseLong(otp);         long currentInterval = clock.getCurrentInterval();          int pastResponse = Math.max(DELAY_WINDOW, 0);          for (int i = pastResponse; i >= 0; --i) {             int candidate = generate(this.secret, currentInterval - i);             if (candidate == code) {                 return true;             }         }         return false;     } 

>这里有个DELAY_WINDOW参数,是为了防止手机客户端与服务器端的时差引入的。默认值是1,即允许那个code在手机端过期30秒之内到服务端验证还有效。

  • clock aerogear-otp-java-1.0.0-sources.jar!/org/jboss/aerogear/security/otp/api/Clock.java
public class Clock {      private final int interval;     private Calendar calendar;      public Clock() {         interval = 30;     }      public Clock(int interval) {         this.interval = interval;     }      public long getCurrentInterval() {         calendar = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));         long currentTimeSeconds = calendar.getTimeInMillis() / 1000;         return currentTimeSeconds / interval;     } } 

>这个interval默认是30,当然你也可以改为1分钟那就是60. >另外这里先把毫秒转为秒,然后再除去interval,由于是使用/,因此是直接取整数部分,因而上面的DELAY_WINDOW的值=N,其实相当于允许过去的N个interval的code还能校验成功

code生成源码

aerogear-otp-java-1.0.0-sources.jar!/org/jboss/aerogear/security/otp/Totp.java

	private int generate(String secret, long interval) {         return hash(secret, interval);     }      private int hash(String secret, long interval) {         byte[] hash = new byte[0];         try {             //Base32 encoding is just a requirement for google authenticator. We can remove it on the next releases.             hash = new Hmac(Hash.SHA1, Base32.decode(secret), interval).digest();         } catch (NoSuchAlgorithmException e) {             e.printStackTrace();         } catch (InvalidKeyException e) {             e.printStackTrace();         } catch (Base32.DecodingException e) {             e.printStackTrace();         }         return bytesToInt(hash);     }      private int bytesToInt(byte[] hash) {         // put selected bytes into result int         int offset = hash[hash.length - 1] & 0xf;          int binary = ((hash[offset] & 0x7f) << 24) |                 ((hash[offset + 1] & 0xff) << 16) |                 ((hash[offset + 2] & 0xff) << 8) |                 (hash[offset + 3] & 0xff);          return binary % Digits.SIX.getValue();     }      /**      * Retrieves the current OTP      *      * @return OTP      */     public String now() {         return leftPadding(hash(secret, clock.getCurrentInterval()));     }      private String leftPadding(int otp) {         return String.format("%06d", otp);     } 

小结

  • interval 默认值为30,在Clock里头可以通过构造器修改interval。不过由于Google Authenticator APP或阿里云的身份宝均为30秒更换一次,因此这个参数可以按默认的来。

  • DELAY_WINDOW aerogear-otp-java本身不提供DELAY_WINDOW的修改,不过你可以继承Totp,自己扩展一下。

doc

本文发表于2017年12月14日 14:34
(c)注:本文转载自https://my.oschina.net/go4it/blog/1589757,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2453 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1