From d3c925ca0b405661000315f8683902790f2c9ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=99=93=E9=9B=B7?= Date: Thu, 1 Jul 2021 19:19:59 +0800 Subject: [PATCH] =?UTF-8?q?fix=20https://gitee.com/dromara/hutool/issues/I?= =?UTF-8?q?3YP53=20=20=E4=BF=AE=E5=A4=8D=E9=97=AE=E9=A2=98=20add=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=94=9F=E6=88=90=E5=85=B1=E4=BA=AB=E7=A7=98?= =?UTF-8?q?=E9=92=A5=E7=9A=84=E6=96=B9=E6=B3=95=20add=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=9A=84=E6=96=B9=E6=B3=95=20add=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E7=94=9F=E6=88=90=E8=B0=B7=E6=AD=8C=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E5=99=A8=E6=89=AB=E7=A0=81=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=94=9F?= =?UTF-8?q?=E6=88=90=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/crypto/digest/otp/HOTP.java | 18 +++++++- .../cn/hutool/crypto/digest/otp/TOTP.java | 32 ++++++++++++++ .../cn/hutool/crypto/test/digest/OTPTest.java | 43 +++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 hutool-crypto/src/test/java/cn/hutool/crypto/test/digest/OTPTest.java 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); + } + +}