From ea96eecd4b80aa3466911f19375d221cd153711c Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 30 Apr 2020 09:11:22 +0800 Subject: [PATCH] add opt --- CHANGELOG.md | 2 + .../hutool/bloomfilter/BitMapBloomFilter.java | 25 ++-- .../main/java/cn/hutool/core/io/IoUtil.java | 13 ++ .../java/cn/hutool/crypto/digest/HMac.java | 28 +++- .../crypto/digest/mac/BCHMacEngine.java | 18 ++- .../crypto/digest/mac/DefaultHMacEngine.java | 28 ++-- .../hutool/crypto/digest/mac/MacEngine.java | 18 ++- .../crypto/digest/mac/MacEngineFactory.java | 6 +- .../cn/hutool/crypto/digest/opt/HOTP.java | 124 ++++++++++++++++++ .../crypto/digest/opt/package-info.java | 14 ++ 10 files changed, 241 insertions(+), 35 deletions(-) create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/package-info.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 970001c6b..fd91bdd0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ * 【extra 】 增加PinyinUtil,封装TinyPinyin * 【extra 】 Ftp和Sftp增加FtpConfig,提供超时等更多可选参数 * 【extra 】 SpringUtil增加getActiveProfiles、getBeansOfType、getBeanNamesForType方法(issue#I1FXF3@Gitee) +* 【bloomFilter】 避免布隆过滤器数字溢出(pr#119@Gitee) +* 【core 】 增加IoUtil.writeObj(issue#I1FZIE) ### Bug修复 * 【core 】 修复URLBuilder中请求参数有`&`导致的问题(issue#850@Github) diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java index a4cc97d67..b1da492d6 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java @@ -13,35 +13,36 @@ import cn.hutool.core.util.NumberUtil; * 2.散列hash映射到数组的bit位置
* 3.验证
* 此实现方式可以指定Hash算法 - * + * * @author Ansj */ -public class BitMapBloomFilter implements BloomFilter{ +public class BitMapBloomFilter implements BloomFilter { private static final long serialVersionUID = 1L; private BloomFilter[] filters; /** * 构造,使用默认的5个过滤器 + * * @param m M值决定BitMap的大小 */ public BitMapBloomFilter(int m) { - long mNum =NumberUtil.div(String.valueOf(m), String.valueOf(5)).longValue(); + long mNum = NumberUtil.div(String.valueOf(m), String.valueOf(5)).longValue(); long size = mNum * 1024 * 1024 * 8; - + filters = new BloomFilter[]{ - new DefaultFilter(size), - new ELFFilter(size), - new JSFilter(size), - new PJWFilter(size), - new SDBMFilter(size) + new DefaultFilter(size), + new ELFFilter(size), + new JSFilter(size), + new PJWFilter(size), + new SDBMFilter(size) }; } /** * 使用自定的多个过滤器建立BloomFilter - * - * @param m M值决定BitMap的大小 + * + * @param m M值决定BitMap的大小 * @param filters Bloom过滤器列表 */ public BitMapBloomFilter(int m, BloomFilter... filters) { @@ -51,6 +52,7 @@ public class BitMapBloomFilter implements BloomFilter{ /** * 增加字符串到Filter映射中 + * * @param str 字符串 */ @Override @@ -64,6 +66,7 @@ public class BitMapBloomFilter implements BloomFilter{ /** * 是否可能包含此字符串,此处存在误判 + * * @param str 字符串 * @return 是否存在 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index 0966d544a..cda8b5efe 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -984,6 +984,19 @@ public class IoUtil { } } + /** + * 将多部分内容写到流中 + * + * @param out 输出流 + * @param isCloseOut 写入完毕是否关闭输出流 + * @param obj 写入的对象内容 + * @throws IORuntimeException IO异常 + * @since 5.3.3 + */ + public static void writeObj(OutputStream out, boolean isCloseOut, Serializable obj) throws IORuntimeException { + writeObjects(out, isCloseOut, obj); + } + /** * 将多部分内容写到流中 * diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java index 4daa0f7ab..363c5057a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java @@ -9,12 +9,12 @@ import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.digest.mac.MacEngine; import cn.hutool.crypto.digest.mac.MacEngineFactory; -import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.security.Key; /** * HMAC摘要算法
@@ -37,7 +37,7 @@ public class HMac implements Serializable { * @param algorithm 算法 {@link HmacAlgorithm} */ public HMac(HmacAlgorithm algorithm) { - this(algorithm, (SecretKey)null); + this(algorithm, (Key)null); } /** @@ -54,7 +54,7 @@ public class HMac implements Serializable { * @param algorithm 算法 {@link HmacAlgorithm} * @param key 密钥 */ - public HMac(HmacAlgorithm algorithm, SecretKey key) { + public HMac(HmacAlgorithm algorithm, Key key) { this(algorithm.getValue(), key); } @@ -74,7 +74,7 @@ public class HMac implements Serializable { * @param key 密钥 * @since 4.5.13 */ - public HMac(String algorithm, SecretKey key) { + public HMac(String algorithm, Key key) { this(MacEngineFactory.createEngine(algorithm, key)); } @@ -223,5 +223,23 @@ public class HMac implements Serializable { public String digestHex(InputStream data, int bufferLength) { return HexUtil.encodeHexStr(digest(data, bufferLength)); } - + + /** + * 获取MAC算法块长度 + * @return MAC算法块长度 + * @since 5.3.3 + */ + public int getMacLength(){ + return this.engine.getMacLength(); + } + + /** + * 获取算法 + * + * @return 算法 + * @since 5.3.3 + */ + public String getAlgorithm() { + return this.engine.getAlgorithm(); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java index e0db7ee49..9f61e5c0a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java @@ -1,16 +1,15 @@ package cn.hutool.crypto.digest.mac; -import java.io.IOException; -import java.io.InputStream; - +import cn.hutool.core.io.IoUtil; +import cn.hutool.crypto.CryptoException; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Mac; import org.bouncycastle.crypto.macs.HMac; import org.bouncycastle.crypto.params.KeyParameter; -import cn.hutool.core.io.IoUtil; -import cn.hutool.crypto.CryptoException; +import java.io.IOException; +import java.io.InputStream; /** * BouncyCastle的HMAC算法实现引擎,使用{@link Mac} 实现摘要
@@ -94,4 +93,13 @@ public class BCHMacEngine implements MacEngine { return mac; } + @Override + public int getMacLength() { + return mac.getMacSize(); + } + + @Override + public String getAlgorithm() { + return this.mac.getAlgorithmName(); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/DefaultHMacEngine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/DefaultHMacEngine.java index 6136347e4..2ea6aef98 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/DefaultHMacEngine.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/DefaultHMacEngine.java @@ -1,16 +1,16 @@ package cn.hutool.crypto.digest.mac; -import java.io.IOException; -import java.io.InputStream; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - import cn.hutool.core.io.IoUtil; import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.SecureUtil; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.io.InputStream; +import java.security.Key; + /** * 默认的HMAC算法实现引擎,使用{@link Mac} 实现摘要
* 当引入BouncyCastle库时自动使用其作为Provider @@ -39,7 +39,7 @@ public class DefaultHMacEngine implements MacEngine { * @param key 密钥 * @since 4.5.13 */ - public DefaultHMacEngine(String algorithm, SecretKey key) { + public DefaultHMacEngine(String algorithm, Key key) { init(algorithm, key); } // ------------------------------------------------------------------------------------------- Constructor end @@ -61,7 +61,7 @@ public class DefaultHMacEngine implements MacEngine { * @return this * @throws CryptoException Cause by IOException */ - public DefaultHMacEngine init(String algorithm, SecretKey key){ + public DefaultHMacEngine init(String algorithm, Key key){ try { mac = SecureUtil.createMac(algorithm); if(null == key){ @@ -106,4 +106,14 @@ public class DefaultHMacEngine implements MacEngine { public Mac getMac() { return mac; } + + @Override + public int getMacLength() { + return mac.getMacLength(); + } + + @Override + public String getAlgorithm() { + return this.mac.getAlgorithm(); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngine.java index 85fb8ec95..04d4d7f42 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngine.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngine.java @@ -1,9 +1,9 @@ package cn.hutool.crypto.digest.mac; -import java.io.InputStream; - import cn.hutool.core.io.IoUtil; +import java.io.InputStream; + /** * MAC(Message Authentication Code)算法引擎 * @@ -20,4 +20,18 @@ public interface MacEngine { * @return 摘要bytes */ byte[] digest(InputStream data, int bufferLength); + + /** + * 获取MAC算法块大小 + * + * @return MAC算法块大小 + */ + int getMacLength(); + + /** + * 获取当前算法 + * + * @return 算法 + */ + String getAlgorithm(); } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngineFactory.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngineFactory.java index 9288b6e72..49d0516dd 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngineFactory.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngineFactory.java @@ -1,10 +1,10 @@ package cn.hutool.crypto.digest.mac; -import javax.crypto.SecretKey; - import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.digest.HmacAlgorithm; +import java.security.Key; + /** * {@link MacEngine} 实现工厂类 * @@ -19,7 +19,7 @@ public class MacEngineFactory { * @param key 密钥 * @return {@link MacEngine} */ - public static MacEngine createEngine(String algorithm, SecretKey key) { + public static MacEngine createEngine(String algorithm, Key key) { if(algorithm.equalsIgnoreCase(HmacAlgorithm.HmacSM3.getValue())) { // HmacSM3算法是BC库实现的 return SmUtil.createHmacSm3Engine(key.getEncoded()); diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java new file mode 100644 index 000000000..fe69e2006 --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java @@ -0,0 +1,124 @@ +package cn.hutool.crypto.digest.opt; + +import cn.hutool.crypto.digest.HMac; +import cn.hutool.crypto.digest.HmacAlgorithm; + +/** + *

HMAC-based one-time passwords (HOTP) 一次性密码生成器, + * 规范见:RFC 4226.

+ * + *

参考:https://github.com/jchambers/java-otp

+ * + * @author Looly + */ +public class HOTP { + + /** + * 数子量级 + */ + private static final int[] MOD_DIVISORS = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000}; + /** + * 默认密码长度. + */ + public static final int DEFAULT_PASSWORD_LENGTH = 6; + + /** + * 默认HMAC算法. + */ + public static final HmacAlgorithm HOTP_HMAC_ALGORITHM = HmacAlgorithm.HmacSHA1; + + private final HMac mac; + private final int passwordLength; + private final int modDivisor; + + private final byte[] buffer; + + /** + * 构造,使用默认密码长度和默认HMAC算法 + */ + public HOTP(byte[] key) { + this(DEFAULT_PASSWORD_LENGTH, key); + } + + /** + * 构造,使用默认HMAC算法 + * + * @param passwordLength 密码长度,可以是6,7,8 + * @param key 共享密码,RFC 4226要求最少128位 + */ + public HOTP(int passwordLength, byte[] key) { + this(passwordLength, HOTP_HMAC_ALGORITHM, key); + } + + /** + * 构造 + * + * @param passwordLength 密码长度,可以是6,7,8 + * @param algorithm HMAC算法枚举 + * @param key 共享密码,RFC 4226要求最少128位 + */ + public HOTP(int passwordLength, HmacAlgorithm algorithm, byte[] key) { + this.mac = new HMac(algorithm, key); + + this.modDivisor = MOD_DIVISORS[passwordLength]; + this.passwordLength = passwordLength; + this.buffer = new byte[this.mac.getMacLength()]; + } + + /** + * 生成一次性密码 + * + * @param counter 事件计数的值,8 字节的整数,称为移动因子(moving factor), + * 可以是基于计次的动移动因子,也可以是计时移动因子 + * @return 一次性密码的int值 + */ + public synchronized int generateOneTimePassword(final long counter) { + // C 的整数值需要用二进制的字符串表达,比如某个事件计数为 3, + // 则C是 "11"(此处省略了前面的二进制的数字0) + this.buffer[0] = (byte) ((counter & 0xff00000000000000L) >>> 56); + this.buffer[1] = (byte) ((counter & 0x00ff000000000000L) >>> 48); + this.buffer[2] = (byte) ((counter & 0x0000ff0000000000L) >>> 40); + this.buffer[3] = (byte) ((counter & 0x000000ff00000000L) >>> 32); + this.buffer[4] = (byte) ((counter & 0x00000000ff000000L) >>> 24); + this.buffer[5] = (byte) ((counter & 0x0000000000ff0000L) >>> 16); + this.buffer[6] = (byte) ((counter & 0x000000000000ff00L) >>> 8); + this.buffer[7] = (byte) (counter & 0x00000000000000ffL); + + final byte[] digest = this.mac.digest(this.buffer); + + return truncate(digest); + } + + /** + * 截断 + * + * @param digest HMAC的hash值 + * @return 截断值 + */ + private int truncate(byte[] digest) { + final int offset = digest[digest.length - 1] & 0x0f; + return ((digest[offset] & 0x7f) << 24 | + (digest[offset + 1] & 0xff) << 16 | + (digest[offset + 2] & 0xff) << 8 | + (digest[offset + 3] & 0xff)) % + this.modDivisor; + } + + /** + * 获取密码长度,可以是6,7,8 + * + * @return 密码长度,可以是6,7,8 + */ + public int getPasswordLength() { + return this.passwordLength; + } + + /** + * 获取HMAC算法 + * + * @return HMAC算法 + */ + 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/opt/package-info.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/package-info.java new file mode 100644 index 000000000..e496dd688 --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/package-info.java @@ -0,0 +1,14 @@ +/** + * OTP 是 One-Time Password的简写,表示一次性密码。 + *

+ * 计算OTP串的公式: + *

+ * OTP(K,C) = Truncate(HMAC-SHA-1(K,C))
+ * K:表示秘钥串
+ * C:是一个数字,表示随机数
+ * Truncate:是一个函数,就是怎么截取加密后的串,并取加密后串的哪些字段组成一个数字。
+ * 
+ * + * @author looly + */ +package cn.hutool.crypto.digest.opt; \ No newline at end of file