com.dahuatech.hutool.crypto.asymmetric.SM2Engine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-sdk-common Show documentation
Show all versions of java-sdk-common Show documentation
Dahua ICC Open API SDK for Java
package com.dahuatech.hutool.crypto.asymmetric;
import com.dahuatech.hutool.core.util.ObjectUtil;
import com.dahuatech.hutool.crypto.CryptoException;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.math.ec.*;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;
import java.math.BigInteger;
import java.util.Random;
/**
* SM2加密解密引擎,来自Bouncy Castle库的SM2Engine类改造
* SM2加密后的数据格式为(两种模式):
*
*
* curve(C1) | data(C2) | digest(C3)
* curve(C1) | digest(C3) | data(C2)
*
*
* @author looly, bouncycastle
* @since 4.5.0
*/
public class SM2Engine {
private final Digest digest;
private boolean forEncryption;
private ECKeyParameters ecKey;
private ECDomainParameters ecParams;
private int curveLength;
private Random random;
/** 加密解密模式 */
private SM2Mode mode;
/** 构造 */
public SM2Engine() {
this(new SM3Digest());
}
/**
* 构造
*
* @param mode SM2密钥生成模式,可选C1C2C3和C1C3C2
*/
public SM2Engine(SM2Mode mode) {
this(new SM3Digest(), mode);
}
/**
* 构造
*
* @param digest 摘要算法啊
*/
public SM2Engine(Digest digest) {
this(digest, null);
}
/**
* 构造
*
* @param digest 摘要算法啊
* @param mode SM2密钥生成模式,可选C1C2C3和C1C3C2
*/
public SM2Engine(Digest digest, SM2Mode mode) {
this.digest = digest;
this.mode = ObjectUtil.defaultIfNull(mode, SM2Mode.C1C3C2);
}
/**
* 初始化引擎
*
* @param forEncryption 是否为加密模式
* @param param {@link CipherParameters},此处应为{@link ParametersWithRandom}(加密时)或{@link
* ECKeyParameters}(解密时)
*/
public void init(boolean forEncryption, CipherParameters param) {
this.forEncryption = forEncryption;
if (param instanceof ParametersWithRandom) {
final ParametersWithRandom rParam = (ParametersWithRandom) param;
this.ecKey = (ECKeyParameters) rParam.getParameters();
this.random = rParam.getRandom();
} else {
this.ecKey = (ECKeyParameters) param;
}
this.ecParams = this.ecKey.getParameters();
if (forEncryption) {
// 检查曲线点
final ECPoint ecPoint = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH());
if (ecPoint.isInfinity()) {
throw new IllegalArgumentException("invalid key: [h]Q at infinity");
}
// 检查随机参数
if (null == this.random) {
this.random = CryptoServicesRegistrar.getSecureRandom();
}
}
// 曲线位长度
this.curveLength = (this.ecParams.getCurve().getFieldSize() + 7) / 8;
}
/**
* 处理块,包括加密和解密
*
* @param in 数据
* @param inOff 数据开始位置
* @param inLen 数据长度
* @return 结果
*/
public byte[] processBlock(byte[] in, int inOff, int inLen) {
if (forEncryption) {
return encrypt(in, inOff, inLen);
} else {
return decrypt(in, inOff, inLen);
}
}
/**
* 设置加密类型
*
* @param mode {@link SM2Mode}
* @return this
*/
public SM2Engine setMode(SM2Mode mode) {
this.mode = mode;
return this;
}
protected ECMultiplier createBasePointMultiplier() {
return new FixedPointCombMultiplier();
}
/**
* 加密
*
* @param in 数据
* @param inOff 位置
* @param inLen 长度
* @return 密文
*/
private byte[] encrypt(byte[] in, int inOff, int inLen) {
// 加密数据
byte[] c2 = new byte[inLen];
System.arraycopy(in, inOff, c2, 0, c2.length);
final ECMultiplier multiplier = createBasePointMultiplier();
byte[] c1;
ECPoint kPB;
BigInteger k;
do {
k = nextK();
// 产生随机数计算出曲线点C1
c1 = multiplier.multiply(ecParams.getG(), k).normalize().getEncoded(false);
kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize();
kdf(kPB, c2);
} while (notEncrypted(c2, in, inOff));
// 杂凑值,效验数据
byte[] c3 = new byte[digest.getDigestSize()];
addFieldElement(kPB.getAffineXCoord());
this.digest.update(in, inOff, inLen);
addFieldElement(kPB.getAffineYCoord());
this.digest.doFinal(c3, 0);
// 按照对应模式输出结果
switch (mode) {
case C1C3C2:
return Arrays.concatenate(c1, c3, c2);
default:
return Arrays.concatenate(c1, c2, c3);
}
}
// --------------------------------------------------------------------------------------------------- Private method start
/**
* 解密,只支持私钥解密
*
* @param in 密文
* @param inOff 位置
* @param inLen 长度
* @return 解密后的内容
*/
private byte[] decrypt(byte[] in, int inOff, int inLen) {
// 获取曲线点
final byte[] c1 = new byte[this.curveLength * 2 + 1];
System.arraycopy(in, inOff, c1, 0, c1.length);
ECPoint c1P = this.ecParams.getCurve().decodePoint(c1);
if (c1P.multiply(this.ecParams.getH()).isInfinity()) {
throw new CryptoException("[h]C1 at infinity");
}
c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize();
final int digestSize = this.digest.getDigestSize();
// 解密C2数据
final byte[] c2 = new byte[inLen - c1.length - digestSize];
if (SM2Mode.C1C3C2 == this.mode) {
// C2位于第三部分
System.arraycopy(in, inOff + c1.length + digestSize, c2, 0, c2.length);
} else {
// C2位于第二部分
System.arraycopy(in, inOff + c1.length, c2, 0, c2.length);
}
kdf(c1P, c2);
// 使用摘要验证C2数据
final byte[] c3 = new byte[digestSize];
addFieldElement(c1P.getAffineXCoord());
this.digest.update(c2, 0, c2.length);
addFieldElement(c1P.getAffineYCoord());
this.digest.doFinal(c3, 0);
int check = 0;
for (int i = 0; i != c3.length; i++) {
check |= c3[i] ^ in[inOff + c1.length + ((SM2Mode.C1C3C2 == this.mode) ? 0 : c2.length) + i];
}
Arrays.fill(c1, (byte) 0);
Arrays.fill(c3, (byte) 0);
if (check != 0) {
Arrays.fill(c2, (byte) 0);
throw new CryptoException("invalid cipher text");
}
return c2;
}
private boolean notEncrypted(byte[] encData, byte[] in, int inOff) {
for (int i = 0; i != encData.length; i++) {
if (encData[i] != in[inOff + i]) {
return false;
}
}
return true;
}
/**
* 解密数据
*
* @param c1 c1点
* @param encData 密文
*/
private void kdf(ECPoint c1, byte[] encData) {
final Digest digest = this.digest;
int digestSize = digest.getDigestSize();
byte[] buf = new byte[Math.max(4, digestSize)];
int off = 0;
Memoable memo = null;
Memoable copy = null;
if (digest instanceof Memoable) {
addFieldElement(c1.getAffineXCoord());
addFieldElement(c1.getAffineYCoord());
memo = (Memoable) digest;
copy = memo.copy();
}
int ct = 0;
while (off < encData.length) {
if (memo != null) {
memo.reset(copy);
} else {
addFieldElement(c1.getAffineXCoord());
addFieldElement(c1.getAffineYCoord());
}
Pack.intToBigEndian(++ct, buf, 0);
digest.update(buf, 0, 4);
digest.doFinal(buf, 0);
int xorLen = Math.min(digestSize, encData.length - off);
xor(encData, buf, off, xorLen);
off += xorLen;
}
}
/**
* 异或
*
* @param data 数据
* @param kdfOut kdf输出值
* @param dOff d偏移
* @param dRemaining d剩余
*/
private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) {
for (int i = 0; i != dRemaining; i++) {
data[dOff + i] ^= kdfOut[i];
}
}
/**
* 下一个K值
*
* @return K值
*/
private BigInteger nextK() {
final int qBitLength = this.ecParams.getN().bitLength();
BigInteger k;
do {
k = new BigInteger(qBitLength, this.random);
} while (k.equals(ECConstants.ZERO) || k.compareTo(this.ecParams.getN()) >= 0);
return k;
}
/**
* 增加字段节点
*
* @param v 节点
*/
private void addFieldElement(ECFieldElement v) {
final byte[] p = BigIntegers.asUnsignedByteArray(this.curveLength, v.toBigInteger());
this.digest.update(p, 0, p.length);
}
/**
* SM2算法模式
* 在SM2算法中,C1C2C3为旧标准模式,C1C3C2为新标准模式
*
* @author looly
*/
public enum SM2Mode {
C1C2C3,
C1C3C2
}
// --------------------------------------------------------------------------------------------------- Private method start
}