From 04e41a609860b73368b2da4a84ec093c06e5e41b Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 9 Jun 2023 22:10:55 +0800 Subject: [PATCH] add Paillier --- .../dromara/hutool/core/io/file/FileUtil.java | 13 +- .../dromara/hutool/core/io/FileUtilTest.java | 7 + .../crypto/asymmetric/BaseAsymmetric.java | 1 + .../paillier/PaillierCipherSpiImpl.java | 467 ++++++++++++++++++ .../asymmetric/paillier/PaillierCrypto.java | 136 +++++ .../asymmetric/paillier/PaillierKey.java | 92 ++++ .../paillier/PaillierKeyPairGenerator.java | 193 ++++++++ .../paillier/PaillierPrivateKey.java | 61 +++ .../paillier/PaillierPublicKey.java | 49 ++ .../asymmetric/paillier/package-info.java | 18 + .../crypto/asymmetric/PaillierTest.java | 55 +++ 11 files changed, 1080 insertions(+), 12 deletions(-) create mode 100755 hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierCipherSpiImpl.java create mode 100755 hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierCrypto.java create mode 100755 hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierKey.java create mode 100755 hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierKeyPairGenerator.java create mode 100755 hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierPrivateKey.java create mode 100755 hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierPublicKey.java create mode 100755 hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/package-info.java create mode 100755 hutool-crypto/src/test/java/org/dromara/hutool/crypto/asymmetric/PaillierTest.java diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/io/file/FileUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/io/file/FileUtil.java index 0f08ff297..81f2791e5 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/io/file/FileUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/io/file/FileUtil.java @@ -2638,18 +2638,7 @@ public class FileUtil extends PathUtil { */ public static File checkSlip(final File parentFile, final File file) throws IllegalArgumentException { if (null != parentFile && null != file) { - String parentCanonicalPath; - String canonicalPath; - try { - parentCanonicalPath = parentFile.getCanonicalPath(); - canonicalPath = file.getCanonicalPath(); - } catch (final IOException e) { - // issue#I4CWMO@Gitee - // getCanonicalPath有时会抛出奇怪的IO异常,此时忽略异常,使用AbsolutePath判断。 - parentCanonicalPath = parentFile.getAbsolutePath(); - canonicalPath = file.getAbsolutePath(); - } - if (!canonicalPath.startsWith(parentCanonicalPath)) { + if(!file.toPath().startsWith(parentFile.toPath())){ throw new IllegalArgumentException("New file is outside of the parent dir: " + file.getName()); } } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/io/FileUtilTest.java index 5a32ccbbe..86ff80dc8 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/io/FileUtilTest.java @@ -494,4 +494,11 @@ public class FileUtilTest { final byte[] bytes = FileUtil.readBytes("test.properties"); Assertions.assertEquals(125, bytes.length); } + + @Test + void checkSlipTest() { + Assertions.assertThrows(IllegalArgumentException.class, ()->{ + FileUtil.checkSlip(FileUtil.file("test/a"), FileUtil.file("test/../a")); + }); + } } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/BaseAsymmetric.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/BaseAsymmetric.java index f93b581ee..41835c7e9 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/BaseAsymmetric.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/BaseAsymmetric.java @@ -28,6 +28,7 @@ import java.util.concurrent.locks.ReentrantLock; /** * 非对称基础,提供锁、私钥和公钥的持有 * + * @param this类型 * @author Looly * @since 3.3.0 */ diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierCipherSpiImpl.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierCipherSpiImpl.java new file mode 100755 index 000000000..6bd5ce667 --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierCipherSpiImpl.java @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.crypto.asymmetric.paillier; + +import org.dromara.hutool.core.util.RandomUtil; +import org.dromara.hutool.crypto.CryptoException; + +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.NoSuchPaddingException; +import java.math.BigInteger; +import java.security.*; +import java.security.spec.AlgorithmParameterSpec; + +/** + * Paillier算法加密器
+ * 来自:https://github.com/peterstefanov/paillier + * + * @author peterstefanov + */ +class PaillierCipherSpiImpl extends CipherSpi { + + protected SecureRandom random = RandomUtil.getSecureRandom(); + protected int stateMode; + protected Key key; + protected int plaintextSize; + protected int ciphertextSize; + protected byte[] dataBuffer; + protected int lengthBuffer; + + + // region ----- init + /** + * PaillierCipher doesn't recognise any algorithm - specific initialisations + * so the algorithm specific engineInit() just calls the previous overloaded + * version of engineInit() (non-Javadoc) + */ + @Override + protected void engineInit(final int mode, final Key key, + final AlgorithmParameterSpec params, final SecureRandom random) + throws InvalidKeyException { + engineInit(mode, key, random); + } + + @Override + protected void engineInit(final int mode, final Key key, final AlgorithmParameters params, + final SecureRandom random) throws InvalidKeyException { + engineInit(mode, key, random); + } + + /** + * Initialises this cipher with key and a source of randomness + */ + @Override + protected void engineInit(final int mode, final Key key, final SecureRandom random) throws InvalidKeyException { + checkKey(key, mode); + + stateMode = mode; + this.key = key; + if (null != random) { + this.random = random; + } + final int modulusLength = ((PaillierKey) key).getN().bitLength(); + calculateBlockSizes(modulusLength); + } + // endregion + + @Override + protected byte[] engineUpdate(final byte[] input, final int inputOffset, final int inputLen) { + byte[] out = new byte[engineGetOutputSize(inputLen)]; + final int length = engineUpdate(input, inputOffset, inputLen, out, 0); + + if (length < out.length) { + final byte[] shorter = new byte[length]; + System.arraycopy(out, 0, shorter, 0, length); + out = shorter; + } + return out; + } + + /** + * Creates a single input array from the buffered data and supplied input + * data. Calculates the location and the length of the last fractional block + * in the input data. Transforms all full blocks in the input data. Save the + * last fractional block in the internal buffer. + * + * @param input - the input buffer + * @param inputOffset - the offset in input where the input starts + * @param inputLen - the input length + * @param output - the buffer for the result + * @param outputOffset - the offset in output where the result is stored + * @return the number of bytes stored in output + */ + @Override + protected int engineUpdate(final byte[] input, final int inputOffset, final int inputLen, + final byte[] output, final int outputOffset) { + // create a single array of input data + final int lengthBuffer = getDataBufferedLength(); + final byte[] totalIn = new byte[inputLen + lengthBuffer]; + readFromBufferAndReset(totalIn); + System.arraycopy(input, inputOffset, totalIn, lengthBuffer, inputLen); + + // figure out the location of last fractional block + final int blockSize = engineGetBlockSize(); + final int lastBlockSize = totalIn.length % blockSize; + final int lastBlockOffset = totalIn.length - lastBlockSize; + + // step through the array + int outputLength = 0; + for (int i = 0; i < lastBlockOffset; i += blockSize) + outputLength += engineTransformBlock(totalIn, i, blockSize, + output, outputOffset + outputLength); + + // copy the reminder into dataBuffer + addToBuffer(totalIn, lastBlockOffset, lastBlockSize); + + return outputLength; + } + + /** + * Calls the second overloaded version of the same method. + */ + @Override + protected byte[] engineDoFinal(final byte[] input, final int inputOffset, final int inputLen) { + final byte[] out = new byte[engineGetOutputSize(inputLen)]; + final int length = engineDoFinal(input, inputOffset, inputLen, out, 0); + + if (length < out.length) { + final byte[] smaller = new byte[length]; + System.arraycopy(out, 0, smaller, 0, length); + return smaller; + } + return out; + } + + /** + * Encrypts or decrypts data in a single-part operation, or finishes a + * multiple-part operation. + *

+ * Creates a single input array from the buffered data and supplied input + * data. Finds the location and the size of the last partial or full block + * in engineUpdate(),just interested in last partial block.. Transforms each + * full blocks in the input array by calling engineTransformBlock(). + * Transform the final partial or full block by calling + * engineTransformBlockFinal(). + * + * @param input - the input buffer + * @param inputOffset - the offset in input where the input starts + * @param inputLen - the input length + * @param output - the buffer for the result + * @param outputOffset - the offset in output where the result is stored + * @return the number of bytes stored in output + */ + @Override + protected int engineDoFinal(final byte[] input, final int inputOffset, final int inputLen, + final byte[] output, final int outputOffset) { + + // Create a single array of input data + final int lengthBuffer = getDataBufferedLength(); + final byte[] totalIn = new byte[inputLen + lengthBuffer]; + readFromBufferAndReset(totalIn); + if (inputLen > 0) + System.arraycopy(input, inputOffset, totalIn, lengthBuffer, + inputLen); + + // Find the location of the last partial or full block. + final int blockSize = engineGetBlockSize(); + int lastBlockSize = totalIn.length % blockSize; + if (lastBlockSize == 0 && totalIn.length > 0) + lastBlockSize = blockSize; + final int lastBlockOffset = totalIn.length - lastBlockSize; + + // Step through the array + int outputLength = 0; + for (int i = 0; i < lastBlockOffset; i += blockSize) + outputLength += engineTransformBlock(totalIn, i, blockSize, + output, outputOffset + outputLength); + + // Transform the final partial or full block + outputLength += engineTransformBlockFinal(totalIn, lastBlockOffset, + lastBlockSize, output, outputOffset + outputLength); + + return outputLength; + + } + + /** + * engineGetBlockSize() returns the appropriate size , based on cipher. + * + * @return plaintextSize - the block size(in bytes). + */ + @Override + protected int engineGetBlockSize() { + if (stateMode == Cipher.DECRYPT_MODE) + return ciphertextSize; + else + return plaintextSize; + } + + /** + * Based on the state of the cipher, figure out how many input blocks are + * represented by inputLen. Then the number of output bytes need to be + * calculated. + * + * @param inputLen the input length (in bytes) + * @return outLength - the required output buffered size (in bytes) + */ + @Override + protected int engineGetOutputSize(final int inputLen) { + final int inBlocks; + final int outLength; + if (stateMode == Cipher.ENCRYPT_MODE) { + inBlocks = (inputLen + getDataBufferedLength() + plaintextSize - 1) + / plaintextSize; + outLength = inBlocks * ciphertextSize; + } else { + inBlocks = (inputLen + getDataBufferedLength() + plaintextSize - 1) + / ciphertextSize; + outLength = inBlocks * plaintextSize; + } + return outLength; + } + + @Override + protected final void engineSetMode(final String mode) throws NoSuchAlgorithmException { + throw new NoSuchAlgorithmException("Paillier supports no modes."); + } + + @Override + protected final void engineSetPadding(final String padding) throws NoSuchPaddingException { + throw new NoSuchPaddingException("Paillier supports no padding."); + } + + /** + * This implementation runs just in Electronic Codebook(ECB) mode - + * "each block is encrypted separately of other blocks" , so this method + * returns null. + */ + @Override + protected byte[] engineGetIV() { + return null; + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + // region ----- private methods + + /** + * 检查密钥有效性 + * + * @param key 密钥 + * @param mode 模式 + * @throws InvalidKeyException 无效KEY异常 + */ + private void checkKey(final Key key, final int mode) throws InvalidKeyException { + if (mode == Cipher.ENCRYPT_MODE) { + if (!(key instanceof PaillierPublicKey)) { + throw new InvalidKeyException("I didn't get a PaillierPublicKey."); + } + } else if (mode == Cipher.DECRYPT_MODE) { + if (!(key instanceof PaillierPrivateKey)) { + throw new InvalidKeyException( + "I didn't get a PaillierPrivateKey. "); + } + } else { + throw new IllegalArgumentException("Bad mode: " + mode); + } + } + + /** + * This helper method returns an array of bytes that is only as long as it + * needs to be , ignoring the sign of the number. + * + * @param big BigInteger + * @return an array of bytes + */ + private byte[] getBytes(final BigInteger big) { + final byte[] bigBytes = big.toByteArray(); + if ((big.bitLength() % 8) != 0) { + return bigBytes; + } else { + final byte[] smallerBytes = new byte[big.bitLength() / 8]; + System.arraycopy(bigBytes, 1, smallerBytes, 0, smallerBytes.length); + return smallerBytes; + } + } + + /** + * Calculates the size of the plaintext block and a ciphertext block, based + * on the size of the key used to initialise the cipher. + * + * @param modulusLength - n = p*q + */ + private void calculateBlockSizes(final int modulusLength) { + plaintextSize = (modulusLength - 1) / 8; + ciphertextSize = ((modulusLength + 7) / 8) * 2; + + } + + /** + * This method may be passed less than a full block. + * + * @param input - byte[] + * @param inputOffset -int + * @param inputLenth -int + * @param output -byte[] + * @param outputOffset -int + * @return The number of bytes written. + */ + private int engineTransformBlockFinal(final byte[] input, final int inputOffset, + final int inputLenth, final byte[] output, final int outputOffset) { + if (inputLenth == 0) { + return 0; + } + return engineTransformBlock(input, inputOffset, inputLenth, output, outputOffset); + } + + /** + * This method is called whenever a full block needs to be + * encrypted/decrypted. The input block is contained in input.The + * transformed block is written into output starting at otputOfset. The + * number of bytes written is returned. + * + * @param input - byte[] + * @param inputOffset -int + * @param inputLenth -int + * @param output -byte[] + * @param outputOffset -int + * @return The number of bytes written. + */ + private int engineTransformBlock(final byte[] input, final int inputOffset, + final int inputLenth, final byte[] output, final int outputOffset) { + if (stateMode == Cipher.ENCRYPT_MODE) { + return encryptBlock(input, inputOffset, inputLenth, output, outputOffset); + } else if (stateMode == Cipher.DECRYPT_MODE) { + return decryptBlock(input, inputOffset, output, outputOffset); + } + return 0; + } + + /** + * Perform actual encryption ,creates single array and updates the result + * after the encryption. + * + * @param input - the input in bytes + * @param inputOffset - the offset in input where the input starts + * @param inputLenth - the input length + * @param output - the buffer for the result + * @param outputOffset - the offset in output where the result is stored + * @return the number of bytes stored in output + * @throws CryptoException throws if Plaintext m is not in Z_n , m should be less then n + */ + private int encryptBlock(final byte[] input, final int inputOffset, final int inputLenth, + final byte[] output, final int outputOffset) throws CryptoException { + final byte[] messageBytes = new byte[plaintextSize]; + final int inLenth = Math.min(plaintextSize, inputLenth); + System.arraycopy(input, inputOffset, messageBytes, 0, inLenth); + final BigInteger m = new BigInteger(1, messageBytes); + + // get the public key in order to encrypt + final PaillierPublicKey key = (PaillierPublicKey) this.key; + final BigInteger g = key.getG(); + final BigInteger n = key.getN(); + final BigInteger nsquare = key.getNSquare(); + final BigInteger r = key.generateRandomRinZn(random); + + if (m.compareTo(BigInteger.ZERO) < 0 || m.compareTo(n) >= 0) { + throw new CryptoException( + "PaillierCipher.encryptBlock :Plaintext m is not in Z_n , m should be less then n"); + } + final BigInteger c = (g.modPow(m, nsquare).multiply(r.modPow(n, nsquare))) + .mod(nsquare); + + final byte[] cBytes = getBytes(c); + System.arraycopy(cBytes, 0, output, outputOffset + ciphertextSize + - cBytes.length, cBytes.length); + + return ciphertextSize; + } + + /** + * Perform actual decryption ,creates single array for the output and updates + * the result after the decryption. + * + * @param input - the input in bytes + * @param inputOffset - the offset in input where the input starts + * @param output - the buffer for the result + * @param outputOffset - the offset in output where the result is stored + * @return the number of bytes stored in output + */ + private int decryptBlock(final byte[] input, final int inputOffset, + final byte[] output, final int outputOffset) { + final PaillierPrivateKey key = (PaillierPrivateKey) this.key; + final BigInteger u = key.getU(); + final BigInteger lambda = key.getLambda(); + final BigInteger n = key.getN(); + final BigInteger nsquare = key.getNSquare(); + + // extract c + final byte[] cBytes = new byte[ciphertextSize]; + System.arraycopy(input, inputOffset, cBytes, 0, ciphertextSize); + final BigInteger c = new BigInteger(1, cBytes); + // calculate the message + final BigInteger m = c.modPow(lambda, nsquare).subtract(BigInteger.ONE) + .divide(n).multiply(u).mod(n); + + final byte[] messageBytes = getBytes(m); + final int gatedLength = Math.min(messageBytes.length, plaintextSize); + System.arraycopy(messageBytes, 0, output, outputOffset + plaintextSize + - gatedLength, gatedLength); + return plaintextSize; + } + + /** + * Retrieved buffered data. The data will be copied into the supplied array + * and the internal buffer is reset - by setting lengthBuffer to 0 + */ + private void readFromBufferAndReset(final byte[] output) { + checkBuffer(); + System.arraycopy(dataBuffer, 0, output, 0, lengthBuffer); + lengthBuffer = 0; + } + + /** + * Returns the length of the data stored in the buffer. + * + * @return lengthBuffer + */ + private int getDataBufferedLength() { + checkBuffer(); + return lengthBuffer; + } + + /** + * Checks to see if the buffer exists. If not , or if it is not the same + * length as the block size, a new buffer created. + */ + private void checkBuffer() { + if (dataBuffer == null || dataBuffer.length != engineGetBlockSize()) { + dataBuffer = new byte[engineGetBlockSize()]; + lengthBuffer = 0; + } + } + + /** + * Adds the specified data to the internal buffer. + */ + private void addToBuffer(final byte[] input, final int offset, final int length) { + checkBuffer(); + System.arraycopy(input, offset, dataBuffer, lengthBuffer, length); + lengthBuffer += length; + } + // endregion +} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierCrypto.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierCrypto.java new file mode 100755 index 000000000..a11587e9a --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierCrypto.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.crypto.asymmetric.paillier; + +import org.dromara.hutool.crypto.CryptoException; +import org.dromara.hutool.crypto.asymmetric.AbstractAsymmetricCrypto; +import org.dromara.hutool.crypto.asymmetric.KeyType; + +import javax.crypto.Cipher; +import java.security.*; + +/** + * 同态加密算法Paillier
+ * 来自:https://github.com/peterstefanov/paillier
+ * 来自:https://github.com/dromara/hutool/pull/3131 + * + * 加法同态,存在有效算法+,E(x+y)=E(x)+E(y)或者 x+y=D(E(x)+E(y))成立,并且不泄漏 x 和 y。 + * 乘法同态,存在有效算法*,E(x×y)=E(x)*E(y)或者 xy=D(E(x)*E(y))成立,并且不泄漏 x 和 y。 + * + * 方案安全性可以归约到判定性合数剩余假设(Decisional Composite Residuosity Assumption, DCRA),即给定一个合数n和整数z,判定z是否在n^2下是否是n次剩余是困难的。 + * 这个假设经过了几十年的充分研究,到目前为止还没有多项式时间的算法可以攻破,所以Paillier加密方案的安全性被认为相当可靠。 + * + * 字符串文本加解密相互配对,此时无法使用同态加法和同态乘法 + * 数值类型不可使用字符串加解密 + * + * 公钥加密和同态加法/同态乘法运算 + * 私钥解密 + * + * @author Revers, peterstefanov + **/ +public class PaillierCrypto extends AbstractAsymmetricCrypto { + private static final long serialVersionUID = 1L; + + private final PaillierCipherSpiImpl spi; + + /** + * 构造,使用随机密钥对 + * + */ + public PaillierCrypto() { + this(PaillierKeyPairGenerator.of().generateKeyPair()); + } + + /** + * 构造 + *

+ * 私钥和公钥同时为空时生成一对新的私钥和公钥
+ * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 + * + * @param keyPair 密钥对 + */ + public PaillierCrypto(final KeyPair keyPair) { + this(keyPair.getPrivate(), keyPair.getPublic()); + } + + /** + * 构造 + *

+ * 私钥和公钥同时为空时生成一对新的私钥和公钥
+ * 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密 + * + * @param privateKey 私钥 + * @param publicKey 公钥 + */ + public PaillierCrypto(final PrivateKey privateKey, final PublicKey publicKey) { + super(PaillierKey.ALGORITHM_NAME, privateKey, publicKey); + this.spi = new PaillierCipherSpiImpl(); + } + + @Override + public byte[] encrypt(final byte[] data, final KeyType keyType) { + final Key key = getKeyByType(keyType); + lock.lock(); + try { + initMode(Cipher.ENCRYPT_MODE, key); + return doFinal(data, 0, data.length); + } catch (final Exception e) { + throw new CryptoException(e); + } finally { + lock.unlock(); + } + } + + @Override + public byte[] decrypt(final byte[] bytes, final KeyType keyType) { + final Key key = getKeyByType(keyType); + lock.lock(); + try { + initMode(Cipher.DECRYPT_MODE, key); + return doFinal(bytes, 0, bytes.length); + } catch (final Exception e) { + throw new CryptoException(e); + } finally { + lock.unlock(); + } + } + + /** + * 初始化模式 + *

+ * + * @param mode 模式,可选{@link Cipher#ENCRYPT_MODE}或{@link Cipher#DECRYPT_MODE} + * @param key 公钥或私钥 + * @return this + * @throws InvalidKeyException 密钥错误 + */ + public PaillierCrypto initMode(final int mode, final Key key) throws InvalidKeyException { + this.spi.engineInit(mode, key, null); + return this; + } + + /** + * 执行加密或解密数据 + * + * @param input 数据 + * @param inputOffset 开始位置 + * @param inputLen 处理长度 + * @return 执行后的数据 + */ + public byte[] doFinal(final byte[] input, final int inputOffset, final int inputLen) { + return this.spi.engineDoFinal(input, inputOffset, inputLen); + } +} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierKey.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierKey.java new file mode 100755 index 000000000..7f10d69b1 --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierKey.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.crypto.asymmetric.paillier; + +import java.math.BigInteger; +import java.security.Key; +import java.security.SecureRandom; + +/** + * Paillier算法密钥 + * + * @author peterstefanov + * @author looly + */ +public class PaillierKey implements Key { + private static final long serialVersionUID = 1L; + + /** + * 算法名称:Paillier + */ + public static final String ALGORITHM_NAME = "Paillier"; + + private final BigInteger n; + + protected PaillierKey(final BigInteger n) { + this.n = n; + } + + /** + * 获取N值 + * + * @return N值 + */ + public BigInteger getN() { + return n; + } + + @Override + public String getAlgorithm() { + return ALGORITHM_NAME; + } + + @Override + public String getFormat() { + return "NONE"; + } + + @Override + public byte[] getEncoded() { + return null; + } + + /** + * 获取N * N + * + * @return N * N + */ + public BigInteger getNSquare() { + return n.multiply(n); + } + + /** + * This method generates a random {@code r} in {@code Z_{n}^*} for + * each separate encryption using the same modulus n Paillier cryptosystem + * allows the generated r to differ every time, such that the same plaintext + * encrypted several times will produce different ciphertext every time. + * + * @param random {@link SecureRandom} + * @return r + */ + public BigInteger generateRandomRinZn(final SecureRandom random) { + BigInteger r; + // use the same key size as initialised + final int length = n.bitLength(); + // generate r random integer in Z*_n + do { + r = new BigInteger(length, 64, random); + } while (r.compareTo(n) >= 0 || r.gcd(n).intValue() != 1); + + return r; + } +} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierKeyPairGenerator.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierKeyPairGenerator.java new file mode 100755 index 000000000..ef182be93 --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierKeyPairGenerator.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.crypto.asymmetric.paillier; + +import org.dromara.hutool.core.util.RandomUtil; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGeneratorSpi; +import java.security.SecureRandom; + +/** + * Paillier算法密钥对生成器
+ * 参考:https://github.com/peterstefanov/paillier + * + * @author peterstefanov, looly + */ +public class PaillierKeyPairGenerator extends KeyPairGeneratorSpi { + + private static final int KEYSIZE_DEFAULT = 64; + + /** + * 创建PaillierKeyPairGenerator + * + * @return PaillierKeyPairGenerator + */ + public static PaillierKeyPairGenerator of() { + return of(KEYSIZE_DEFAULT); + } + + /** + * 创建PaillierKeyPairGenerator + * + * @param keySize 密钥长度,范围:8~3096 + * @return PaillierKeyPairGenerator + */ + public static PaillierKeyPairGenerator of(final int keySize) { + return of(keySize, null); + } + + /** + * 创建PaillierKeyPairGenerator + * + * @param keySize 密钥长度,范围:8~3096 + * @param random 随机对象 + * @return PaillierKeyPairGenerator + */ + public static PaillierKeyPairGenerator of(final int keySize, final SecureRandom random) { + final PaillierKeyPairGenerator generator = new PaillierKeyPairGenerator(); + generator.initialize(keySize, random); + return generator; + } + + private int certainty = 64; + private int keySize = KEYSIZE_DEFAULT; + private SecureRandom random; + + @Override + public void initialize(final int keySize, final SecureRandom random) { + this.random = random; + if (keySize < 8 || keySize > 3096) { + this.keySize = KEYSIZE_DEFAULT; + } else { + this.keySize = keySize; + } + } + + /** + * 设置certainty值,执行时间与此参数的值成比例。 + * + * @param certainty certainty值 + */ + public void setCertainty(final int certainty) { + this.certainty = certainty; + } + + @Override + public KeyPair generateKeyPair() { + if (null == this.random) { + this.random = RandomUtil.getSecureRandom(); + } + + final BigInteger p = generateP(); + final BigInteger q = generateQ(p); + + // n = p*q + final BigInteger n = p.multiply(q); + // nsquare = n*n + final BigInteger nsquare = n.multiply(n); + final BigInteger lambda = getLambda(p, q); + final BigInteger g = generateG(n, nsquare, lambda); + final BigInteger u = getU(g, n, nsquare, lambda); + + final PaillierPublicKey publicKey = new PaillierPublicKey(n, g); + final PaillierPrivateKey privateKey = new PaillierPrivateKey(n, lambda, u); + + return new KeyPair(publicKey, privateKey); + } + + // region ----- private methods + + /** + * 生成随机P值 + * + * @return P值 + */ + private BigInteger generateP() { + return new BigInteger(this.keySize / 2, this.certainty, this.random); + } + + /** + * 生成随机Q值 + * + * @param p P值 + * @return Q值 + */ + private BigInteger generateQ(final BigInteger p) { + BigInteger q; + do { + q = new BigInteger(this.keySize / 2, this.certainty, this.random); + } while (q.compareTo(p) == 0); + + return q; + } + + /** + * 生成G + * + * @param n N值 + * @param nsquare n*n + * @param lambda lambda值 + * @return G值 + */ + private BigInteger generateG(final BigInteger n, final BigInteger nsquare, final BigInteger lambda) { + BigInteger g; + do { + // generate g, a random integer in Z*_{n^2} + do { + g = new BigInteger(this.keySize, 64, this.random); + } while (g.compareTo(nsquare) >= 0 + || g.gcd(nsquare).intValue() != 1); + + // verify g, the following must hold: gcd(L(g^lambda mod n^2), n) = + // 1, + // where L(u) = (u-1)/n + } while (g.modPow(lambda, nsquare).subtract(BigInteger.ONE).divide(n) + .gcd(n).intValue() != 1); + + return g; + } + + /** + * lambda = lcm(p-1, q-1) = (p-1)*(q-1)/gcd(p-1, q-1) + * + * @param p P值 + * @param q Q值 + * @return Lambda + */ + private static BigInteger getLambda(final BigInteger p, final BigInteger q) { + return p.subtract(BigInteger.ONE) + .multiply(q.subtract(BigInteger.ONE)) + .divide(p.subtract(BigInteger.ONE) + .gcd(q.subtract(BigInteger.ONE))); + } + + /** + * 获取U(MU)值 + * + * @param g Q值 + * @param n N值 + * @param nsquare n*n + * @param lambda lambda值 + * @return U值 + */ + private static BigInteger getU(final BigInteger g, final BigInteger n, final BigInteger nsquare, final BigInteger lambda) { + // u = (L(g^lambda mod n^2))^{-1} mod n, where L(u) = (u-1)/n + return g.modPow(lambda, nsquare) + .subtract(BigInteger.ONE) + .divide(n) + .modInverse(n); + } + // endregion +} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierPrivateKey.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierPrivateKey.java new file mode 100755 index 000000000..bfe3ce167 --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierPrivateKey.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.crypto.asymmetric.paillier; + +import org.dromara.hutool.core.lang.Assert; + +import java.math.BigInteger; +import java.security.PrivateKey; + +/** + * Paillier算法公钥 + * + * @author peterstefanov, Revers, looly + */ +public class PaillierPrivateKey extends PaillierKey implements PrivateKey { + private static final long serialVersionUID = 1L; + + private final BigInteger u; + private final BigInteger lambda; + + /** + * 构造 + * + * @param n N值 + * @param lambda lambda值 + * @param u U值 + */ + public PaillierPrivateKey(final BigInteger n, final BigInteger lambda, final BigInteger u) { + super(Assert.notNull(n)); + this.lambda = Assert.notNull(lambda); + this.u = Assert.notNull(u); + } + + /** + * 获取lambda值 + * + * @return lambda值 + */ + public BigInteger getLambda() { + return lambda; + } + + /** + * 获取U值 + * + * @return U值 + */ + public BigInteger getU() { + return u; + } +} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierPublicKey.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierPublicKey.java new file mode 100755 index 000000000..6bcb6f4d9 --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/PaillierPublicKey.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.crypto.asymmetric.paillier; + +import org.dromara.hutool.core.lang.Assert; + +import java.math.BigInteger; +import java.security.PublicKey; + +/** + * Paillier算法公钥 + * + * @author peterstefanov, Revers, looly + */ +public class PaillierPublicKey extends PaillierKey implements PublicKey { + private static final long serialVersionUID = 1L; + + private final BigInteger g; + + /** + * 构造 + * + * @param n N值 + * @param g G值 + */ + public PaillierPublicKey(final BigInteger n, final BigInteger g) { + super(Assert.notNull(n)); + this.g = Assert.notNull(g); + } + + /** + * 获取G值 + * + * @return G值 + */ + public BigInteger getG() { + return g; + } +} diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/package-info.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/package-info.java new file mode 100755 index 000000000..0cd815165 --- /dev/null +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/asymmetric/paillier/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +/** + * 同态加密算法 Paillier + * + * @author Revers + */ +package org.dromara.hutool.crypto.asymmetric.paillier; diff --git a/hutool-crypto/src/test/java/org/dromara/hutool/crypto/asymmetric/PaillierTest.java b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/asymmetric/PaillierTest.java new file mode 100755 index 000000000..b4c3f3a83 --- /dev/null +++ b/hutool-crypto/src/test/java/org/dromara/hutool/crypto/asymmetric/PaillierTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.crypto.asymmetric; + +import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.crypto.asymmetric.paillier.PaillierCrypto; +import org.dromara.hutool.crypto.asymmetric.paillier.PaillierKeyPairGenerator; +import org.dromara.hutool.crypto.asymmetric.paillier.PaillierProvider; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.Security; + +public class PaillierTest { + @Test + void keyPairGeneratorTest() { + final PaillierKeyPairGenerator generator = PaillierKeyPairGenerator.of(); + final KeyPair keyPair = generator.generateKeyPair(); + Assertions.assertNotNull(keyPair.getPrivate()); + Assertions.assertNotNull(keyPair.getPublic()); + } + + @Test + void keyPairGeneratorByJceTest() throws NoSuchAlgorithmException { + Security.addProvider(new PaillierProvider()); + final KeyPairGenerator generator = KeyPairGenerator.getInstance("Paillier"); + final KeyPair keyPair = generator.generateKeyPair(); + Assertions.assertNotNull(keyPair.getPrivate()); + Assertions.assertNotNull(keyPair.getPublic()); + } + + @Test + void encryptAndDecryptTest() { + final String text = "Hutool测试字符串"; + + final PaillierCrypto crypto = new PaillierCrypto(); + final byte[] encrypt = crypto.encrypt(text, KeyType.PublicKey); + + final byte[] decrypt = crypto.decrypt(encrypt, KeyType.PrivateKey); + Assertions.assertEquals(text, StrUtil.utf8Str(decrypt)); + } +}