京東科技 姚永健
一、術語表:
1.對稱演演算法
加密解密金鑰是相同的。這些演演算法也叫秘密金鑰演演算法或單金鑰演演算法,它要求傳送者和接收者在安全通訊之前,商定一個金鑰。對稱演演算法的安全性依賴於金鑰,洩漏金鑰就意味著任何人都能對訊息進行加密解密。只要通訊需要保密,金鑰就必須保密。
對稱演演算法可分為兩類。一次只對明文中的單個位(有時對位元組)運算的演演算法稱為序列演演算法或序列密碼。另一類演演算法是對明文的一組位進行運算,這些位組稱為分組,相應的演演算法稱為分組演演算法或分組密碼。現代計算機密碼演演算法的典型分組長度為64位――這個長度大到足以防止分析破譯,但又小到足以方便作用。
2.非對稱演演算法
非對稱演演算法也叫公開金鑰加密,它是用兩個數學相關的金鑰對資訊進行編碼。在此係統中,其中一個金鑰叫公開金鑰,可隨意發給期望與金鑰持有者進行安全通訊的人。公開金鑰用於對資訊加密。第二個金鑰是私有金鑰,屬於金鑰持有者,此人要仔細儲存私有金鑰。金鑰持有者用私有金鑰對收到的資訊進行解密。
一般來說,都是公鑰加密,私鑰解密。如果系統雙方需要相互通訊,可以生成兩對金鑰對。各自儲存好自己的私鑰和對方的公鑰,用公鑰加密,私鑰進行解密
3.可逆加密演演算法
一般來說,涉及到秘鑰之類的演演算法,都是可逆的。意思就是透過演演算法和秘鑰加密之後,可以再次透過解密演演算法還原。常見的有DES、3DES、AES128、AES192、AES256 。其中AES後面的數字代表的是金鑰長度。對稱加密演演算法的安全性相對較低,比較適用的場景就是內網環境中的加解密。
4.不可逆演演算法
常見的不可逆加密演演算法有MD5,HMAC,SHA1、SHA-224、SHA-256、SHA-384,和SHA-512,其中SHA-224、SHA-256、SHA-384,和SHA-512我們可以統稱為SHA2加密演演算法,SHA加密演演算法的安全性要比MD5更高,而SHA2加密演演算法比SHA1的要高。其中SHA後面的數字表示的是加密後的字串長度,SHA1預設會產生一個160位的資訊摘要。
不可逆加密演演算法最大的特點就是不需要金鑰
5.加密鹽
加密鹽也是比較常聽到的一個概念,鹽就是一個隨機字串用來和我們的加密串拼接後進行加密。加鹽主要是為了保證加密字串的安全性。假如有一個加鹽後的加密串,駭客透過一定手段得到這個加密串,他解密後拿到的明文,並不是我們加密前的字串,而是加密前的字串和鹽組合的字串,這樣相對來說又增加了字串的安全性
或者也可以用在簽名,例如簽名是對明文或者密文加鹽後的簽名,有人想串改資料,如果不知道這個鹽和規則,那麼接收方驗籤就會不透過,從而保證通訊的安全。
二、傳統加密演演算法介紹
DES(Data Encryption Standard):
對稱演演算法,資料加密標準,速度較快,適用於加密大量資料的場合。
AES演演算法:
是DES的升級版,屬於對稱演演算法。可逆
程式碼:AESUtil
package AES;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class AESUtil {
/**
* AES加密字串
*
* @param content
* 需要被加密的字串
* @param password
* 加密需要的密碼
* @return 密文
*/
public static byte[] encrypt(String content, String password) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");// 建立AES的Key生產者
kgen.init(128, new SecureRandom(password.getBytes()));// 利用使用者密碼作為隨機數初始化出
//加密沒關係,SecureRandom是生成安全隨機數序列,password.getBytes()是種子,只要種子相同,序列就一樣,所以解密只要有password就行
SecretKey secretKey = kgen.generateKey();// 根據使用者密碼,生成一個金鑰
byte[] enCodeFormat = secretKey.getEncoded();// 返回基本編碼格式的金鑰,如果此金鑰不支援編碼,則返回
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");// 轉換為AES專用金鑰
Cipher cipher = Cipher.getInstance("AES");// 建立密碼器
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化為加密模式的密碼器
byte[] result = cipher.doFinal(byteContent);// 加密
return result;
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
}
/**
* 解密AES加密過的字串
*
* @param content
* AES加密過過的內容
* @param password
* 加密時的密碼
* @return 明文
*/
public static byte[] decrypt(byte[] content, String password) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");// 建立AES的Key生產者
kgen.init(128, new SecureRandom(password.getBytes()));
SecretKey secretKey = kgen.generateKey();// 根據使用者密碼,生成一個金鑰
byte[] enCodeFormat = secretKey.getEncoded();// 返回基本編碼格式的金鑰
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");// 轉換為AES專用金鑰
Cipher cipher = Cipher.getInstance("AES");// 建立密碼器
cipher.init(Cipher.DECRYPT_MODE, key);// 初始化為解密模式的密碼器
byte[] result = cipher.doFinal(content);
return result; // 明文
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws Exception {
String a = Base64.getEncoder().encodeToString("435678(*&*&^*&^%&^$%^#%#!@#$%^&%%90|8752$".getBytes(StandardCharsets.UTF_8));
String b = new String(Base64.getDecoder().decode(a.getBytes(StandardCharsets.UTF_8)));
System.out.println(a+":"+b);
String content = "{'aaa':'111','bbb':'222'}";
String secretKey = "43567890|8752$";
System.out.println("需要加密的內容:" + content);
byte[] encrypt = encrypt(content, secretKey);
System.out.println("加密後的2進位制密文:");
System.out.println(new String(encrypt));
String hexStr = Base64.getEncoder().encodeToString(encrypt);
System.out.println("base64編碼後密文:" + hexStr);
byte[] byte2 = Base64.getDecoder().decode(hexStr);
System.out.println("解碼後轉換為2進位制後密文:");
System.out.println(new String(byte2));
byte[] decrypt = decrypt(byte2, secretKey);
System.out.println("解密後的內容:" + new String(decrypt,"utf-8"));
}
}
RSA演演算法:
公鑰加密演演算法,非對稱,可逆
程式碼:RSAUtil
package RSASha256;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class RSAUtil {
private static final int DEFAULT_RSA_KEY_SIZE = 2048;
private static final String KEY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "MD5withRSA";
public static void main(String [] args){
Map<String,String> result = generateRsaKey(DEFAULT_RSA_KEY_SIZE);
String publicKey = result.get("publicKey");
String privateKey = result.get("privateKey");
// String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuazymB25s/ueIfwMM2H74736dQQE4bvRDgNfWltM4PzduNC84ntav41qEgbZtby47O57m/YDVf6vMXJH25ejlsBBhls7FD7xHTkOrskZqLekH0xCs2xa/akdwllbewUNzvWMuQy8X3j2VnrILXzVjvqYxkFY0itMo8fq1xSO6B/S5/RaeKpTtIepFB2cIU9vMrtLoCsnjVoTuPdWcoC79LtS0g1Q5BV4dn+Uca+8/gUbhS7vbth3oLZt6gXTRjJrPxhT4lqWsCZqwkQHPG/8JQgnFqs7+R0KcyemG/411IaZI9WYRGTsQji8sQ2q7HAlZRXvJn+As7jJPvyi67JGKwIDAQAB";
// String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5rPKYHbmz+54h/AwzYfvjvfp1BAThu9EOA19aW0zg/N240Lzie1q/jWoSBtm1vLjs7nub9gNV/q8xckfbl6OWwEGGWzsUPvEdOQ6uyRmot6QfTEKzbFr9qR3CWVt7BQ3O9Yy5DLxfePZWesgtfNWO+pjGQVjSK0yjx+rXFI7oH9Ln9Fp4qlO0h6kUHZwhT28yu0ugKyeNWhO491ZygLv0u1LSDVDkFXh2f5Rxr7z+BRuFLu9u2Hegtm3qBdNGMms/GFPiWpawJmrCRAc8b/wlCCcWqzv5HQpzJ6Yb/jXUhpkj1ZhEZOxCOLyxDarscCVlFe8mf4CzuMk+/KLrskYrAgMBAAECggEABjC+8dVj4J1N+2IU4g2tQT2PQSF+LCx/3tC7+B49JO8pUUUcVwy3zNUhKTKzRXziSXv2ARAlslNIcgSWYrreiGMmjB00jgs/LLM/SxKHWXmt7iEzxBmjuvtNc7JY+3QCrti+9Vh4W1KEHAQB8opL8HVobIu3M2KgLoG20a7syM5gPiCUb3EQ6P30u47y7I2wsbMnMmu79rPu3Q7lSNeoB9KdVV6EA6vukL3chY1QxFh6oFZS1yNjFjxy5RxT8U5Juhr5LYRnOP8MEVptbtrh34SSm0uyckkgJ6jOsrDJqo/DJxjPxixj+BWgG5/XbNq3PFEyEAqkrTZY+FMHSufoaQKBgQDpqPxpl05mDUXCnc7I6F8LZqjnjZU1h9zwKuQe94Fs3I3siWS62sJx1pZ66P9eeIOsT0T4Ye9nDx2x04yyfUcENhydFTQ5M5M9e2q1kKdNeBUT6Xy4WcRibKozUq07mLYSn0kDbuimp/Erp2w3hl5nZu/eLKDjt7yM0otv3uPp5wKBgQDLbYC4hcvk+f1kd/xJQ2ljwF+c8kHHFrlLRipf7qTB24dcrauWXbAGbbcKXx2G+3Waswyf09NdALiWab/jUUUHJm0YGxyr0k5lHci0/eC7hD3tWTL/HRyeF3umKZEkBQypzNeymE6t0lSqjL3MXFQLqumu4h677qA9/DNh7DYhHQKBgQCfAm/bj6s7iba6jVfmozPi91bkVRaAWlgBXL7nT/nU0ncGzC0vd6WxgJ3hQORgLtU0krFV8pfP45qKpHNwGA8XD5gDUiW68500zuM8chdYgeqeJVvJvNUHQfnFeXMIRpFJNPqkCnrqxwk5cvMTCi7+YS/FW0uWDDiVAMcBN4aUawKBgCOgvAiVNk6WEfEEqqTSL6UOzjAYpbiOnEk4src2fpiNMDnlGMYvBmM51/LzEaLQa5p6fV2Ipd4GAE4nmznew+4qprSwGudk3+IJw1sfk7qDwKzPEIVpvddaWYeShB8A22TpwWVAE5eR3M459AvUp8ubVW4RoDxd4Ka6gu1Fh31pAoGAYNPKJNCrdOsQH0nkH0Ld44cH6HD+zcZtoad2eQk0T2SPnKBsIS1S9W0adreOXUv1edO/hN3P/BcTuCaax7ijTscS2f0atc/NIa6LjBnK7oUBBzib9v21L72ZVZ5st4c/H7IzbQCXfS81489a7TTHP+e1HzS/XePftO0pAkr1GJ0=";
System.out.println("公鑰為:" + publicKey);
System.out.println("私鑰為:" + privateKey);
String plaintext = "{'a':'1111','b':'2222'}";
String ciphertext = null;
try {
System.out.println("開始加密明文:"+plaintext);
ciphertext = encrypt(plaintext,publicKey);
} catch (Exception e) {
System.out.println("加密失敗");
throw new RuntimeException(e);
}
System.out.println("得到的密文為:"+ciphertext);
String deciphering = decrypt(ciphertext,privateKey);
System.out.println("解密後:"+deciphering);
}
/**
* 生成RSA 公私鑰,可選長度為1025,2048位.
*/
public static Map<String,String> generateRsaKey(int keySize) {
Map<String,String> result = new HashMap<>(2);
try {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
// 初始化金鑰對生成器,金鑰大小為1024 2048位
keyPairGen.initialize(keySize, new SecureRandom());
// 生成一個金鑰對,儲存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到公鑰字串
result.put("publicKey", new String(Base64.getEncoder().encode(keyPair.getPublic().getEncoded())));
// 得到私鑰字串
result.put("privateKey", new String(Base64.getEncoder().encode(keyPair.getPrivate().getEncoded())));
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
return result;
}
/**
* RSA私鑰解密
* @param str 解密字串
* @param privateKey 私鑰
* @return 明文
*/
public static String decrypt(String str, String privateKey) {
//64位解碼加密後的字串
byte[] inputByte;
String outStr = "";
try {
inputByte = Base64.getDecoder().decode(str.getBytes("UTF-8"));
//base64編碼的私鑰
byte[] decoded = Base64.getDecoder().decode(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
outStr = new String(cipher.doFinal(inputByte));
} catch (UnsupportedEncodingException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
return outStr;
}
/**
* RSA公鑰加密
* @param str 需要加密的字串
* @param publicKey 公鑰
* @return 密文
* @throws Exception 加密過程中的異常資訊
*/
public static String encrypt(String str, String publicKey) throws Exception {
//base64編碼的公鑰
byte[] decoded = Base64.getDecoder().decode(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
String outStr = Base64.getEncoder().encodeToString(cipher.doFinal(str.getBytes("UTF-8")));
return outStr;
}
public static String sign(String data, String priKey) throws Exception {
byte[] decoded = Base64.getDecoder().decode(priKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(new X509EncodedKeySpec(decoded));
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(privateKey);
signature.update(data.getBytes());
return new String(Base64.getEncoder().encode(signature.sign()));
}
public static boolean verify(String pubKey, String sign, String data) throws Exception{
//獲取KeyFactory,指定RSA演演算法
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//將BASE64編碼的公鑰字串進行解碼
byte[] encodeByte = Base64.getDecoder().decode(pubKey);
//將BASE64解碼後的位元組陣列,構造成X509EncodedKeySpec物件,生成公鑰物件
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodeByte));
Signature signature = Signature.getInstance("MD5withRSA");
//載入公鑰
signature.initVerify(publicKey);
//更新原資料
signature.update(data.getBytes("UTF-8"));
//公鑰驗籤(true-驗籤透過;false-驗籤失敗)
return signature.verify(Base64.getDecoder().decode(sign));
}
}
MD5演演算法:
資訊摘要(Hash安全雜湊)演演算法,也叫雜湊演演算法,雜湊值也叫雜湊值,不可逆,不需要秘鑰。
程式碼: MD5Util
package MD5;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Util {
public static void main(String[] args) {
String sha256Str = getSha256Str("123456");
System.out.println(sha256Str);
}
/**
* sha256加密
*
* @param str 要加密的字串
* @return 加密後的字串
*/
public static String getSha256Str(String str) {
MessageDigest messageDigest;
String encodeStr = "";
try {
messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(str.getBytes(StandardCharsets.UTF_8));
encodeStr = byte2Hex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return encodeStr;
}
/**
* sha256加密 將byte轉為16進位制
*
* @param bytes 位元組碼
* @return 加密後的字串
*/
private static String byte2Hex(byte[] bytes) {
StringBuilder stringBuilder = new StringBuilder();
String temp;
for (byte aByte : bytes) {
temp = Integer.toHexString(aByte & 0xFF);
if (temp.length() == 1) {
//1得到一位的進行補0操作
stringBuilder.append("0");
}
stringBuilder.append(temp);
}
return stringBuilder.toString();
}
}
SHA-256演演算法:
sha256演演算法也是一種密碼雜湊函式,對於任意長度的訊息,SHA256都會產生一個256bit長的雜湊值(雜湊值),用於確保資訊傳輸完整一致,稱作訊息摘要。這個摘要相當於是個長度為32個位元組的陣列,通常用一個長度為64的十六進位制字串來表示。
和MD5演演算法對比
相同點:
1、都是密碼雜湊函式,加密不可逆。
2、都可以實現對任意長度物件加密,都不能防止碰撞。
安全性方面:
1、SHA256(稱SHA2)的安全性最高,但是耗時要其他兩種多很多。
2、md5相對來說比較容易碰撞,安全性沒這麼高。
效能方面:
以個60M的件為測試樣本,經過1000次的測試平均值,這兩種演演算法的表現如下:
MD5演演算法運1000次的平均時間為:226ms
SHA256演演算法運1000次的平均時間為:473ms
總而言之,md5和sha256都是密碼雜湊函式,加密不可逆。雖然都不能防止碰撞,但是相對而言,md5比較容易碰撞,安全性沒有sha256高。
程式碼:SHA256Util
package RSASha256;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SHA256Util {
public static void main(String[] args) {
String sha256Str = getSha256Str("123456");
System.out.println(sha256Str);
}
/**
* sha256加密
*
* @param str 要加密的字串
* @return 加密後的字串
*/
public static String getSha256Str(String str) {
MessageDigest messageDigest;
String encodeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str.getBytes(StandardCharsets.UTF_8));
encodeStr = byte2Hex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return encodeStr;
}
/**
* sha256加密 將byte轉為16進位制
*
* @param bytes 位元組碼
* @return 加密後的字串
*/
private static String byte2Hex(byte[] bytes) {
StringBuilder stringBuilder = new StringBuilder();
String temp;
for (byte aByte : bytes) {
temp = Integer.toHexString(aByte & 0xFF);
if (temp.length() == 1) {
//1得到一位的進行補0操作
stringBuilder.append("0");
}
stringBuilder.append(temp);
}
return stringBuilder.toString();
}
}
三、國密演演算法介紹
簡介:
為保障國家密碼應用安全,2011年GJMM管理局釋出《關於做好公鑰密碼演演算法升級工作的通知》,
要求自2011年3月1日起在建和擬建公鑰密碼基礎設施電子認證系統和金鑰管理系統應使用國密演演算法。
《金融和重要領域密碼應用與創新發展工作規劃(2018-2022年) 》以及相關法規檔案也要求我國金融
和重要領域密碼應用採用SM2國產密碼演演算法體系。
國密演演算法是指GJMM管理局認定的一系列國產密碼演演算法,包括SM1-SM9以及ZUC等。
其中SM1、SM4、SM5、SM6、SM7、SM8、ZUC等屬於對稱密碼,SM2、SM9等屬於公鑰密碼,SM3屬於單向雜湊函式。
目前我國主要使用公開的SM2、SM3、SM4作為商用密碼。
SM1:
SM1也叫商密1號演演算法,是一種國產的對稱演演算法,分組長度和金鑰長度都為 128 位元,該演演算法不公開,呼叫該演演算法時,需要透過加密晶片的介面進行呼叫。演演算法安全保密強度及相關軟硬體實現效能與 AES 相當
SM2:
SM2演演算法和RSA演演算法都是公鑰密碼演演算法,SM2演演算法是一種更先進安全的演演算法,在我們國家商用密碼體系中被用來替換RSA演演算法。
隨著密碼技術和計算機技術的發展,目前常用的1024位RSA演演算法面臨嚴重的安全威脅,我們國家密碼管理部門經過研究,決定採用SM2橢圓曲線演演算法替換RSA演演算法。
程式碼:SM2Util
package GMSM;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
/**
* SM2 工具類
* */
public class SM2Util {
//SM2推薦曲線名稱
static String SM2_CURVE_NAME = "sm2p256v1";
public static final Charset UTF_8 = Charset.forName("utf-8");
/**
* 生成金鑰
*
* @return
* @throws Exception
*/
public static KeyPair genKeyPair() throws Exception {
final ECGenParameterSpec sm2Spec = new ECGenParameterSpec(SM2_CURVE_NAME);
// 獲取一個橢圓曲線型別的金鑰對生成器
final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
SecureRandom random = new SecureRandom();
// 使用SM2的演演算法區域初始化金鑰生成器
kpg.initialize(sm2Spec, random);
// 獲取金鑰對
KeyPair keyPair = kpg.generateKeyPair();
return keyPair;
}
/**SM2根據公鑰加密
* param: message 待加密內容 , publicKey 加密公鑰(BASE64編碼)
* return: 加密資訊的Base64編碼
* @throws InvalidCipherTextException
* */
public static String encryptBySM2(String message, String publicKey) throws InvalidCipherTextException {
ECDomainParameters domin = getDomain();
//公鑰物件
ECPublicKeyParameters pubKeyParameters = getPubKey(publicKey,domin);
byte[] cipherBytes = new byte[0];
cipherBytes = encrypt(SM2Engine.Mode.C1C3C2, pubKeyParameters, message.getBytes(UTF_8));
return Base64.toBase64String(cipherBytes);
}
/**
* SM2根據私鑰解密
* param: cipherText 待解密密文 privateKey-私鑰(BASE64編碼)
* */
public static String decryptBySM2(String cipherText, String privateKey) throws InvalidCipherTextException {
BigInteger d = new BigInteger(1,Base64.decode(privateKey));
ECDomainParameters domin = getDomain();
//私鑰物件
ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(d, domin);
byte[] decrypt = decrypt(SM2Engine.Mode.C1C3C2, ecPrivateKeyParameters, Base64.decode(cipherText));
return new String(decrypt,UTF_8);
}
/**
* 根據公鑰字串建立公鑰物件
*
* */
public static ECPublicKeyParameters getPubKey(String publicKey, ECDomainParameters domain) {
ECCurve curve = domain.getCurve();
ECPoint point = curve.decodePoint(Base64.decode(publicKey));
ECPublicKeyParameters PublicKey = new ECPublicKeyParameters(point, domain);
return PublicKey;
}
/**
* @param mode 指定密文結構,舊標準的為C1C2C3,新的[《SM2密碼演演算法使用規範》 GM/T 0009-2012]標準為C1C3C2
* @param pubKeyParameters 公鑰
* @param srcData 原文
* @return 根據mode不同,輸出的密文C1C2C3排列順序不同。C1為65位元組第1位元組為壓縮標識,這裡固定為0x04,後面64位元組為xy分量各32位元組。C3為32位元組。C2長度與原文一致。
* @throws InvalidCipherTextException
*/
public static byte[] encrypt(SM2Engine.Mode mode, ECPublicKeyParameters pubKeyParameters, byte[] srcData)
throws InvalidCipherTextException {
SM2Engine engine = new SM2Engine(mode);
ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom());
engine.init(true, pwr);
return engine.processBlock(srcData, 0, srcData.length);
}
/**
* @param mode 指定密文結構,舊標準的為C1C2C3,新的[《SM2密碼演演算法使用規範》 GM/T 0009-2012]標準為C1C3C2
* @param priKeyParameters 私鑰
* @param sm2Cipher 根據mode不同,需要輸入的密文C1C2C3排列順序不同。C1為65位元組第1位元組為壓縮標識,這裡固定為0x04,後面64位元組為xy分量各32位元組。C3為32位元組。C2長度與原文一致。
* @return 原文。SM2解密返回了資料則一定是原文,因為SM2自帶校驗,如果密文被篡改或者金鑰對不上,都是會直接報異常的。
* @throws InvalidCipherTextException
*/
public static byte[] decrypt(SM2Engine.Mode mode, ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
throws InvalidCipherTextException {
SM2Engine engine = new SM2Engine(mode);
engine.init(false, priKeyParameters);
return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
}
public static ECDomainParameters getDomain() {
//獲取一條SM2曲線引數
X9ECParameters x9ECParameters = GMNamedCurves.getByName(SM2_CURVE_NAME);
//構造domain引數
ECDomainParameters domain = new ECDomainParameters(
x9ECParameters.getCurve(),
x9ECParameters.getG(),
x9ECParameters.getN(),
x9ECParameters.getH()
);
return domain;
}
/**
* 私鑰簽名
* @param privateKey 私鑰
* @param content 待簽名內容
* @return
*/
public static String sign(String privateKey, String content) throws CryptoException, CryptoException {
//待簽名內容轉為位元組陣列
byte[] message = content.getBytes();
BigInteger domainParameters = new BigInteger(1,Base64.decode(privateKey));
ECDomainParameters domin = getDomain();
//私鑰物件
ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(domainParameters, domin);
//建立簽名例項
SM2Signer sm2Signer = new SM2Signer();
//初始化簽名例項,帶上ID,國密的要求,ID預設值:1234567812345678
try {
sm2Signer.init(true, new ParametersWithID(new ParametersWithRandom(privateKeyParameters, SecureRandom.getInstance("SHA1PRNG")), Strings.toByteArray("1234567812345678")));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
sm2Signer.update(message, 0, message.length);
//生成簽名,簽名分為兩部分r和s,分別對應索引0和1的陣列
byte[] signBytes = sm2Signer.generateSignature();
String sign = Base64.toBase64String(signBytes);
return sign;
}
/**
* 驗證簽名
* @param publicKey 公鑰
* @param content 待簽名內容
* @param sign 簽名值
* @return
*/
public static boolean verify(String publicKey, String content, String sign) {
//待簽名內容
byte[] message = content.getBytes();
byte[] signData = Base64.decode(sign);
// 獲取一條SM2曲線引數
X9ECParameters sm2ECParameters = GMNamedCurves.getByName(SM2_CURVE_NAME);
// 構造domain引數
ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(),
sm2ECParameters.getN());
//提取公鑰點
ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(java.util.Base64.getDecoder().decode(publicKey));
// 公鑰前面的02或者03表示是壓縮公鑰,04表示未壓縮公鑰, 04的時候,可以去掉前面的04
ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
//建立簽名例項
SM2Signer sm2Signer = new SM2Signer();
ParametersWithID parametersWithID = new ParametersWithID(publicKeyParameters, Strings.toByteArray("1234567812345678"));
sm2Signer.init(false, parametersWithID);
sm2Signer.update(message, 0, message.length);
//驗證簽名結果
boolean verify = sm2Signer.verifySignature(signData);
return verify;
}
}
SM3:
國產雜湊演演算法,也叫訊息摘要演演算法,可以用MD5作為對比理解。該演演算法已公開。校驗結果為256位。SM3是中華人民共和國政府採用的一種密碼雜湊函式標準,由GJMM管理局於2010年12月17日釋出。相關標準為“GM/T 0004-2012 《SM3密碼雜湊演演算法》”。
在商用密碼體系中,SM3主要用於數字簽名及驗證、訊息認證碼生成及驗證、隨機數生成等,其演演算法公開。據GJMM管理局表示,其安全性及效率與SHA-256相當。
程式碼:SM3Util
package GMSM;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.util.encoders.Hex;
import java.util.Arrays;
public class SM3Util {
/**
* 計算SM3摘要值
*
* @param srcData 原文
* @return 摘要值,對於SM3演演算法來說是32位元組
*/
public static byte[] hash(byte[] srcData) {
SM3Digest digest = new SM3Digest();
digest.update(srcData, 0, srcData.length);
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
return hash;
}
/**
* 驗證摘要
*
* @param srcData 原文
* @param sm3Hash 摘要值
* @return 返回true標識驗證成功,false標識驗證失敗
*/
public static boolean verify(byte[] srcData, byte[] sm3Hash) {
byte[] newHash = hash(srcData);
System.out.println(Hex.toHexString(newHash));
System.out.println(Hex.toHexString(sm3Hash));
if (Arrays.equals(newHash, sm3Hash)) {
return true;
} else {
return false;
}
}
}
SM4:
無線區域網標準的分組資料演演算法。對稱加密,金鑰長度和分組長度均為128位。對標AES
程式碼:SM4Util
package GMSM;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
public class SM4Util {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static final String ALGORITHM_NAME = "SM4"; // AES
public static final String DEFAULT_KEY = "random_seed";
// 128-32位16進位制;256-64位16進位制
public static final int DEFAULT_KEY_SIZE = 128;
public static byte[] generateKey() throws NoSuchAlgorithmException, NoSuchProviderException {
return generateKey(DEFAULT_KEY, DEFAULT_KEY_SIZE);
}
public static byte[] generateKey(String seed) throws NoSuchAlgorithmException, NoSuchProviderException {
return generateKey(seed, DEFAULT_KEY_SIZE);
}
public static byte[] generateKey(String seed, int keySize) throws NoSuchAlgorithmException, NoSuchProviderException {
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
if (null != seed && !"".equals(seed)) {
random.setSeed(seed.getBytes());
}
kg.init(keySize, random);
return kg.generateKey().getEncoded();
}
/**
* @description 加密
*/
public static byte[] encrypt(String algorithmName, byte[] key, byte[] iv, byte[] data) throws Exception {
return sm4core(algorithmName, Cipher.ENCRYPT_MODE, key, iv, data);
}
/**
* @description 解密
*/
public static byte[] decrypt(String algorithmName, byte[] key, byte[] iv, byte[] data) throws Exception {
return sm4core(algorithmName, Cipher.DECRYPT_MODE, key, iv, data);
}
private static byte[] sm4core(String algorithmName, int type, byte[] key, byte[] iv, byte[] data) throws Exception {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
if (algorithmName.contains("/ECB/")) {
cipher.init(type, sm4Key);
} else {
if(iv == null ){
cipher.init(type, sm4Key);
}else{
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(type, sm4Key, ivParameterSpec);
}
}
return cipher.doFinal(data);
}
}
四、各類支付系統常見加密方式組合介紹
在支付系統互動中,有多種多樣的加密方式組合,這裡就簡單介紹兩鍾常用的。
1.傳統方式
用可逆,非對稱演演算法(rsa,sm2等)使用對方公鑰對報文內的關鍵資訊進行加密,得到的密文進行編碼,然後再對密文編碼後的串加鹽後使用不可逆演演算法(sha256,md5,sm3等)進行簽名,使用這些演演算法簽名後得到的是一個16進位制的串,簽名放到另一個欄位,一般是和加密後的資訊並列的。
步驟:
1.使用對方的RSA公鑰對明文進行加密,得到的密文進行base64編碼,作為data
2.將data的值加上約定好的鹽,使用sha256演演算法進行簽名,得到的簽名是16進位制的串,放到sign
加密後的json:
{
"data": "auwPDINowtdseZTfcCR8B1OPsevJE1MqIVOLn56WDvw3gjksSjkpMGp4lpYiqZkJ9G2PTaYC2DZSiyhbjtLvfnJE54mZz649gTWQVIHAn5fzV8Vs3JL3Kf9WB0Br9EbR07qrfGlWCvkkgktmTfDS/4qQGywn7TVQ3EH6HwQI7kUf0GlZ9CYcYHKi4/F61s6H9Hws+CPrhchGfJmHnu+835dSVS4IJOnA+0S5JXWVEN/is5i5cmxFb3RUYZbfpu+7JLDgcXLC8oNKIZxSE1aXhoFMj4CTTYCyRxKNCD015fnBMKh+7TYHEksyyybtVjkPqDeczgT3z+Qvj9vdyUZB1Q==",
"sign": "d3b36e7a837d6241445e506faf72c8d8e3467ee9c11efae0c67dfb5b15f44b6e"
}
接收後,先拿到密文,加入約定好的鹽進行驗籤,驗籤透過後再用私鑰進行解密。返回報文同理,只不過返回報文用的也是對方的公鑰加密,對方接收後用自己的私鑰解密。
demo:RSASha256Test
package RSASha256;
import java.util.HashMap;
import java.util.Map;
public class RSASha256Test {
public static void main(String[] args) {
Map<String,String> jsonData = new HashMap<>();
String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuazymB25s/ueIfwMM2H74736dQQE4bvRDgNfWltM4PzduNC84ntav41qEgbZtby47O57m/YDVf6vMXJH25ejlsBBhls7FD7xHTkOrskZqLekH0xCs2xa/akdwllbewUNzvWMuQy8X3j2VnrILXzVjvqYxkFY0itMo8fq1xSO6B/S5/RaeKpTtIepFB2cIU9vMrtLoCsnjVoTuPdWcoC79LtS0g1Q5BV4dn+Uca+8/gUbhS7vbth3oLZt6gXTRjJrPxhT4lqWsCZqwkQHPG/8JQgnFqs7+R0KcyemG/411IaZI9WYRGTsQji8sQ2q7HAlZRXvJn+As7jJPvyi67JGKwIDAQAB";
String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5rPKYHbmz+54h/AwzYfvjvfp1BAThu9EOA19aW0zg/N240Lzie1q/jWoSBtm1vLjs7nub9gNV/q8xckfbl6OWwEGGWzsUPvEdOQ6uyRmot6QfTEKzbFr9qR3CWVt7BQ3O9Yy5DLxfePZWesgtfNWO+pjGQVjSK0yjx+rXFI7oH9Ln9Fp4qlO0h6kUHZwhT28yu0ugKyeNWhO491ZygLv0u1LSDVDkFXh2f5Rxr7z+BRuFLu9u2Hegtm3qBdNGMms/GFPiWpawJmrCRAc8b/wlCCcWqzv5HQpzJ6Yb/jXUhpkj1ZhEZOxCOLyxDarscCVlFe8mf4CzuMk+/KLrskYrAgMBAAECggEABjC+8dVj4J1N+2IU4g2tQT2PQSF+LCx/3tC7+B49JO8pUUUcVwy3zNUhKTKzRXziSXv2ARAlslNIcgSWYrreiGMmjB00jgs/LLM/SxKHWXmt7iEzxBmjuvtNc7JY+3QCrti+9Vh4W1KEHAQB8opL8HVobIu3M2KgLoG20a7syM5gPiCUb3EQ6P30u47y7I2wsbMnMmu79rPu3Q7lSNeoB9KdVV6EA6vukL3chY1QxFh6oFZS1yNjFjxy5RxT8U5Juhr5LYRnOP8MEVptbtrh34SSm0uyckkgJ6jOsrDJqo/DJxjPxixj+BWgG5/XbNq3PFEyEAqkrTZY+FMHSufoaQKBgQDpqPxpl05mDUXCnc7I6F8LZqjnjZU1h9zwKuQe94Fs3I3siWS62sJx1pZ66P9eeIOsT0T4Ye9nDx2x04yyfUcENhydFTQ5M5M9e2q1kKdNeBUT6Xy4WcRibKozUq07mLYSn0kDbuimp/Erp2w3hl5nZu/eLKDjt7yM0otv3uPp5wKBgQDLbYC4hcvk+f1kd/xJQ2ljwF+c8kHHFrlLRipf7qTB24dcrauWXbAGbbcKXx2G+3Waswyf09NdALiWab/jUUUHJm0YGxyr0k5lHci0/eC7hD3tWTL/HRyeF3umKZEkBQypzNeymE6t0lSqjL3MXFQLqumu4h677qA9/DNh7DYhHQKBgQCfAm/bj6s7iba6jVfmozPi91bkVRaAWlgBXL7nT/nU0ncGzC0vd6WxgJ3hQORgLtU0krFV8pfP45qKpHNwGA8XD5gDUiW68500zuM8chdYgeqeJVvJvNUHQfnFeXMIRpFJNPqkCnrqxwk5cvMTCi7+YS/FW0uWDDiVAMcBN4aUawKBgCOgvAiVNk6WEfEEqqTSL6UOzjAYpbiOnEk4src2fpiNMDnlGMYvBmM51/LzEaLQa5p6fV2Ipd4GAE4nmznew+4qprSwGudk3+IJw1sfk7qDwKzPEIVpvddaWYeShB8A22TpwWVAE5eR3M459AvUp8ubVW4RoDxd4Ka6gu1Fh31pAoGAYNPKJNCrdOsQH0nkH0Ld44cH6HD+zcZtoad2eQk0T2SPnKBsIS1S9W0adreOXUv1edO/hN3P/BcTuCaax7ijTscS2f0atc/NIa6LjBnK7oUBBzib9v21L72ZVZ5st4c/H7IzbQCXfS81489a7TTHP+e1HzS/XePftO0pAkr1GJ0=";
String salt = "E4bvRDg";
System.out.println("公鑰為:" + publicKey);
String plaintext = "{'a':'1111','b':'2222'}";
String ciphertext = null;
String sign = null;
try {
ciphertext = RSAUtil.encrypt(plaintext,publicKey);
jsonData.put("data",ciphertext);
System.out.println("第一步,使用對方的RSA公鑰對明文進行加密,得到的密文進行base64編碼,作為data:"+jsonData.get("data"));
sign = SHA256Util.getSha256Str(ciphertext+salt);
jsonData.put("sign",sign);
System.out.println("第二步,將data的值加上約定好的鹽,使用sha256演演算法進行簽名,得到的簽名是16進位制的串,放到sign:"+jsonData.get("sign"));
} catch (Exception e) {
System.out.println("加密失敗");
throw new RuntimeException(e);
}
System.out.println("得到的密文和簽名為:"+jsonData);
System.out.println("模擬另一方收到密文和簽名之後..........");
System.out.println("私鑰為:" + privateKey);
String ciphertext1 = jsonData.get("data");
String sign1 = jsonData.get("sign");
String deciphering1 = null;
if(sign1.equals(SHA256Util.getSha256Str(ciphertext1+salt))){
System.out.println("簽名驗證成功");
deciphering1 = RSAUtil.decrypt(ciphertext1,privateKey);
System.out.printf("資料解密成功,data值為:"+deciphering1);
}else{
System.out.println("簽名驗證失敗,解密失敗");
}
}
}
2. 一次一密+簽名身份驗證
生成一個對稱演演算法秘鑰,使用該秘鑰對明文進行加密,然後將該秘鑰使用對方的公鑰進行加密,加密後編碼,然後再對明文使用自己的私鑰進行簽名,因為私鑰只有自己有,所以用自己的私鑰簽名後,對方使用你提供的公鑰進行驗籤,就可以驗證你的身份。
步驟:
1. 隨機生成一個SM4金鑰;
2. 使用SM4金鑰加密密文,得到的密文進行base64編碼,作為textToDecrypt
3. 將SM4金鑰進行base64編碼後,再使用對方的SM2公鑰對這個base64串進行加密,得到的密文進行base64編碼,作為keyCiphertext;
4. 對第二步的明文使用第三方sm2私鑰簽名,得到的簽名進行base64編碼後作為signature。
加密後json:
{
"textToDecrypt": "cU0ymFMho3HXmVq0hwDHvZg9oLsuZT19GLBKcvHzdZ4=",
"signature": "MEUCIA8nO1g705B3zzmaYv7yK8jajz9r2fKuvSPZliY8k1xBAiEA0noAEiBos3fU7RwEt81jTjwlrQL+tbQfy77VK/h/ES4=",
"keyCiphertext": "BJv91ULAd6fNchtKF/+b1JW2o7il0Hjbr6erQrY96S8QrDrkE7Y6EpgzM/eY1OumNsr6VbRuK0wx6yo8fq3QapYHZU5X1LzjgyMC+AiCnhzut5V3xxz4Yd0M1Zx2D4ljaOnJTDED4nS5kU2+C5VFF81DyzVDmWw6ZQ=="
}
對方接收後,按照以上步驟再進行驗籤,解密等操作。
demo:GMSMTest
package GMSM;
import java.util.HashMap;
import java.util.Map;
import java.util.Base64;
public class GMSMTest {
public static void main(String[] args) throws Exception {
// 你這邊拿到的sm2公私鑰,對方的公鑰和自己的私鑰
String priK = "AMz5m/WOgpgYiXnlckC7+zdvZXnQYMBcWXt1n7khfdVO";
String otherPubK = "BJkVGU87s7xhLrFCI8MGPzp2X+lizte+2So52CQ1tVEDDqOe+Q4vdQXglV2JtIOZeWc/bE8Rzdo29svmTj8+7mA=";
String messge = "{'aaa':'111','bbb':'222'}";
Map<String, String> jsonData = new HashMap<>();
byte[] keyB = SM4Util.generateKey(null);
String key = Base64.getEncoder().encodeToString(keyB);
String algorithmName = ("SM4/ECB/PKCS5PADDING");
System.out.println("第一步,隨機生成一個SM4秘鑰:"+key);
System.out.println("選定演演算法:"+algorithmName);
// 用隨機生成的SM4秘鑰加密
byte[] encryptMSG = SM4Util.encrypt(algorithmName, keyB, null, messge.getBytes());
jsonData.put("textToDecrypt",Base64.getEncoder().encodeToString(encryptMSG));
System.out.println("第二步,使用SM4金鑰加密明文,得到的密文進行base64編碼,作為textToDecrypt :"+jsonData.get("textToDecrypt"));
// 將隨機生成的SM4秘鑰,使用對方的SM2公鑰進行加密
String keyCiphertext = SM2Util.encryptBySM2(key, otherPubK);
jsonData.put("keyCiphertext",keyCiphertext);
System.out.println("第三步,將SM4金鑰進行base64編碼後,再使用對方的SM2公鑰對這個base64串進行加密,得到的密文進行base64編碼,作為 keyCiphertext;"+jsonData.get("keyCiphertext"));
// 對明文使用自己的sm2私鑰進行簽名 如果有需要,可以加點鹽
String signature = SM2Util.sign(priK,messge);
jsonData.put("signature",signature);
System.out.println("第四步,對第二步的明文使用對方sm2私鑰簽名,得到的簽名進行base64編碼後作為 signature:"+jsonData.get("signature"));
System.out.println("完整json串:"+jsonData);
// 模擬接到json傳之後的處理
// 對方拿到的,你的公鑰,和對方自己的私鑰
String PubK = "BNTFr3oo1xm3biGoQQnk20QJfbFvK4HNFgs1DkZJuUT9pW8I7K0D9L9P4mW+tQhWwfJlD/Nsx3BY+oriF1B25bA=";
String otherPriK = "I0sHzcDm+oS2FIAHFLxxkWJi1AbrHRxEYs1AxpZTlNw=";
// 將sm4秘鑰進行解密
String keyCiphertext1 = SM2Util.decryptBySM2(jsonData.get("keyCiphertext"), otherPriK);
System.out.println("對方使用sm2私鑰解密sm4秘鑰:"+keyCiphertext1);
// 透過sm4秘鑰解密資料密文
byte[] textToDecrypts = SM4Util.decrypt(algorithmName, Base64.getDecoder().decode(keyCiphertext1), null, Base64.getDecoder().decode(jsonData.get("textToDecrypt").getBytes()));
String textToDecryptsS = new String(textToDecrypts);
System.out.println("對方使用sm4秘鑰解密後的資料:"+ textToDecryptsS);
// 驗證簽名
boolean proving = SM2Util.verify(PubK, textToDecryptsS, jsonData.get("signature"));
System.out.println("驗證簽名是否透過:"+proving);
}
}
國密依賴pom
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
</dependencies>