您的位置:首页 > 编程语言 > PHP开发

使用aerogear生成totp

2017-12-14 00:00 471 查看

本文主要讲述一下如何使用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);

7fe0
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

身份宝

google-authenticator

Java 接入 Google Authenticator

使用OTP动态口令(每分钟变一次)进行登录认证

GoogleAuth

aerogear-otp-java
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  OTP