Looly 5 years ago
parent
commit
ea96eecd4b

+ 2 - 0
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)

+ 14 - 11
hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java

@@ -13,35 +13,36 @@ import cn.hutool.core.util.NumberUtil;
  * 2.散列hash映射到数组的bit位置 <br>
  * 3.验证<br>
  * 此实现方式可以指定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 是否存在
 	 */

+ 13 - 0
hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java

@@ -989,6 +989,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);
+	}
+
+	/**
+	 * 将多部分内容写到流中
+	 *
+	 * @param out        输出流
+	 * @param isCloseOut 写入完毕是否关闭输出流
 	 * @param contents   写入的内容
 	 * @throws IORuntimeException IO异常
 	 */

+ 23 - 5
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摘要算法<br>
@@ -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();
+	}
 }

+ 13 - 5
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} 实现摘要<br>
@@ -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();
+	}
 }

+ 18 - 8
hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/DefaultHMacEngine.java

@@ -1,15 +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 cn.hutool.crypto.SecureUtil;
 
 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 java.io.IOException;
+import java.io.InputStream;
+import java.security.Key;
 
 /**
  * 默认的HMAC算法实现引擎,使用{@link Mac} 实现摘要<br>
@@ -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();
+	}
 }

+ 16 - 2
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();
 }

+ 3 - 3
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());

+ 124 - 0
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;
+
+/**
+ * <p>HMAC-based one-time passwords (HOTP) 一次性密码生成器,
+ * 规范见:<a href="https://tools.ietf.org/html/rfc4226">RFC&nbsp;4226</a>.</p>
+ *
+ * <p>参考:https://github.com/jchambers/java-otp</p>
+ *
+ * @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();
+	}
+}

+ 14 - 0
hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/package-info.java

@@ -0,0 +1,14 @@
+/**
+ * OTP 是 One-Time Password的简写,表示一次性密码。
+ * <p>
+ * 计算OTP串的公式:
+ * <pre>
+ * OTP(K,C) = Truncate(HMAC-SHA-1(K,C))
+ * K:表示秘钥串
+ * C:是一个数字,表示随机数
+ * Truncate:是一个函数,就是怎么截取加密后的串,并取加密后串的哪些字段组成一个数字。
+ * </pre>
+ *
+ * @author looly
+ */
+package cn.hutool.crypto.digest.opt;