add Paillier

This commit is contained in:
Looly 2023-06-09 22:10:55 +08:00
parent 5d26b29103
commit 04e41a6098
11 changed files with 1080 additions and 12 deletions

View File

@ -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());
}
}

View File

@ -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"));
});
}
}

View File

@ -28,6 +28,7 @@ import java.util.concurrent.locks.ReentrantLock;
/**
* 非对称基础提供锁私钥和公钥的持有
*
* @param <T> this类型
* @author Looly
* @since 3.3.0
*/

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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)));
}
/**
* 获取UMU
*
* @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
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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));
}
}