上一篇博客介绍了SM2算法生成密钥的过程,详见-SM2生成密钥。这篇博客接着介绍SM2算法如何用上篇博客生成的密钥进行加解密操作。
因为密钥都是byte数组,在进行加解密前,我们需要将密钥转换成BC库的CipherParameters。代码如下:
/**
* 私钥转换为 {@link ECPrivateKeyParameters}
* @param key key
* @return
* @throws InvalidKeyException
*/
public static ECPrivateKeyParameters privateKeyToParams(String algorithm, byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException {
Preconditions.checkNotNull(key, "key must be not null !");
PrivateKey privateKey = generatePrivateKey(algorithm, key);
return (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey);
}
/**
* 生成私钥
* @param algorithm 算法
* @param key key
* @return
*/
public static PrivateKey generatePrivateKey(String algorithm, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException {
Preconditions.checkNotNull(algorithm, "algorithm must be not null !");
Preconditions.checkNotNull(key, "key must be not null !");
KeySpec keySpec = new PKCS8EncodedKeySpec(key);
algorithm = getAlgorithmAfterWith(algorithm);
return getKeyFactory(algorithm).generatePrivate(keySpec);
}
/**
* 公钥转换为 {@link ECPublicKeyParameters}
* @param key key
* @return
* @throws InvalidKeyException
*/
public static ECPublicKeyParameters publicKeyToParams(String algorithm, byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException {
Preconditions.checkNotNull(key, "key must be not null !");
PublicKey publicKey = generatePublicKey(algorithm, key);
return (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey);
}
/**
* 生成公钥
* @param algorithm 算法
* @param key key
* @return
*/
public static PublicKey generatePublicKey(String algorithm, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException {
Preconditions.checkNotNull(algorithm, "algorithm must be not null !");
Preconditions.checkNotNull(key, "key must be not null !");
KeySpec keySpec = new X509EncodedKeySpec(key);
algorithm = getAlgorithmAfterWith(algorithm);
return getKeyFactory(algorithm).generatePublic(keySpec);
}
/**
* 获取用于密钥生成的算法
* 获取XXXwithXXX算法的后半部分算法,如果为ECDSA或SM2,返回算法为EC
* @param algorithm XXXwithXXX算法
* @return 算法
*/
private static String getAlgorithmAfterWith(String algorithm) {
Preconditions.checkNotNull(algorithm, "algorithm must be not null !");
int indexOfWith = StringUtils.lastIndexOfIgnoreCase(algorithm, "with");
if (indexOfWith > 0) {
algorithm = StringUtils.substring(algorithm, indexOfWith + "with".length());
}
if ("ECDSA".equalsIgnoreCase(algorithm) || "SM2".equalsIgnoreCase(algorithm)) {
algorithm = "EC";
}
return algorithm;
}
/**
* 获取{@link KeyFactory}
* @param algorithm 非对称加密算法
* @return {@link KeyFactory}
*/
private static KeyFactory getKeyFactory(String algorithm) throws NoSuchAlgorithmException {
final Provider provider = new BouncyCastleProvider();
return KeyFactory.getInstance(algorithm, provider);
}
SM2操作主要有四类:加密、解密、签名和验签。代码如下:
/**
* 加密
* @param data 数据
* @param publicKey 公钥
* @return 加密之后的数据
*/
public static byte[] encrypt(byte[] data, byte[] publicKey) throws Exception {
CipherParameters pubKeyParameters = new ParametersWithRandom(publicKeyToParams("SM2", publicKey));
SM2Engine engine = new SM2Engine(DIGEST);
engine.init(true, pubKeyParameters);
return engine.processBlock(data, 0, data.length);
}
/**
* 解密
* @param data 数据
* @param privateKey 私钥
* @return 解密之后的数据
*/
public static byte[] decrypt(byte[] data, byte[] privateKey) throws Exception {
CipherParameters privateKeyParameters = privateKeyToParams("SM2", privateKey);
SM2Engine engine = new SM2Engine(DIGEST);
engine.init(false, privateKeyParameters);
byte[] byteDate = engine.processBlock(data, 0, data.length);
return byteDate;
}
/**
* 签名
* @param data 数据
* @return 签名
*/
public static byte[] sign(byte[] data, byte[] privateKey) throws Exception {
SM2Signer signer = new SM2Signer();
CipherParameters param = new ParametersWithRandom(privateKeyToParams("SM2", privateKey));
signer.init(true, param);
signer.update(data, 0, data.length);
return signer.generateSignature();
}
/**
* 用公钥检验数字签名的合法性
* @param data 数据
* @param sign 签名
* @param publicKey 公钥
* @return 是否验证通过
*/
public static boolean verify(byte[] data, byte[] sign, byte[] publicKey) throws Exception {
SM2Signer signer = new SM2Signer();
CipherParameters param = publicKeyToParams("SM2", publicKey);
signer.init(false, param);
signer.update(data, 0, data.length);
return signer.verifySignature(sign);
}
跑个测试用例试下:
public static void main(String[] args) throws Exception {
KeyPair keyPair = generateSm2KeyPair();
//明文
String plaintext = "test";
//加密
String ciphertext = Base64Utils.encode(encrypt(plaintext.getBytes("utf-8"), keyPair.getPublic().getEncoded()));
//生成签名
String signature = Base64Utils.encode(sign(plaintext.getBytes("utf-8"),keyPair.getPrivate().getEncoded()));
System.out.println("ciphertext: " + ciphertext);
System.out.println("signature: " + signature);
//解密
plaintext = new String(decrypt(Base64Utils.decode(ciphertext),keyPair.getPrivate().getEncoded()),"utf-8");
//验签
boolean result = verify(plaintext.getBytes("utf-8"),Base64Utils.decode(signature),keyPair.getPublic().getEncoded());
System.out.println("plaintext: " + plaintext);
System.out.println("verify result: " + result);
}
输出结果如下:
ciphertext: BFH0SXC1bX7OL1i5I4GO/Ck8Lak6gfzrsEzhrk4GY2cKZOug73ThLBYSjbtnpC5z30CJuKmaAf/W+jlviRr9PT1dcBWyrO599UUC4XWW8as4XHej8nE/Rlr9TP5+wP+4AWaub6o=
signature: MEYCIQC7OldJ7B8JvP51zw8P2DfieG5iAj5rWEybqZ3bPG8D5wIhAIVXlOmMxk0t4cNm0oQ0HYIzJZ5JShGIWuVwZLZ/t9mQ
plaintext: test
verify result: true
从结果可以看出,可以进行正常的加解密、生成签名以及验签操作。