簡介
SM2是非對稱加密算法酝静,一提非對稱加密算法辩棒,第一想到的是RSA狼忱,沒錯,這個就是替代RSA的一睁。
它是基于橢圓曲線密碼的公鑰密碼算法標準钻弄,其秘鑰長度256bit,包含數(shù)字簽名者吁、密鑰交換和公鑰加密窘俺,用于替換RSA/DH/ECDSA/ECDH等國際算法「吹剩可以滿足電子認證服務系統(tǒng)等應用需求瘤泪,由國家密碼管理局于2010年12月17號發(fā)布。
SM2采用的是ECC 256位的一種育八,其安全強度比RSA 2048位高对途,且運算速度快于RSA。隨著密碼技術和計算技術的發(fā)展髓棋,目前常用的1024位RSA算法面臨嚴重的安全威脅掀宋,我們國家密碼管理部門經(jīng)過研究,決定采用SM2橢圓曲線算法替換RSA算法仲锄。SM2算法在安全性、性能上都具有優(yōu)勢湃鹊。
用途
可以用于前后端傳輸數(shù)據(jù)加密解密
可以用于對數(shù)據(jù)加簽驗簽儒喊,確保報文的安全性和完整性。
比如币呵,生成一套前端公私鑰密鑰對怀愧,生成一套后端服務器公私鑰密鑰對侨颈。
前端把參數(shù)json字符串通過服務器公鑰用sm2算法加密,服務器后端接收到請求后用服務器私鑰解密芯义,拿到原始參數(shù)哈垢,處理數(shù)據(jù)并生成響應數(shù)據(jù),把響應數(shù)據(jù)用前端公鑰加密扛拨,前端接收到響應加密后數(shù)據(jù)耘分,用前端私鑰解密,拿到響應json绑警。這個過程是快速且安全的求泰。
一般這個過程在網(wǎng)關上公共實現(xiàn)。
java使用
maven引入bcprov-jdk15on jar包,截止發(fā)文计盒,最新版本是1.70
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
新建StandardSM2Engine實體類
package com.zhaohy.app.utils;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine.Mode;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECMultiplier;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;
/**
* 自定義SM2Engine類渴频,對加密解密數(shù)據(jù)進行了ASN.1編碼
*/
public class StandardSM2Engine {
private final Digest digest;
private final Mode mode;
private boolean forEncryption;
private ECKeyParameters ecKey;
private ECDomainParameters ecParams;
private int curveLength;
private SecureRandom random;
public StandardSM2Engine() {
this(new SM3Digest());
}
public StandardSM2Engine(Mode mode) {
this(new SM3Digest(), mode);
}
public StandardSM2Engine(Digest digest) {
this(digest, Mode.C1C2C3);
}
public StandardSM2Engine(Digest digest, Mode mode) {
if (mode == null) {
throw new IllegalArgumentException("mode cannot be NULL");
}
this.digest = digest;
this.mode = mode;
}
public void init(boolean forEncryption, CipherParameters param) {
this.forEncryption = forEncryption;
if (forEncryption) {
ParametersWithRandom rParam = (ParametersWithRandom) param;
ecKey = (ECKeyParameters) rParam.getParameters();
ecParams = ecKey.getParameters();
ECPoint s = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH());
if (s.isInfinity()) {
throw new IllegalArgumentException("invalid key: [h]Q at infinity");
}
random = rParam.getRandom();
} else {
ecKey = (ECKeyParameters) param;
ecParams = ecKey.getParameters();
}
curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8;
}
public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
if (forEncryption) {
return encrypt(in, inOff, inLen);
} else {
return decrypt(in, inOff, inLen);
}
}
public int getOutputSize(int inputLen) {
return (1 + 2 * curveLength) + inputLen + digest.getDigestSize();
}
protected ECMultiplier createBasePointMultiplier() {
return new FixedPointCombMultiplier();
}
/**
* 加密
*
* @param in
* @param inOff
* @param inLen
* @return
* @throws InvalidCipherTextException
*/
private byte[] encrypt(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
byte[] c2 = new byte[inLen];
System.arraycopy(in, inOff, c2, 0, c2.length);
ECMultiplier multiplier = createBasePointMultiplier();
ECPoint c1P;
ECPoint kPB;
do {
BigInteger k = nextK();
c1P = multiplier.multiply(ecParams.getG(), k).normalize();
kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize();
kdf(digest, kPB, c2);
} while (notEncrypted(c2, in, inOff));
byte[] c3 = new byte[digest.getDigestSize()];
addFieldElement(digest, kPB.getAffineXCoord());
digest.update(in, inOff, inLen);
addFieldElement(digest, kPB.getAffineYCoord());
digest.doFinal(c3, 0);
return convertToASN1(c1P, c2, c3);
}
/**
* 解密
*
* @param in
* @param inOff
* @param inLen
* @return
* @throws InvalidCipherTextException
*/
private byte[] decrypt(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
byte[] decryptData = new byte[inLen];
System.arraycopy(in, inOff, decryptData, 0, decryptData.length);
BigInteger x;
BigInteger y;
byte[] originC3;
byte[] c2;
ECPoint c1P;
byte[] c1;
try (ASN1InputStream aIn = new ASN1InputStream(decryptData)) {
ASN1Sequence seq;
try {
seq = (ASN1Sequence) aIn.readObject();
} catch (IOException e) {
throw new InvalidCipherTextException();
}
x = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue();
y = ASN1Integer.getInstance(seq.getObjectAt(1)).getValue();
c1P = ecParams.getCurve().validatePoint(x, y);
c1 = c1P.getEncoded(false);
if (mode == Mode.C1C3C2) {
originC3 = ASN1OctetString.getInstance(seq.getObjectAt(2)).getOctets();
c2 = ASN1OctetString.getInstance(seq.getObjectAt(3)).getOctets();
} else {
c2 = ASN1OctetString.getInstance(seq.getObjectAt(2)).getOctets();
originC3 = ASN1OctetString.getInstance(seq.getObjectAt(3)).getOctets();
}
} catch (IOException e) {
throw new InvalidCipherTextException();
}
ECPoint s = c1P.multiply(ecParams.getH());
if (s.isInfinity()) {
throw new InvalidCipherTextException("[h]C1 at infinity");
}
c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize();
kdf(digest, c1P, c2);
byte[] c3 = new byte[digest.getDigestSize()];
addFieldElement(digest, c1P.getAffineXCoord());
digest.update(c2, 0, c2.length);
addFieldElement(digest, c1P.getAffineYCoord());
digest.doFinal(c3, 0);
int check = 0;
for (int i = 0; i != c3.length; i++) {
check |= c3[i] ^ originC3[i];
}
Arrays.fill(c1, (byte) 0);
Arrays.fill(c3, (byte) 0);
if (check != 0) {
Arrays.fill(c2, (byte) 0);
throw new InvalidCipherTextException("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;
}
private void kdf(Digest digest, ECPoint c1, byte[] encData) {
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(digest, c1.getAffineXCoord());
addFieldElement(digest, c1.getAffineYCoord());
memo = (Memoable) digest;
copy = memo.copy();
}
int ct = 0;
while (off < encData.length) {
if (memo != null) {
memo.reset(copy);
} else {
addFieldElement(digest, c1.getAffineXCoord());
addFieldElement(digest, 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;
}
}
private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) {
for (int i = 0; i != dRemaining; i++) {
data[dOff + i] ^= kdfOut[i];
}
}
private BigInteger nextK() {
int qBitLength = ecParams.getN().bitLength();
BigInteger k;
do {
k = BigIntegers.createRandomBigInteger(qBitLength, random);
} while (k.equals(BigIntegers.ZERO) || k.compareTo(ecParams.getN()) >= 0);
return k;
}
private void addFieldElement(Digest digest, ECFieldElement v) {
byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger());
digest.update(p, 0, p.length);
}
private byte[] convertToASN1(ECPoint c1P, byte[] c2, byte[] c3) {
ASN1Integer x = new ASN1Integer(c1P.getXCoord().toBigInteger());
ASN1Integer y = new ASN1Integer(c1P.getYCoord().toBigInteger());
DEROctetString derDig = new DEROctetString(c3);
DEROctetString derEnc = new DEROctetString(c2);
ASN1EncodableVector v = new ASN1EncodableVector();
switch (mode) {
case C1C3C2:
v.add(x);
v.add(y);
v.add(derDig);
v.add(derEnc);
break;
default:
v.add(x);
v.add(y);
v.add(derEnc);
v.add(derDig);
}
DERSequence seq = new DERSequence(v);
try {
return seq.getEncoded();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
新建Sm2Utils工具類
package com.zhaohy.app.utils;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* SM2工具類
*/
public class Sm2Utils {
/**
* 加簽
* @param plainText
* @return
*/
public static String sign(String plainText, String privateKeyStr) {
BouncyCastleProvider provider = new BouncyCastleProvider();
try {
// 獲取橢圓曲線KEY生成器
KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
byte[] privateKeyData = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyData);
Signature rsaSignature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), provider);
rsaSignature.initSign(keyFactory.generatePrivate(privateKeySpec));
rsaSignature.update(plainText.getBytes());
byte[] signed = rsaSignature.sign();
return Base64.getEncoder().encodeToString(signed);
} catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException e) {
throw new RuntimeException(e);
}
}
/**
* 驗簽
* @param plainText
* @param signatureValue
* @return
*/
public static boolean verify(String plainText, String signatureValue, String publicKeyStr) {
BouncyCastleProvider provider = new BouncyCastleProvider();
try {
// 獲取橢圓曲線KEY生成器
KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
byte[] publicKeyData = Base64.getDecoder().decode(publicKeyStr);
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyData);
// 初始化為驗簽狀態(tài)
Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), provider);
signature.initVerify(keyFactory.generatePublic(publicKeySpec));
signature.update(Hex.decodeHex(plainText.toCharArray()));
return signature.verify(Hex.decodeHex(signatureValue.toCharArray()));
} catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
LogUtils.error("驗簽失敗", e);
return false;
} catch (DecoderException e) {
LogUtils.error("驗簽失敗", e);
return false;
}
}
/**
* 加密
* @param plainText
* @return
*/
public static byte[] encrypt(String plainText, String publicKeyStr) throws Exception {
Security.addProvider(new BouncyCastleProvider());
try {
// 獲取橢圓曲線KEY生成器
KeyFactory keyFactory = KeyFactory.getInstance("EC");
byte[] publicKeyData = Base64.getDecoder().decode(publicKeyStr);
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyData);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
CipherParameters publicKeyParamerters = ECUtil.generatePublicKeyParameter(publicKey);
//數(shù)據(jù)加密
StandardSM2Engine engine = new StandardSM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2);
engine.init(true, new ParametersWithRandom(publicKeyParamerters));
byte[] encryptData = engine.processBlock(plainText.getBytes(), 0, plainText.getBytes().length);
return encryptData;
} catch (NoSuchAlgorithmException | InvalidKeySpecException
| InvalidKeyException | InvalidCipherTextException e) {
throw new RuntimeException(e);
}
}
/**
* 解密
* @param encryptedText
* @return
*/
public static String decrypt(byte[] encryptedData, String privateKeyStr) {
Security.addProvider(new BouncyCastleProvider());
try {
// 獲取橢圓曲線KEY生成器
KeyFactory keyFactory = KeyFactory.getInstance("EC");
byte[] privateKeyData = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyData);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
CipherParameters privateKeyParamerters = ECUtil.generatePrivateKeyParameter(privateKey);
//數(shù)據(jù)解密
StandardSM2Engine engine = new StandardSM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2);
engine.init(false, privateKeyParamerters);
byte[] plainText = engine.processBlock(encryptedData, 0, encryptedData.length);
return new String(plainText);
} catch (NoSuchAlgorithmException | InvalidKeySpecException
| InvalidKeyException | InvalidCipherTextException e) {
throw new RuntimeException(e);
}
}
/**
* SM2算法生成密鑰對
* @return 密鑰對信息
*/
public static KeyPair generateSm2KeyPair() {
try {
final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
// 獲取一個橢圓曲線類型的密鑰對生成器
final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
SecureRandom random = new SecureRandom();
// 使用SM2的算法區(qū)域初始化密鑰生成器
kpg.initialize(sm2Spec, random);
// 獲取密鑰對
KeyPair keyPair = kpg.generateKeyPair();
return keyPair;
} catch (Exception e) {
LogUtils.error("generate sm2 key pair failed:{}", e.getMessage(), e);
return null;
}
}
public static void main(String[] args) throws Exception {
KeyPair keyPair = generateSm2KeyPair();
String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
String data = "{\"daId\":\"123456\"}";
String encryptedJsonStr = Hex.encodeHexString(encrypt(data, publicKey)) + "";//16進制字符串
String decryptedJsonStr = decrypt(Hex.decodeHex(encryptedJsonStr), privateKey);
String sign = Hex.encodeHexString(Base64.getDecoder().decode(sign(data, privateKey)));
boolean flag = verify(Hex.encodeHexString(data.getBytes()), sign, publicKey);
System.out.println("base64后privateKey:" + privateKey);
System.out.println("base64后publicKey:" + publicKey);
System.out.println("加密前數(shù)據(jù):" + data);
System.out.println("公鑰加密后16進制字符串:" + encryptedJsonStr);
System.out.println("私鑰解密后數(shù)據(jù):" + decryptedJsonStr);
System.out.println("私鑰加簽后數(shù)據(jù)(16進制):" + sign);
System.out.println("公鑰驗簽結果:" + flag);
}
}
可以看到工具類里已經(jīng)寫好了調用測試main方法,運行結果如下:
base64后privateKey:MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgmze2hmNoi3XpeMPmLNIQBOC7+P1iMbwGMMAdQXko0VWgCgYIKoEcz1UBgi2hRANCAATyF9jHsCvFaLkR4DS+viX9CShZEXc7Nc1OueDaIpzZx/DRTRejSpTOcFmb0B9TsyYutnRWAx46nfkQ289NVXjg
base64后publicKey:MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE8hfYx7ArxWi5EeA0vr4l/QkoWRF3OzXNTrng2iKc2cfw0U0Xo0qUznBZm9AfU7MmLrZ0VgMeOp35ENvPTVV44A==
加密前數(shù)據(jù):{"daId":"123456"}
公鑰加密后16進制字符串:307b022100f1ab8b4fc8755d2ab84bf530f15ab14f5250d060d52679fd38e822a85eeceb860221009eef85a579ef9e2f27f44461a89046b95f8c630773be7ced1f3f3f48057f4777042064d9b5fb396591e022791858e334fc40b1ee9c93412442d403385266a00dd78704117b30ed38afcb361f8176acbbfbabad71e9
私鑰解密后數(shù)據(jù):{"daId":"123456"}
私鑰加簽后數(shù)據(jù)(16進制):304402201505caa731d9b70987a6c19f40618593e9acd8cad880dc001e52ef4b48638e2302204bdb5c5dc881789e716de7360aa387e5e4caa9f9f2fd6d1fbce35f1227b34440
公鑰驗簽結果:true
可以看到加密解密加簽驗簽都是成功的北启。
上面加密后生成的是byte[]卜朗,轉換成16進制字符串處理的,也可以用base64實現(xiàn)byte[]和字符串的轉換處理咕村,但是要保證對應加解密都是用base64實現(xiàn)byte[]和字符串的轉換场钉,對應的上就行。
java實現(xiàn)byte[]和16進制字符串之間的轉換是通過Apache Commons Codec包來實現(xiàn)的培廓,一般springboot里有依賴這個惹悄,如果沒有,則maven引入:
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>