mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
add Paillier
This commit is contained in:
parent
5d26b29103
commit
04e41a6098
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
/**
|
||||
* 非对称基础,提供锁、私钥和公钥的持有
|
||||
*
|
||||
* @param <T> this类型
|
||||
* @author Looly
|
||||
* @since 3.3.0
|
||||
*/
|
||||
|
@ -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算法加密器<br>
|
||||
* 来自: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.
|
||||
* <p>
|
||||
* 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
|
||||
}
|
@ -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<br>
|
||||
* 来自:https://github.com/peterstefanov/paillier<br>
|
||||
* 来自: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<PaillierCrypto> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final PaillierCipherSpiImpl spi;
|
||||
|
||||
/**
|
||||
* 构造,使用随机密钥对
|
||||
*
|
||||
*/
|
||||
public PaillierCrypto() {
|
||||
this(PaillierKeyPairGenerator.of().generateKeyPair());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* <p>
|
||||
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
|
||||
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
|
||||
*
|
||||
* @param keyPair 密钥对
|
||||
*/
|
||||
public PaillierCrypto(final KeyPair keyPair) {
|
||||
this(keyPair.getPrivate(), keyPair.getPublic());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* <p>
|
||||
* 私钥和公钥同时为空时生成一对新的私钥和公钥<br>
|
||||
* 私钥和公钥可以单独传入一个,如此则只能使用此钥匙来做加密或者解密
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化模式
|
||||
* <ul>
|
||||
* <li>加密模式下,使用{@link Cipher#ENCRYPT_MODE},密钥使用公钥</li>
|
||||
* <li>解密模式下,使用{@link Cipher#DECRYPT_MODE},密钥使用私钥</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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算法密钥对生成器<br>
|
||||
* 参考: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
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
@ -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));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user