diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/otp/HOTP.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/otp/HOTP.java index b368b3c46..24e328104 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/otp/HOTP.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/otp/HOTP.java @@ -1,8 +1,12 @@ package cn.hutool.crypto.digest.otp; +import cn.hutool.core.codec.Base32; +import cn.hutool.core.util.RandomUtil; import cn.hutool.crypto.digest.HMac; import cn.hutool.crypto.digest.HmacAlgorithm; +import java.util.Arrays; + /** *
HMAC-based one-time passwords (HOTP) 一次性密码生成器, * 规范见:RFC 4226.
@@ -86,11 +90,21 @@ public class HOTP { this.buffer[6] = (byte) ((counter & 0x000000000000ff00L) >>> 8); this.buffer[7] = (byte) (counter & 0x00000000000000ffL); - final byte[] digest = this.mac.digest(this.buffer); + final byte[] digest = this.mac.digest(Arrays.copyOfRange(this.buffer,0,8)); return truncate(digest); } + /** + * 生成共享密钥 + * + * @param numBytes 将生成的种子字节数量。 + * @return 共享密钥 + */ + public static String generateSecretKey(final int numBytes) { + return Base32.encode(RandomUtil.getSHA1PRNGRandom(RandomUtil.randomBytes(256)).generateSeed(numBytes)); + } + /** * 截断 * @@ -123,4 +137,4 @@ public class HOTP { public String getAlgorithm() { return this.mac.getAlgorithm(); } -} \ No newline at end of file +} diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/otp/TOTP.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/otp/TOTP.java index e091da33d..bd3106a35 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/otp/TOTP.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/otp/TOTP.java @@ -1,5 +1,6 @@ package cn.hutool.crypto.digest.otp; +import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.digest.HmacAlgorithm; import java.time.Duration; @@ -75,6 +76,37 @@ public class TOTP extends HOTP { return this.generate(timestamp.toEpochMilli() / this.timeStep.toMillis()); } + /** + * 用于验证code是否正确 + * @param timestamp 验证时间戳 + * @param offsetSize 误差范围 + * @param code code + * @return 是否通过 + */ + public boolean validate(Instant timestamp, final int offsetSize, final int code) { + if(offsetSize == 0){ + return generate(timestamp) == code; + } + for (int i = -offsetSize; i <= offsetSize; i++) { + if(generate(timestamp.plus(getTimeStep().multipliedBy(i))) == code){ + return true; + } + } + return false; + } + + /** + * 生成谷歌认证器的字符串(扫码字符串) + * 基于时间的,计数器不适合 + * + * @param account 账户名。 + * @param numBytes 将生成的种子字节数量。 + * @return 共享密钥 + */ + public static String generateGoogleSecretKey(final String account,final int numBytes) { + return StrUtil.format("otpauth://totp/{}?secret={}",account,generateSecretKey(numBytes)); + } + /** * 获取步进 * diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/OTPTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/OTPTest.java new file mode 100644 index 000000000..2ed9537f4 --- /dev/null +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/OTPTest.java @@ -0,0 +1,43 @@ +package cn.hutool.crypto.test.digest; + +import cn.hutool.core.codec.Base32; +import cn.hutool.crypto.digest.otp.TOTP; +import org.junit.Assert; +import org.junit.Test; + +import java.time.Instant; + +/** + * @author: xlgogo@outlook.com + * @date: 2021-07-01 18:14 + * @description: + */ +public class OTPTest { + @Test + public void genKey(){ + String key = TOTP.generateSecretKey(8); + System.out.println(key); + } + + @Test + public void valid(){ + String key = "VYCFSW2QZ3WZO"; + // 2021/7/1下午6:29:54 显示code为 106659 + //Assert.assertEquals(new TOTP(Base32.decode(key)).generate(Instant.ofEpochSecond(1625135394L)),106659); + TOTP totp = new TOTP(Base32.decode(key)); + Instant instant = Instant.ofEpochSecond(1625135394L); + Assert.assertTrue(totp.validate(instant,0,106659)); + Assert.assertTrue(totp.validate(instant.plusSeconds(30),1,106659)); + Assert.assertTrue(totp.validate(instant.plusSeconds(60),2,106659)); + + Assert.assertFalse(totp.validate(instant.plusSeconds(60),1,106659)); + Assert.assertFalse(totp.validate(instant.plusSeconds(90),2,106659)); + } + + @Test + public void googleAuthTest(){ + String str = TOTP.generateGoogleSecretKey("xl7@qq.com", 10); + System.out.println(str); + } + +} diff --git a/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentParser.java b/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentParser.java index d4e10db4d..6a083741a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentParser.java +++ b/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentParser.java @@ -24,7 +24,7 @@ public class UserAgentParser { // 浏览器 final Browser browser = parseBrowser(userAgentString); - userAgent.setBrowser(parseBrowser(userAgentString)); + userAgent.setBrowser(browser); userAgent.setVersion(browser.getVersion(userAgentString)); // 浏览器引擎