diff --git a/CHANGELOG.md b/CHANGELOG.md index b69334025..ef7905c8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ # 5.7.0 (2021-06-10) ### 🐣新特性 +* 【jwt 】 添加JWT模块,实现了JWT的创建、解析和验证 * 【crypto 】 SymmetricCrypto增加update方法(pr#1642@Github) +* 【crypto 】 MacEngine增加接口update,doFinal,reset等接口 ### 🐞Bug修复 diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index 422e868cc..2533be44b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java @@ -54,6 +54,15 @@ public class StrUtilTest { Assert.assertEquals(2, strings.length); } + @Test + public void splitTest2() { + String str = "a.b."; + List split = StrUtil.split(str, '.'); + Assert.assertEquals(3, split.size()); + Assert.assertEquals("b", split.get(1)); + Assert.assertEquals("", split.get(2)); + } + @Test public void splitToLongTest() { String str = "1,2,3,4, 5"; diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index cd3a712e8..4580d1dbb 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -1,7 +1,7 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 jar @@ -27,12 +27,12 @@ hutool-core ${project.parent.version} - - org.bouncycastle - bcprov-jdk15to18 - ${bouncycastle.version} - compile - true - + + org.bouncycastle + bcprov-jdk15to18 + ${bouncycastle.version} + compile + true + diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 3d97f1fc1..b9df3e891 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -367,6 +367,7 @@ public class KeyUtil { // ECIES算法对KEY的长度有要求,此处默认256 keySize = 256; } + return generateKeyPair(algorithm, keySize); } @@ -622,6 +623,7 @@ public class KeyUtil { * @since 4.5.2 */ public static String getMainAlgorithm(String algorithm) { + Assert.notBlank(algorithm, "Algorithm must be not blank!"); final int slashIndex = algorithm.indexOf(CharUtil.SLASH); if (slashIndex > 0) { return algorithm.substring(0, slashIndex); diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/SecureUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/SecureUtil.java index df25715c2..7e5916741 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/SecureUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/SecureUtil.java @@ -1072,6 +1072,26 @@ public class SecureUtil { return mac; } + /** + * 创建{@link Signature} + * + * @param algorithm 算法 + * @return {@link Signature} + * @since 5.7.0 + */ + public static Signature createSignature(String algorithm) { + final Provider provider = GlobalBouncyCastleProvider.INSTANCE.getProvider(); + + Signature signature; + try { + signature = (null == provider) ? Signature.getInstance(algorithm) : Signature.getInstance(algorithm, provider); + } catch (NoSuchAlgorithmException e) { + throw new CryptoException(e); + } + + return signature; + } + /** * RC4算法 * diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/Sign.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/Sign.java index 22a7d0c11..8888b3756 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/Sign.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/Sign.java @@ -1,8 +1,19 @@ package cn.hutool.crypto.asymmetric; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.CryptoException; +import cn.hutool.crypto.SecureUtil; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.Charset; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; @@ -11,14 +22,9 @@ import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; import java.util.Set; -import cn.hutool.core.codec.Base64; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.crypto.CryptoException; -import cn.hutool.crypto.SecureUtil; - /** * 签名包装,{@link Signature} 包装类 - * + * * @author looly * @since 3.3.0 */ @@ -30,7 +36,7 @@ public class Sign extends BaseAsymmetric { // ------------------------------------------------------------------ Constructor start /** * 构造,创建新的私钥公钥对 - * + * * @param algorithm {@link SignAlgorithm} */ public Sign(SignAlgorithm algorithm) { @@ -39,7 +45,7 @@ public class Sign extends BaseAsymmetric { /** * 构造,创建新的私钥公钥对 - * + * * @param algorithm 算法 */ public Sign(String algorithm) { @@ -49,7 +55,7 @@ public class Sign extends BaseAsymmetric { /** * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做签名或验证 - * + * * @param algorithm {@link SignAlgorithm} * @param privateKeyStr 私钥Hex或Base64表示 * @param publicKeyStr 公钥Hex或Base64表示 @@ -61,7 +67,7 @@ public class Sign extends BaseAsymmetric { /** * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做签名或验证 - * + * * @param algorithm {@link SignAlgorithm} * @param privateKey 私钥 * @param publicKey 公钥 @@ -69,11 +75,11 @@ public class Sign extends BaseAsymmetric { public Sign(SignAlgorithm algorithm, byte[] privateKey, byte[] publicKey) { this(algorithm.getValue(), privateKey, publicKey); } - + /** * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做签名或验证 - * + * * @param algorithm {@link SignAlgorithm} * @param keyPair 密钥对(包括公钥和私钥) */ @@ -84,7 +90,7 @@ public class Sign extends BaseAsymmetric { /** * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做签名或验证 - * + * * @param algorithm {@link SignAlgorithm} * @param privateKey 私钥 * @param publicKey 公钥 @@ -96,7 +102,7 @@ public class Sign extends BaseAsymmetric { /** * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做签名或验证 - * + * * @param algorithm 非对称加密算法 * @param privateKeyBase64 私钥Base64 * @param publicKeyBase64 公钥Base64 @@ -107,10 +113,10 @@ public class Sign extends BaseAsymmetric { /** * 构造 - * + * * 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做签名或验证 - * + * * @param algorithm 算法 * @param privateKey 私钥 * @param publicKey 公钥 @@ -121,11 +127,11 @@ public class Sign extends BaseAsymmetric { SecureUtil.generatePublicKey(algorithm, publicKey)// ); } - + /** * 构造 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做签名或验证 - * + * * @param algorithm 算法,见{@link SignAlgorithm} * @param keyPair 密钥对(包括公钥和私钥) */ @@ -135,10 +141,10 @@ public class Sign extends BaseAsymmetric { /** * 构造 - * + * * 私钥和公钥同时为空时生成一对新的私钥和公钥
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做签名或验证 - * + * * @param algorithm 算法 * @param privateKey 私钥 * @param publicKey 公钥 @@ -147,10 +153,10 @@ public class Sign extends BaseAsymmetric { super(algorithm, privateKey, publicKey); } // ------------------------------------------------------------------ Constructor end - + /** * 初始化 - * + * * @param algorithm 算法 * @param privateKey 私钥 * @param publicKey 公钥 @@ -158,18 +164,14 @@ public class Sign extends BaseAsymmetric { */ @Override public Sign init(String algorithm, PrivateKey privateKey, PublicKey publicKey) { - try { - signature = Signature.getInstance(algorithm); - } catch (NoSuchAlgorithmException e) { - throw new CryptoException(e); - } + signature = SecureUtil.createSignature(algorithm); super.init(algorithm, privateKey, publicKey); return this; } - + /** * 设置签名的参数 - * + * * @param params {@link AlgorithmParameterSpec} * @return this * @since 4.6.5 @@ -184,18 +186,138 @@ public class Sign extends BaseAsymmetric { } // --------------------------------------------------------------------------------- Sign and Verify + /** + * 生成文件签名 + * + * @param data 被签名数据 + * @param charset 编码 + * @return 签名 + * @since 5.7.0 + */ + public byte[] sign(String data, Charset charset) { + return sign(StrUtil.bytes(data, charset)); + } + + /** + * 生成文件签名 + * + * @param data 被签名数据 + * @return 签名 + * @since 5.7.0 + */ + public byte[] sign(String data) { + return sign(data, CharsetUtil.CHARSET_UTF_8); + } + + /** + * 生成文件签名,并转为16进制字符串 + * + * @param data 被签名数据 + * @param charset 编码 + * @return 签名 + * @since 5.7.0 + */ + public String signHex(String data, Charset charset) { + return HexUtil.encodeHexStr(sign(data, charset)); + } + + /** + * 生成文件签名 + * + * @param data 被签名数据 + * @return 签名 + * @since 5.7.0 + */ + public String signHex(String data) { + return signHex(data, CharsetUtil.CHARSET_UTF_8); + } + /** * 用私钥对信息生成数字签名 - * + * * @param data 加密数据 * @return 签名 */ public byte[] sign(byte[] data) { + return sign(new ByteArrayInputStream(data), -1); + } + + /** + * 生成签名,并转为16进制字符串
+ * + * @param data 被签名数据 + * @return 签名 + * @since 5.7.0 + */ + public String signHex(byte[] data) { + return HexUtil.encodeHexStr(sign(data)); + } + + /** + * 生成签名,并转为16进制字符串
+ * 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE} + * + * @param data 被签名数据 + * @return 签名 + * @since 5.7.0 + */ + public String signHex(InputStream data) { + return HexUtil.encodeHexStr(sign(data)); + } + + /** + * 生成签名,使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE} + * + * @param data {@link InputStream} 数据流 + * @return 签名bytes + * @since 5.7.0 + */ + public byte[] sign(InputStream data) { + return sign(data, IoUtil.DEFAULT_BUFFER_SIZE); + } + + /** + * 生成签名,并转为16进制字符串
+ * 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE} + * + * @param data 被签名数据 + * @param bufferLength 缓存长度,不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值 + * @return 签名 + * @since 5.7.0 + */ + public String digestHex(InputStream data, int bufferLength) { + return HexUtil.encodeHexStr(sign(data, bufferLength)); + } + + /** + * 生成签名 + * + * @param data {@link InputStream} 数据流 + * @param bufferLength 缓存长度,不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值 + * @return 签名bytes + * @since 5.7.0 + */ + public byte[] sign(InputStream data, int bufferLength){ + if (bufferLength < 1) { + bufferLength = IoUtil.DEFAULT_BUFFER_SIZE; + } + + final byte[] buffer = new byte[bufferLength]; lock.lock(); try { signature.initSign(this.privateKey); - signature.update(data); - return signature.sign(); + byte[] result; + try { + int read = data.read(buffer, 0, bufferLength); + while (read > -1) { + signature.update(buffer, 0, read); + read = data.read(buffer, 0, bufferLength); + } + result = signature.sign(); + } catch (Exception e) { + throw new CryptoException(e); + } + return result; } catch (Exception e) { throw new CryptoException(e); } finally { @@ -205,7 +327,7 @@ public class Sign extends BaseAsymmetric { /** * 用公钥检验数字签名的合法性 - * + * * @param data 数据 * @param sign 签名 * @return 是否验证通过 @@ -225,7 +347,7 @@ public class Sign extends BaseAsymmetric { /** * 获得签名对象 - * + * * @return {@link Signature} */ public Signature getSignature() { @@ -234,7 +356,7 @@ public class Sign extends BaseAsymmetric { /** * 设置签名 - * + * * @param signature 签名对象 {@link Signature} * @return 自身 {@link AsymmetricCrypto} */ @@ -246,7 +368,7 @@ public class Sign extends BaseAsymmetric { /** * 设置{@link Certificate} 为PublicKey
* 如果Certificate是X509Certificate,我们需要检查是否有密钥扩展 - * + * * @param certificate {@link Certificate} * @return this */ diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SignAlgorithm.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SignAlgorithm.java index 2fe633638..9656e9070 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SignAlgorithm.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SignAlgorithm.java @@ -3,41 +3,45 @@ package cn.hutool.crypto.asymmetric; /** * 签名算法类型
* see: https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Signature - * - * @author Looly * + * @author Looly */ public enum SignAlgorithm { // The RSA signature algorithm - NONEwithRSA("NONEwithRSA"), // + NONEwithRSA("NONEwithRSA"), // The MD2/MD5 with RSA Encryption signature algorithm - MD2withRSA("MD2withRSA"), // - MD5withRSA("MD5withRSA"), // + MD2withRSA("MD2withRSA"), + MD5withRSA("MD5withRSA"), // The signature algorithm with SHA-* and the RSA - SHA1withRSA("SHA1withRSA"), // - SHA256withRSA("SHA256withRSA"), // - SHA384withRSA("SHA384withRSA"), // - SHA512withRSA("SHA512withRSA"), // + SHA1withRSA("SHA1withRSA"), + SHA256withRSA("SHA256withRSA"), + SHA384withRSA("SHA384withRSA"), + SHA512withRSA("SHA512withRSA"), // The Digital Signature Algorithm - NONEwithDSA("NONEwithDSA"), // + NONEwithDSA("NONEwithDSA"), // The DSA with SHA-1 signature algorithm - SHA1withDSA("SHA1withDSA"), // + SHA1withDSA("SHA1withDSA"), // The ECDSA signature algorithms - NONEwithECDSA("NONEwithECDSA"), // - SHA1withECDSA("SHA1withECDSA"), // - SHA256withECDSA("SHA256withECDSA"), // - SHA384withECDSA("SHA384withECDSA"), // - SHA512withECDSA("SHA512withECDSA");// + NONEwithECDSA("NONEwithECDSA"), + SHA1withECDSA("SHA1withECDSA"), + SHA256withECDSA("SHA256withECDSA"), + SHA384withECDSA("SHA384withECDSA"), + SHA512withECDSA("SHA512withECDSA"), + + // 需要BC库加入支持 + SHA256withRSA_PSS("SHA256WithRSA/PSS"), + SHA384withRSA_PSS("SHA384WithRSA/PSS"), + SHA512withRSA_PSS("SHA512WithRSA/PSS"); private final String value; /** * 构造 - * + * * @param value 算法字符表示,区分大小写 */ SignAlgorithm(String value) { @@ -46,7 +50,7 @@ public enum SignAlgorithm { /** * 获取算法字符串表示,区分大小写 - * + * * @return 算法字符串表示 */ public String getValue() { 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 85a0ecb83..83da5b44b 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 @@ -14,6 +14,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.nio.charset.Charset; import java.security.Key; import java.security.MessageDigest; @@ -89,6 +90,15 @@ public class HMac implements Serializable { } // ------------------------------------------------------------------------------------------- Constructor end + /** + * 获得MAC算法引擎 + * + * @return MAC算法引擎 + */ + public MacEngine getEngine(){ + return this.engine; + } + // ------------------------------------------------------------------------------------------- Digest /** * 生成文件摘要 @@ -97,7 +107,7 @@ public class HMac implements Serializable { * @param charset 编码 * @return 摘要 */ - public byte[] digest(String data, String charset) { + public byte[] digest(String data, Charset charset) { return digest(StrUtil.bytes(data, charset)); } @@ -108,7 +118,7 @@ public class HMac implements Serializable { * @return 摘要 */ public byte[] digest(String data) { - return digest(data, CharsetUtil.UTF_8); + return digest(data, CharsetUtil.CHARSET_UTF_8); } /** @@ -118,7 +128,7 @@ public class HMac implements Serializable { * @param charset 编码 * @return 摘要 */ - public String digestHex(String data, String charset) { + public String digestHex(String data, Charset charset) { return HexUtil.encodeHexStr(digest(data, charset)); } @@ -129,7 +139,7 @@ public class HMac implements Serializable { * @return 摘要 */ public String digestHex(String data) { - return digestHex(data, CharsetUtil.UTF_8); + return digestHex(data, CharsetUtil.CHARSET_UTF_8); } /** 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 fba8d93bf..0b78412b4 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,20 +1,15 @@ package cn.hutool.crypto.digest.mac; -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 java.io.IOException; -import java.io.InputStream; - /** * BouncyCastle的HMAC算法实现引擎,使用{@link Mac} 实现摘要
* 当引入BouncyCastle库时自动使用其作为Provider - * + * * @author Looly * @since 4.5.13 */ @@ -25,7 +20,7 @@ public class BCHMacEngine implements MacEngine { // ------------------------------------------------------------------------------------------- Constructor start /** * 构造 - * + * * @param digest 摘要算法,为{@link Digest} 的接口实现 * @param key 密钥 * @since 4.5.13 @@ -36,7 +31,7 @@ public class BCHMacEngine implements MacEngine { /** * 构造 - * + * * @param digest 摘要算法 * @param params 参数,例如密钥可以用{@link KeyParameter} * @since 4.5.13 @@ -48,7 +43,7 @@ public class BCHMacEngine implements MacEngine { /** * 初始化 - * + * * @param digest 摘要算法 * @param params 参数,例如密钥可以用{@link KeyParameter} * @return this @@ -59,40 +54,32 @@ public class BCHMacEngine implements MacEngine { return this; } - @Override - public byte[] digest(InputStream data, int bufferLength) { - if (bufferLength < 1) { - bufferLength = IoUtil.DEFAULT_BUFFER_SIZE; - } - final byte[] buffer = new byte[bufferLength]; - - byte[] result; - try { - int read = data.read(buffer, 0, bufferLength); - - while (read > -1) { - mac.update(buffer, 0, read); - read = data.read(buffer, 0, bufferLength); - } - result = new byte[this.mac.getMacSize()]; - mac.doFinal(result, 0); - } catch (IOException e) { - throw new CryptoException(e); - } finally { - mac.reset(); - } - return result; - } - /** * 获得 {@link Mac} - * + * * @return {@link Mac} */ public Mac getMac() { return mac; } + @Override + public void update(byte[] in, int inOff, int len) { + this.mac.update(in, inOff, len); + } + + @Override + public byte[] doFinal() { + final byte[] result = new byte[getMacLength()]; + this.mac.doFinal(result, 0); + return result; + } + + @Override + public void reset() { + this.mac.reset(); + } + @Override public int getMacLength() { return mac.getMacSize(); 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 23e80b7d5..679764890 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,27 +1,24 @@ package cn.hutool.crypto.digest.mac; -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 - * + * * @author Looly *@since 4.5.13 */ public class DefaultHMacEngine implements MacEngine { private Mac mac; - + // ------------------------------------------------------------------------------------------- Constructor start /** * 构造 @@ -32,7 +29,7 @@ public class DefaultHMacEngine implements MacEngine { public DefaultHMacEngine(String algorithm, byte[] key) { init(algorithm, key); } - + /** * 构造 * @param algorithm 算法 @@ -43,7 +40,7 @@ public class DefaultHMacEngine implements MacEngine { init(algorithm, key); } // ------------------------------------------------------------------------------------------- Constructor end - + /** * 初始化 * @param algorithm 算法 @@ -53,7 +50,7 @@ public class DefaultHMacEngine implements MacEngine { public DefaultHMacEngine init(String algorithm, byte[] key){ return init(algorithm, (null == key) ? null : new SecretKeySpec(key, algorithm)); } - + /** * 初始化 * @param algorithm 算法 @@ -74,39 +71,35 @@ public class DefaultHMacEngine implements MacEngine { return this; } - @Override - public byte[] digest(InputStream data, int bufferLength) { - if (bufferLength < 1) { - bufferLength = IoUtil.DEFAULT_BUFFER_SIZE; - } - byte[] buffer = new byte[bufferLength]; - - byte[] result; - try { - int read = data.read(buffer, 0, bufferLength); - - while (read > -1) { - mac.update(buffer, 0, read); - read = data.read(buffer, 0, bufferLength); - } - result = mac.doFinal(); - } catch (IOException e) { - throw new CryptoException(e); - } finally { - mac.reset(); - } - return result; - } - /** * 获得 {@link Mac} - * + * * @return {@link Mac} */ public Mac getMac() { return mac; } + @Override + public void update(byte[] in) { + this.mac.update(in); + } + + @Override + public void update(byte[] in, int inOff, int len) { + this.mac.update(in, inOff, len); + } + + @Override + public byte[] doFinal() { + return this.mac.doFinal(); + } + + @Override + public void reset() { + this.mac.reset(); + } + @Override public int getMacLength() { return mac.getMacLength(); 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 f1cf40dd1..5197896ef 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,25 +1,81 @@ package cn.hutool.crypto.digest.mac; import cn.hutool.core.io.IoUtil; +import cn.hutool.crypto.CryptoException; +import java.io.IOException; import java.io.InputStream; /** * MAC(Message Authentication Code)算法引擎 - * + * * @author Looly * @since 4.5.13 */ public interface MacEngine { - + + /** + * 加入需要被摘要的内容 + * @param in 内容 + * @since 5.7.0 + */ + default void update(byte[] in){ + update(in, 0, in.length); + } + + /** + * 加入需要被摘要的内容 + * @param in 内容 + * @param inOff 内容起始位置 + * @param len 内容长度 + * @since 5.7.0 + */ + void update(byte[] in, int inOff, int len); + + /** + * 结束并生成摘要 + * + * @return 摘要内容 + * @since 5.7.0 + */ + byte[] doFinal(); + + /** + * 重置 + * @since 5.7.0 + */ + void reset(); + /** * 生成摘要 - * + * * @param data {@link InputStream} 数据流 * @param bufferLength 缓存长度,不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值 * @return 摘要bytes */ - byte[] digest(InputStream data, int bufferLength); + default byte[] digest(InputStream data, int bufferLength){ + if (bufferLength < 1) { + bufferLength = IoUtil.DEFAULT_BUFFER_SIZE; + } + + final byte[] buffer = new byte[bufferLength]; + + byte[] result; + try { + int read = data.read(buffer, 0, bufferLength); + + while (read > -1) { + update(buffer, 0, read); + read = data.read(buffer, 0, bufferLength); + } + result = doFinal(); + } catch (IOException e) { + throw new CryptoException(e); + } finally { + reset(); + } + return result; + } /** * 获取MAC算法块大小 diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SignTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SignTest.java index cafd234ef..0bc8f268f 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SignTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/asymmetric/SignTest.java @@ -13,7 +13,7 @@ import java.util.Map; /** * 签名单元测试 - * + * * @author looly * */ @@ -22,7 +22,7 @@ public class SignTest { @Test public void signAndVerifyUseKeyTest() { String content = "我是Hanley."; - + String privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJ4fG8vJ0tzu7tjXMSJhyNjlE5B7GkTKMKEQlR6LY3IhIhMFVjuA6W+DqH1VMxl9h3GIM4yCKG2VRZEYEPazgVxa5/ifO8W0pfmrzWCPrddUq4t0Slz5u2lLKymLpPjCzboHoDb8VlF+1HOxjKQckAXq9q7U7dV5VxOzJDuZXlz3AgMBAAECgYABo2LfVqT3owYYewpIR+kTzjPIsG3SPqIIWSqiWWFbYlp/BfQhw7EndZ6+Ra602ecYVwfpscOHdx90ZGJwm+WAMkKT4HiWYwyb0ZqQzRBGYDHFjPpfCBxrzSIJ3QL+B8c8YHq4HaLKRKmq7VUF1gtyWaek87rETWAmQoGjt8DyAQJBAOG4OxsT901zjfxrgKwCv6fV8wGXrNfDSViP1t9r3u6tRPsE6Gli0dfMyzxwENDTI75sOEAfyu6xBlemQGmNsfcCQQCzVWQkl9YUoVDWEitvI5MpkvVKYsFLRXKvLfyxLcY3LxpLKBcEeJ/n5wLxjH0GorhJMmM2Rw3hkjUTJCoqqe0BAkATt8FKC0N2O5ryqv1xiUfuxGzW/cX2jzOwDdiqacTuuqok93fKBPzpyhUS8YM2iss7jj6Xs29JzKMOMxK7ZcpfAkAf21lwzrAu9gEgJhYlJhKsXfjJAAYKUwnuaKLs7o65mtp242ZDWxI85eK1+hjzptBJ4HOTXsfufESFY/VBovIBAkAltO886qQRoNSc0OsVlCi4X1DGo6x2RqQ9EsWPrxWEZGYuyEdODrc54b8L+zaUJLfMJdsCIHEUbM7WXxvFVXNv"; Sign sign = SecureUtil.sign(SignAlgorithm.SHA1withRSA, privateKey, null); Assert.assertNull(sign.getPublicKeyBase64()); @@ -35,7 +35,7 @@ public class SignTest { boolean verify = sign.verify(content.getBytes(), signed); Assert.assertTrue(verify); } - + @Test public void signAndVerifyTest() { signAndVerify(SignAlgorithm.NONEwithRSA); @@ -60,7 +60,7 @@ public class SignTest { /** * 测试各种算法的签名和验证签名 - * + * * @param signAlgorithm 算法 */ private void signAndVerify(SignAlgorithm signAlgorithm) { @@ -74,19 +74,19 @@ public class SignTest { boolean verify = sign.verify(data, signed); Assert.assertTrue(verify); } - + /** * 测试MD5withRSA算法的签名和验证签名 */ @Test - public void signAndVerify2() { + public void signAndVerifyTest2() { String str = "wx2421b1c4370ec43b 支付测试 JSAPI支付测试 10000100 1add1a30ac87aa2db72f57a2375d8fec http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php oUpF8uMuAJO_M2pxb1Q9zNjWeS6o 1415659990 14.23.150.211 1 JSAPI 0CB01533B8C1EF103065174F50BCA001"; byte[] data = StrUtil.utf8Bytes(str); Sign sign = SecureUtil.sign(SignAlgorithm.MD5withRSA); - + // 签名 byte[] signed = sign.sign(data); - + // 验证签名 boolean verify = sign.verify(data, signed); Assert.assertTrue(verify); @@ -105,4 +105,21 @@ public class SignTest { String sign3 = SecureUtil.signParamsSha1(build, "12345678", "abc"); Assert.assertEquals("edee1b477af1b96ebd20fdf08d818f352928d25d", sign3); } + + /** + * 测试MD5withRSA算法的签名和验证签名 + */ + @Test + public void signAndVerifyPSSTest() { + String str = "wx2421b1c4370ec43b 支付测试 JSAPI支付测试 10000100 1add1a30ac87aa2db72f57a2375d8fec http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php oUpF8uMuAJO_M2pxb1Q9zNjWeS6o 1415659990 14.23.150.211 1 JSAPI 0CB01533B8C1EF103065174F50BCA001"; + byte[] data = StrUtil.utf8Bytes(str); + Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA_PSS); + + // 签名 + byte[] signed = sign.sign(data); + + // 验证签名 + boolean verify = sign.verify(data, signed); + Assert.assertTrue(verify); + } } diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 9accffc77..7f3ccdd05 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -14,6 +14,11 @@ ${project.artifactId} Hutool JWT生成、解析和验证实现 + + + 1.68 + + cn.hutool @@ -25,5 +30,11 @@ hutool-crypto ${project.parent.version} + + org.bouncycastle + bcprov-jdk15to18 + ${bouncycastle.version} + test + diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/Claims.java b/hutool-jwt/src/main/java/cn/hutool/jwt/Claims.java index 2b7f7d593..0b00fe4fa 100644 --- a/hutool-jwt/src/main/java/cn/hutool/jwt/Claims.java +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/Claims.java @@ -1,19 +1,25 @@ package cn.hutool.jwt; +import cn.hutool.core.codec.Base64; import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; import java.io.Serializable; +import java.nio.charset.Charset; +import java.util.Map; /** * Claims 认证,简单的JSONObject包装 * * @author looly + * @since 5.7.0 */ public class Claims implements Serializable { private static final long serialVersionUID = 1L; - private final JSONObject claimJSON; + private JSONObject claimJSON; public Claims() { this.claimJSON = new JSONObject(); @@ -34,6 +40,28 @@ public class Claims implements Serializable { claimJSON.set(name, value); } + /** + * 加入多个Claims属性 + * @param headerClaims 多个Claims属性 + */ + protected void putAll(Map headerClaims){ + if (MapUtil.isNotEmpty(headerClaims)) { + for (Map.Entry entry : headerClaims.entrySet()) { + setClaim(entry.getKey(), entry.getValue()); + } + } + } + + /** + * 获取指定名称属性 + * + * @param name 名称 + * @return 属性 + */ + public Object getClaim(String name) { + return this.claimJSON.getObj(name); + } + /** * 获取Claims的JSON字符串形式 * @@ -42,4 +70,14 @@ public class Claims implements Serializable { public String getClaimsJson() { return this.claimJSON.toString(); } + + /** + * 解析JWT JSON + * + * @param tokenPart JWT JSON + * @param charset 编码 + */ + public void parse(String tokenPart, Charset charset) { + this.claimJSON = JSONUtil.parseObj(Base64.decodeStr(tokenPart, charset)); + } } diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/JWT.java b/hutool-jwt/src/main/java/cn/hutool/jwt/JWT.java index c63085129..04153933c 100644 --- a/hutool-jwt/src/main/java/cn/hutool/jwt/JWT.java +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/JWT.java @@ -1,11 +1,18 @@ package cn.hutool.jwt; import cn.hutool.core.codec.Base64; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.jwt.signers.AlgorithmUtil; import cn.hutool.jwt.signers.JWTSigner; +import cn.hutool.jwt.signers.JWTSignerUtil; import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; /** * JSON Web Token (JWT),基于JSON的开放标准((RFC 7519)用于在网络应用环境间传递声明。
@@ -31,6 +38,26 @@ public class JWT { private Charset charset; private JWTSigner signer; + private List tokens; + + /** + * 创建空的JWT对象 + * + * @return {@link JWT} + */ + public static JWT create() { + return new JWT(); + } + + /** + * 创建并解析JWT对象 + * + * @return {@link JWT} + */ + public static JWT of(String token) { + return new JWT(token); + } + /** * 构造 */ @@ -40,6 +67,28 @@ public class JWT { this.charset = CharsetUtil.CHARSET_UTF_8; } + /** + * 构造 + */ + public JWT(String token) { + this(); + parse(token); + } + + /** + * 解析JWT内容 + * + * @param token JWT token + * @return this + */ + public JWT parse(String token) { + final List tokens = splitToken(token); + this.tokens = tokens; + this.header.parse(tokens.get(0), this.charset); + this.payload.parse(tokens.get(1), this.charset); + return this; + } + /** * 设置编码 * @@ -51,6 +100,27 @@ public class JWT { return this; } + /** + * 设置密钥,默认算法是:HS256(HmacSHA256) + * + * @param key 密钥 + * @return this + */ + public JWT setKey(byte[] key) { + return setSigner(JWTSignerUtil.hs256(key)); + } + + /** + * 设置签名算法 + * + * @param algorithmId 签名算法ID,如HS256 + * @param key 密钥 + * @return this + */ + public JWT setSigner(String algorithmId, byte[] key) { + return setSigner(JWTSignerUtil.createSigner(algorithmId, key)); + } + /** * 设置签名算法 * @@ -62,16 +132,140 @@ public class JWT { return this; } + /** + * 获取头信息 + * + * @param name 头信息名称 + * @return 头信息 + */ + public Object getHeader(String name) { + return this.header.getClaim(name); + } + + /** + * 设置JWT头信息 + * + * @param name 头名 + * @param value 头 + * @return this + */ + public JWT setHeader(String name, Object value) { + this.header.setClaim(name, value); + return this; + } + + /** + * 增加JWT头信息 + * + * @param headers 头信息 + * @return this + */ + public JWT addHeaders(Map headers) { + this.header.addHeaders(headers); + return this; + } + + /** + * 获取载荷信息 + * + * @param name 载荷信息名称 + * @return 载荷信息 + */ + public Object getPayload(String name) { + return this.payload.getClaim(name); + } + + /** + * 设置JWT载荷信息 + * + * @param name 载荷名 + * @param value 头 + * @return this + */ + public JWT setPayload(String name, Object value) { + this.payload.setClaim(name, value); + return this; + } + + /** + * 增加JWT载荷信息 + * + * @param payloads 载荷信息 + * @return this + */ + public JWT addPayloads(Map payloads) { + this.payload.addPayloads(payloads); + return this; + } + /** * 签名生成JWT字符串 * * @return JWT字符串 */ public String sign() { + return sign(this.signer); + } + + /** + * 签名生成JWT字符串 + * + * @param signer JWT签名器 + * @return JWT字符串 + */ + public String sign(JWTSigner signer) { + Assert.notNull(signer, () -> new JWTException("No Signer provided!")); + + // 检查头信息中是否有算法信息 + final String claim = (String) this.header.getClaim(JWTHeader.ALGORITHM); + if (StrUtil.isBlank(claim)) { + this.header.setClaim(JWTHeader.ALGORITHM, + AlgorithmUtil.getId(signer.getAlgorithm())); + } + final String headerBase64 = Base64.encodeUrlSafe(this.header.getClaimsJson(), charset); final String payloadBase64 = Base64.encodeUrlSafe(this.payload.getClaimsJson(), charset); final String sign = signer.sign(headerBase64, payloadBase64); return StrUtil.format("{}.{}.{}", headerBase64, payloadBase64, sign); } + + /** + * 验证JWT Token是否有效 + * + * @return 是否有效 + */ + public boolean verify() { + return verify(this.signer); + } + + /** + * 验证JWT Token是否有效 + * + * @param signer 签名器(签名算法) + * @return 是否有效 + */ + public boolean verify(JWTSigner signer) { + Assert.notNull(signer, () -> new JWTException("No Signer provided!")); + + final List tokens = this.tokens; + if(CollUtil.isEmpty(tokens)){ + throw new JWTException("No token to verify!"); + } + return signer.verify(tokens.get(0), tokens.get(1), tokens.get(2)); + } + + /** + * 将JWT字符串拆分为3部分,无加密算法则最后一部分是"" + * + * @param token JWT Token + * @return 三部分内容 + */ + private static List splitToken(String token) { + final List tokens = StrUtil.split(token, CharUtil.DOT); + if (3 != tokens.size()) { + throw new JWTException("The token was expected 3 parts, but got {}.", tokens.size()); + } + return tokens; + } } diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/JWTException.java b/hutool-jwt/src/main/java/cn/hutool/jwt/JWTException.java new file mode 100644 index 000000000..b2fd6aece --- /dev/null +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/JWTException.java @@ -0,0 +1,34 @@ +package cn.hutool.jwt; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.StrUtil; + +/** + * JWT异常 + * + * @author looly + * @since 5.7.0 + */ +public class JWTException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public JWTException(Throwable e) { + super(ExceptionUtil.getMessage(e), e); + } + + public JWTException(String message) { + super(message); + } + + public JWTException(String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params)); + } + + public JWTException(String message, Throwable cause) { + super(message, cause); + } + + public JWTException(Throwable throwable, String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params), throwable); + } +} diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/JWTHeader.java b/hutool-jwt/src/main/java/cn/hutool/jwt/JWTHeader.java index 323ba0c71..914f6248f 100644 --- a/hutool-jwt/src/main/java/cn/hutool/jwt/JWTHeader.java +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/JWTHeader.java @@ -6,6 +6,7 @@ import java.util.Map; * JWT头部信息 * * @author looly + * @since 5.7.0 */ public class JWTHeader extends Claims { private static final long serialVersionUID = 1L; @@ -28,6 +29,13 @@ public class JWTHeader extends Claims { */ public static String KEY_ID = "kid"; + /** + * 构造,初始化默认(typ=JWT) + */ + public JWTHeader() { + setClaim(TYPE, "JWT"); + } + /** * 增加“kid”头信息 * @@ -46,14 +54,7 @@ public class JWTHeader extends Claims { * @return this */ public JWTHeader addHeaders(Map headerClaims) { - if (headerClaims == null) { - return this; - } - - for (Map.Entry entry : headerClaims.entrySet()) { - setClaim(entry.getKey(), entry.getValue()); - } - + putAll(headerClaims); return this; } } diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/JWTPayload.java b/hutool-jwt/src/main/java/cn/hutool/jwt/JWTPayload.java index 30cb8b922..fc0ebf3e7 100644 --- a/hutool-jwt/src/main/java/cn/hutool/jwt/JWTPayload.java +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/JWTPayload.java @@ -16,6 +16,7 @@ import java.util.Map; * 详细介绍见:https://www.jianshu.com/p/576dbf44b2ae * * @author looly + * @since 5.7.0 */ public class JWTPayload extends Claims { private static final long serialVersionUID = 1L; @@ -134,15 +135,8 @@ public class JWTPayload extends Claims { * @param payloadClaims 载荷信息 * @return this */ - public JWTPayload addPayload(Map payloadClaims) { - if (payloadClaims == null) { - return this; - } - - for (Map.Entry entry : payloadClaims.entrySet()) { - setClaim(entry.getKey(), entry.getValue()); - } - + public JWTPayload addPayloads(Map payloadClaims) { + putAll(payloadClaims); return this; } } diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/JWTUtil.java b/hutool-jwt/src/main/java/cn/hutool/jwt/JWTUtil.java new file mode 100644 index 000000000..1bf5b6fa9 --- /dev/null +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/JWTUtil.java @@ -0,0 +1,97 @@ +package cn.hutool.jwt; + +import cn.hutool.jwt.signers.JWTSigner; + +import java.util.Map; + +/** + * JSON Web Token (JWT)工具类 + */ +public class JWTUtil { + + /** + * 创建HS256(HmacSHA256) JWT Token + * + * @param payload 荷载信息 + * @param key HS256(HmacSHA256)密钥 + * @return JWT Token + */ + public static String createToken(Map payload, byte[] key) { + return createToken(null, payload, key); + } + + /** + * 创建HS256(HmacSHA256) JWT Token + * + * @param headers 头信息 + * @param payload 荷载信息 + * @param key HS256(HmacSHA256)密钥 + * @return JWT Token + */ + public static String createToken(Map headers, Map payload, byte[] key) { + return JWT.create() + .addHeaders(headers) + .addPayloads(payload) + .setKey(key) + .sign(); + } + + /** + * 创建JWT Token + * + * @param payload 荷载信息 + * @param signer 签名算法 + * @return JWT Token + */ + public static String createToken(Map payload, JWTSigner signer) { + return createToken(null, payload, signer); + } + + /** + * 创建JWT Token + * + * @param headers 头信息 + * @param payload 荷载信息 + * @param signer 签名算法 + * @return JWT Token + */ + public static String createToken(Map headers, Map payload, JWTSigner signer) { + return JWT.create() + .addHeaders(headers) + .addPayloads(payload) + .setSigner(signer) + .sign(); + } + + /** + * 解析JWT Token + * + * @param token token + * @return {@link JWT} + */ + public JWT parseToken(String token) { + return JWT.of(token); + } + + /** + * 验证JWT Token有效性 + * + * @param token JWT Token + * @param key HS256(HmacSHA256)密钥 + * @return 是否有效 + */ + public boolean verify(String token, byte[] key) { + return JWT.of(token).setKey(key).verify(); + } + + /** + * 验证JWT Token有效性 + * + * @param token JWT Token + * @param signer 签名器 + * @return 是否有效 + */ + public boolean verify(String token, JWTSigner signer) { + return JWT.of(token).verify(signer); + } +} diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/AlgorithmUtil.java b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/AlgorithmUtil.java new file mode 100644 index 000000000..a9f6752bb --- /dev/null +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/AlgorithmUtil.java @@ -0,0 +1,76 @@ +package cn.hutool.jwt.signers; + +import cn.hutool.core.map.BiMap; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.asymmetric.SignAlgorithm; +import cn.hutool.crypto.digest.HmacAlgorithm; + +import java.util.HashMap; + +/** + * 算法工具类,算法和JWT算法ID对应表 + * + * @author looly + * @since 5.7.0 + */ +public class AlgorithmUtil { + + private static final BiMap map; + + static { + map = new BiMap<>(new HashMap<>()); + map.put("HS256", HmacAlgorithm.HmacSHA256.getValue()); + map.put("HS384", HmacAlgorithm.HmacSHA384.getValue()); + map.put("HS512", HmacAlgorithm.HmacSHA512.getValue()); + + map.put("RS256", SignAlgorithm.SHA256withRSA.getValue()); + map.put("RS384", SignAlgorithm.SHA384withRSA.getValue()); + map.put("RS512", SignAlgorithm.SHA512withRSA.getValue()); + + map.put("ES256", SignAlgorithm.SHA256withECDSA.getValue()); + map.put("ES384", SignAlgorithm.SHA384withECDSA.getValue()); + map.put("ES512", SignAlgorithm.SHA512withECDSA.getValue()); + + map.put("PS256", SignAlgorithm.SHA256withRSA_PSS.getValue()); + map.put("PS384", SignAlgorithm.SHA384withRSA_PSS.getValue()); + map.put("PS512", SignAlgorithm.SHA512withRSA_PSS.getValue()); + } + + /** + * 获取算法,用户传入算法ID返回算法名,传入算法名返回本身 + * @param idOrAlgorithm 算法ID或算法名 + * @return 算法名 + */ + public static String getAlgorithm(String idOrAlgorithm){ + return ObjectUtil.defaultIfNull(getAlgorithmById(idOrAlgorithm), idOrAlgorithm); + } + + /** + * 获取算法ID,用户传入算法名返回ID,传入算法ID返回本身 + * @param idOrAlgorithm 算法ID或算法名 + * @return 算法ID + */ + public static String getId(String idOrAlgorithm){ + return ObjectUtil.defaultIfNull(getIdByAlgorithm(idOrAlgorithm), idOrAlgorithm); + } + + /** + * 根据JWT算法ID获取算法 + * + * @param id JWT算法ID + * @return 算法 + */ + private static String getAlgorithmById(String id) { + return map.get(id.toUpperCase()); + } + + /** + * 根据算法获取JWT算法ID + * + * @param algorithm 算法 + * @return JWT算法ID + */ + private static String getIdByAlgorithm(String algorithm) { + return map.getInverse().get(algorithm); + } +} diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/AsymmetricJWTSigner.java b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/AsymmetricJWTSigner.java new file mode 100644 index 000000000..4d9a4cb34 --- /dev/null +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/AsymmetricJWTSigner.java @@ -0,0 +1,75 @@ +package cn.hutool.jwt.signers; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.asymmetric.Sign; + +import java.nio.charset.Charset; +import java.security.Key; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * 非对称加密JWT签名封装 + * + * @author looly + * @since 5.7.0 + */ +public class AsymmetricJWTSigner implements JWTSigner { + + private Charset charset = CharsetUtil.CHARSET_UTF_8; + private final Sign sign; + + /** + * 构造 + * + * @param algorithm 算法字符串表示 + * @param key 公钥{@link PublicKey}或私钥{@link PrivateKey},公钥用于验证签名,私钥用于产生签名 + */ + public AsymmetricJWTSigner(String algorithm, Key key) { + final PublicKey publicKey = key instanceof PublicKey ? (PublicKey) key : null; + final PrivateKey privateKey = key instanceof PrivateKey ? (PrivateKey) key : null; + this.sign = new Sign(algorithm, privateKey, publicKey); + } + + /** + * 构造 + * + * @param algorithm 算法字符串表示 + * @param keyPair 密钥对 + */ + public AsymmetricJWTSigner(String algorithm, KeyPair keyPair) { + this.sign = new Sign(algorithm, keyPair); + } + + /** + * 设置编码 + * + * @param charset 编码 + * @return 编码 + */ + public AsymmetricJWTSigner setCharset(Charset charset) { + this.charset = charset; + return this; + } + + @Override + public String sign(String headerBase64, String payloadBase64) { + return Base64.encodeUrlSafe(sign.sign(StrUtil.format("{}.{}", headerBase64, payloadBase64))); + } + + @Override + public boolean verify(String headerBase64, String payloadBase64, String signBase64) { + return sign.verify( + StrUtil.bytes(StrUtil.format("{}.{}", headerBase64, payloadBase64), charset), + Base64.decode(signBase64)); + } + + @Override + public String getAlgorithm() { + return this.sign.getSignature().getAlgorithm(); + } + +} diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/HMacJWTSigner.java b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/HMacJWTSigner.java new file mode 100644 index 000000000..5873c7ddf --- /dev/null +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/HMacJWTSigner.java @@ -0,0 +1,69 @@ +package cn.hutool.jwt.signers; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.HMac; + +import java.nio.charset.Charset; +import java.security.Key; + +/** + * HMac算法签名实现 + * + * @author looly + * @since 5.7.0 + */ +public class HMacJWTSigner implements JWTSigner { + + private Charset charset = CharsetUtil.CHARSET_UTF_8; + private final HMac hMac; + + /** + * 构造 + * + * @param algorithm HMAC签名算法 + * @param key 密钥 + */ + public HMacJWTSigner(String algorithm, byte[] key) { + this.hMac = new HMac(algorithm, key); + } + + /** + * 构造 + * + * @param algorithm HMAC签名算法 + * @param key 密钥 + */ + public HMacJWTSigner(String algorithm, Key key) { + this.hMac = new HMac(algorithm, key); + } + + /** + * 设置编码 + * + * @param charset 编码 + * @return 编码 + */ + public HMacJWTSigner setCharset(Charset charset) { + this.charset = charset; + return this; + } + + @Override + public String sign(String headerBase64, String payloadBase64) { + return hMac.digestHex(StrUtil.format("{}.{}", headerBase64, payloadBase64), charset); + } + + @Override + public boolean verify(String headerBase64, String payloadBase64, String signBase64) { + final String sign = sign(headerBase64, payloadBase64); + return hMac.verify( + StrUtil.bytes(sign, charset), + StrUtil.bytes(signBase64, charset)); + } + + @Override + public String getAlgorithm() { + return this.hMac.getAlgorithm(); + } +} diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSigner.java b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSigner.java index a217173d0..81bfe3f88 100644 --- a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSigner.java +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSigner.java @@ -9,9 +9,27 @@ public interface JWTSigner { /** * 签名 - * @param header JWT头的JSON字符串 - * @param payload JWT载荷的JSON字符串 + * + * @param headerBase64 JWT头的JSON字符串的Base64表示 + * @param payloadBase64 JWT载荷的JSON字符串Base64表示 * @return 签名结果,即JWT的第三部分 */ - String sign(String header, String payload); + String sign(String headerBase64, String payloadBase64); + + /** + * 验签 + * + * @param headerBase64 JWT头的JSON字符串Base64表示 + * @param payloadBase64 JWT载荷的JSON字符串Base64表示 + * @param signBase64 被验证的签名Base64表示 + * @return 签名是否一致 + */ + boolean verify(String headerBase64, String payloadBase64, String signBase64); + + /** + * 获取算法 + * + * @return 算法 + */ + String getAlgorithm(); } diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSignerUtil.java b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSignerUtil.java new file mode 100644 index 000000000..f7d04b025 --- /dev/null +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSignerUtil.java @@ -0,0 +1,164 @@ +package cn.hutool.jwt.signers; + +import cn.hutool.core.lang.Assert; + +import java.security.Key; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * JWT签名器工具类 + * + * @author looly + * @since 5.7.0 + */ +public class JWTSignerUtil { + + /** + * 无签名 + * + * @return 无签名的签名器 + */ + public static JWTSigner none() { + return NoneJWTSigner.NONE; + } + + //------------------------------------------------------------------------- HSxxx + + /** + * HS256(HmacSHA256)签名器 + * + * @return 签名器 + */ + public static JWTSigner hs256(byte[] key) { + return createSigner("HS256", key); + } + + /** + * HS384(HmacSHA384)签名器 + * + * @return 签名器 + */ + public static JWTSigner hs384(byte[] key) { + return createSigner("HS384", key); + } + + /** + * HS512(HmacSHA512)签名器 + * + * @return 签名器 + */ + public static JWTSigner hs512(byte[] key) { + return createSigner("HS512", key); + } + + //------------------------------------------------------------------------- RSxxx + + /** + * RS256(SHA256withRSA)签名器 + * + * @return 签名器 + */ + public static JWTSigner rs256(Key key) { + return createSigner("RS256", key); + } + + /** + * RS384(SHA384withRSA)签名器 + * + * @return 签名器 + */ + public static JWTSigner rs384(Key key) { + return createSigner("RS384", key); + } + + /** + * RS512(SHA512withRSA)签名器 + * + * @return 签名器 + */ + public static JWTSigner rs512(Key key) { + return createSigner("RS512", key); + } + + //------------------------------------------------------------------------- ESxxx + + /** + * ES256(SHA256withECDSA)签名器 + * + * @return 签名器 + */ + public static JWTSigner es256(Key key) { + return createSigner("ES256", key); + } + + /** + * ES384(SHA383withECDSA)签名器 + * + * @return 签名器 + */ + public static JWTSigner es384(Key key) { + return createSigner("ES384", key); + } + + /** + * ES512(SHA512withECDSA)签名器 + * + * @return 签名器 + */ + public static JWTSigner es512(Key key) { + return createSigner("ES512", key); + } + + /** + * 创建签名器 + * + * @param algorithmId 算法ID,见{@link AlgorithmUtil} + * @param key 密钥 + * @return 签名器 + */ + public static JWTSigner createSigner(String algorithmId, byte[] key) { + Assert.notNull(key, "Signer key must be not null!"); + + if (null == algorithmId || NoneJWTSigner.ID_NONE.equals(algorithmId)) { + return none(); + } + return new HMacJWTSigner(AlgorithmUtil.getAlgorithm(algorithmId), key); + } + + /** + * 创建签名器 + * + * @param algorithmId 算法ID,见{@link AlgorithmUtil} + * @param keyPair 密钥对 + * @return 签名器 + */ + public static JWTSigner createSigner(String algorithmId, KeyPair keyPair) { + Assert.notNull(keyPair, "Signer key pair must be not null!"); + + if (null == algorithmId || NoneJWTSigner.ID_NONE.equals(algorithmId)) { + return none(); + } + return new AsymmetricJWTSigner(AlgorithmUtil.getAlgorithm(algorithmId), keyPair); + } + + /** + * 创建签名器 + * + * @param algorithmId 算法ID,见{@link AlgorithmUtil} + * @param key 密钥 + * @return 签名器 + */ + public static JWTSigner createSigner(String algorithmId, Key key) { + Assert.notNull(key, "Signer key must be not null!"); + + if (null == algorithmId || NoneJWTSigner.ID_NONE.equals(algorithmId)) { + return NoneJWTSigner.NONE; + } + if (key instanceof PrivateKey || key instanceof PublicKey) { + return new AsymmetricJWTSigner(AlgorithmUtil.getAlgorithm(algorithmId), key); + } + return new HMacJWTSigner(AlgorithmUtil.getAlgorithm(algorithmId), key); + } +} diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/NoneJWTSigner.java b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/NoneJWTSigner.java new file mode 100644 index 000000000..39a3e4442 --- /dev/null +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/NoneJWTSigner.java @@ -0,0 +1,31 @@ +package cn.hutool.jwt.signers; + +import cn.hutool.core.util.StrUtil; + +/** + * 无需签名的JWT签名器 + * + * @author looly + * @since 5.7.0 + */ +public class NoneJWTSigner implements JWTSigner { + + public static final String ID_NONE = "none"; + + public static NoneJWTSigner NONE = new NoneJWTSigner(); + + @Override + public String sign(String headerBase64, String payloadBase64) { + return StrUtil.EMPTY; + } + + @Override + public boolean verify(String headerBase64, String payloadBase64, String signBase64) { + return StrUtil.isEmpty(signBase64); + } + + @Override + public String getAlgorithm() { + return ID_NONE; + } +} diff --git a/hutool-jwt/src/test/java/cn/hutool/jwt/JWTSignerTest.java b/hutool-jwt/src/test/java/cn/hutool/jwt/JWTSignerTest.java new file mode 100644 index 000000000..e391a4e00 --- /dev/null +++ b/hutool-jwt/src/test/java/cn/hutool/jwt/JWTSignerTest.java @@ -0,0 +1,121 @@ +package cn.hutool.jwt; + +import cn.hutool.crypto.KeyUtil; +import cn.hutool.jwt.signers.AlgorithmUtil; +import cn.hutool.jwt.signers.JWTSigner; +import cn.hutool.jwt.signers.JWTSignerUtil; +import org.junit.Assert; +import org.junit.Test; + +public class JWTSignerTest { + + @Test + public void hs256Test(){ + String id = "hs256"; + final JWTSigner signer = JWTSignerUtil.createSigner(id, KeyUtil.generateKey(AlgorithmUtil.getAlgorithm(id))); + Assert.assertEquals(AlgorithmUtil.getAlgorithm(id), signer.getAlgorithm()); + + signAndVerify(signer); + } + + @Test + public void hs384Test(){ + String id = "hs384"; + final JWTSigner signer = JWTSignerUtil.createSigner(id, KeyUtil.generateKey(AlgorithmUtil.getAlgorithm(id))); + Assert.assertEquals(AlgorithmUtil.getAlgorithm(id), signer.getAlgorithm()); + + signAndVerify(signer); + } + + @Test + public void hs512Test(){ + String id = "hs512"; + final JWTSigner signer = JWTSignerUtil.createSigner(id, KeyUtil.generateKey(AlgorithmUtil.getAlgorithm(id))); + Assert.assertEquals(AlgorithmUtil.getAlgorithm(id), signer.getAlgorithm()); + + signAndVerify(signer); + } + + @Test + public void rs256Test(){ + String id = "rs256"; + final JWTSigner signer = JWTSignerUtil.createSigner(id, KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(id))); + Assert.assertEquals(AlgorithmUtil.getAlgorithm(id), signer.getAlgorithm()); + + signAndVerify(signer); + } + + @Test + public void rs384Test(){ + String id = "rs384"; + final JWTSigner signer = JWTSignerUtil.createSigner(id, KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(id))); + Assert.assertEquals(AlgorithmUtil.getAlgorithm(id), signer.getAlgorithm()); + + signAndVerify(signer); + } + + @Test + public void rs512Test(){ + String id = "rs512"; + final JWTSigner signer = JWTSignerUtil.createSigner(id, KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(id))); + Assert.assertEquals(AlgorithmUtil.getAlgorithm(id), signer.getAlgorithm()); + + signAndVerify(signer); + } + + @Test + public void es256Test(){ + String id = "es256"; + final JWTSigner signer = JWTSignerUtil.createSigner(id, KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(id))); + Assert.assertEquals(AlgorithmUtil.getAlgorithm(id), signer.getAlgorithm()); + + signAndVerify(signer); + } + + @Test + public void es384Test(){ + String id = "es384"; + final JWTSigner signer = JWTSignerUtil.createSigner(id, KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(id))); + Assert.assertEquals(AlgorithmUtil.getAlgorithm(id), signer.getAlgorithm()); + + signAndVerify(signer); + } + + @Test + public void es512Test(){ + String id = "es512"; + final JWTSigner signer = JWTSignerUtil.createSigner(id, KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(id))); + Assert.assertEquals(AlgorithmUtil.getAlgorithm(id), signer.getAlgorithm()); + + signAndVerify(signer); + } + + @Test + public void ps256Test(){ + String id = "ps256"; + final JWTSigner signer = JWTSignerUtil.createSigner(id, KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(id))); + Assert.assertEquals(AlgorithmUtil.getAlgorithm(id), signer.getAlgorithm()); + + signAndVerify(signer); + } + + @Test + public void ps384Test(){ + String id = "ps384"; + final JWTSigner signer = JWTSignerUtil.createSigner(id, KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(id))); + Assert.assertEquals(AlgorithmUtil.getAlgorithm(id), signer.getAlgorithm()); + + signAndVerify(signer); + } + + private static void signAndVerify(JWTSigner signer){ + JWT jwt = JWT.create() + .setPayload("sub", "1234567890") + .setPayload("name", "looly") + .setPayload("admin", true) + .setSigner(signer); + + String token = jwt.sign(); + Assert.assertTrue(JWT.of(token).setSigner(signer).verify()); + } +} diff --git a/hutool-jwt/src/test/java/cn/hutool/jwt/JWTTest.java b/hutool-jwt/src/test/java/cn/hutool/jwt/JWTTest.java new file mode 100644 index 000000000..2de7a81c7 --- /dev/null +++ b/hutool-jwt/src/test/java/cn/hutool/jwt/JWTTest.java @@ -0,0 +1,76 @@ +package cn.hutool.jwt; + +import cn.hutool.jwt.signers.JWTSignerUtil; +import org.junit.Assert; +import org.junit.Test; + +public class JWTTest { + + @Test + public void createHs256Test(){ + byte[] key = "1234567890".getBytes(); + JWT jwt = JWT.create() + .setPayload("sub", "1234567890") + .setPayload("name", "looly") + .setPayload("admin", true) + .setKey(key); + + String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." + + "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9." + + "536690902d931d857d2f47d337ec81048ee09a8e71866bcc8404edbbcbf4cc40"; + + String token = jwt.sign(); + Assert.assertEquals(token, token); + + Assert.assertTrue(JWT.of(rightToken).setKey(key).verify()); + } + + @Test + public void parseTest(){ + String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." + + "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9." + + "536690902d931d857d2f47d337ec81048ee09a8e71866bcc8404edbbcbf4cc40"; + + final JWT jwt = JWT.of(rightToken); + + //header + Assert.assertEquals("JWT", jwt.getHeader(JWTHeader.TYPE)); + Assert.assertEquals("HS256", jwt.getHeader(JWTHeader.ALGORITHM)); + Assert.assertNull(jwt.getHeader(JWTHeader.CONTENT_TYPE)); + + //payload + Assert.assertEquals("1234567890", jwt.getPayload("sub")); + Assert.assertEquals("looly", jwt.getPayload("name")); + Assert.assertEquals(true, jwt.getPayload("admin")); + } + + @Test + public void createNoneTest(){ + JWT jwt = JWT.create() + .setPayload("sub", "1234567890") + .setPayload("name", "looly") + .setPayload("admin", true) + .setSigner(JWTSignerUtil.none()); + + String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." + + "eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9."; + + String token = jwt.sign(); + Assert.assertEquals(token, token); + + Assert.assertTrue(JWT.of(rightToken).setSigner(JWTSignerUtil.none()).verify()); + } + + /** + * 必须定义签名器 + */ + @Test(expected = JWTException.class) + public void needSignerTest(){ + JWT jwt = JWT.create() + .setPayload("sub", "1234567890") + .setPayload("name", "looly") + .setPayload("admin", true); + + jwt.sign(); + } +}