三步輕松實現(xiàn)Java的SM2前端加密后端解密
話不多說,先上需要用到的js文件下載鏈接 和 jsp前端代碼。
第一步:下載兩個必備的js文件—— crypto-js.js、sm2.js 。
它們的下載鏈接如下↓(該網(wǎng)頁會很卡,畢竟github,建議卡的話就多重新加載幾次,我差不多試了8次才進(jìn)去):
https://github.com/Saberization/SM2
進(jìn)入網(wǎng)頁,下載后拿取下方↓圖片上的兩個js就行:
然后,將這兩js放到存放靜態(tài)資源(static)的目錄下,我放的是static中的sm2目錄(sm2這個目錄是我創(chuàng)的)
后面,在jsp的<head></head>中引入這兩個js文件
<!--jsp引入sm2,實現(xiàn)前端加密--> <script type="text/javascript" src="${pageContext.request.contextPath}/static/sm2/crypto-js.js" charset="utf-8"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/static/sm2/sm2.js" charset="utf-8"></script>
第二步:前端jsp代碼實現(xiàn),下方的代碼寫在jsp頁面的<script type="text/javascript"></script>中。
作用是,當(dāng)調(diào)用下方↓函數(shù)的時候,會給你傳入的值進(jìn)行加密并返回。
注:【你生成的公鑰】的生成方式可以在文章末尾的SM2Utils.java工具類中可以找到,是個main方法,一鍵生成,簡單粗暴
<!--sm2加密函數(shù)--> function sm2EncryptPwd(data) { <!--sm2公鑰--> var publicKey = '你生成的公鑰'; return sm2Encrypt(data, publicKey, 1); }
至此,前端加密結(jié)束,進(jìn)入后端解密環(huán)節(jié)——
后端需要的jar包是:bcprov-jdk15on-1.68.jar
注意?。?!該jar包必須要1.60版本以上的,不然解密的時候會缺少必要的條件?。?!并且我用的時候,本地環(huán)境是JDK1.8的,其他環(huán)境沒試過,建議如有不同,先查一下。
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.68</version> </dependency>
第三步:后端代碼實現(xiàn)。
導(dǎo)入jar包后,就是必要的工具類實體類:SM2Utils.java
你可以用工具類本身的加密方法解密,也可以用工具類中我寫的 getJieMiJieGuo() 方法解密。
我個人建議用我寫的,因為操作起來更簡單些,只需要傳入加密后的字符串,就可以返回解密的數(shù)據(jù)。
其中【你生成的公鑰】和【你生成的私鑰】可以通過工具類中的方法生成,生成完復(fù)制粘貼進(jìn)去就行
注!后端和前端的公鑰要保持一致!因為公鑰私鑰是配對使用的!
package com.test.common.utils; import org.apache.commons.net.util.Base64; import org.bouncycastle.asn1.gm.GMNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.engines.SM2Engine; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.params.ParametersWithRandom; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.jce.spec.ECPrivateKeySpec; import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.*; import java.security.spec.ECGenParameterSpec; public class SM2Utils { private static final String publicKey = "你生成的公鑰"; private static final String privateKey = "你生成私鑰"; /** * 解密,并且獲取解密后的字符串 * * @return 秦?zé)o解 */ public static String getJieMiJieGuo(String cipherData) { byte[] cipherDataByte = Hex.decode(cipherData); BigInteger privateKeyD = new BigInteger(privateKey, 16); X9ECParameters sm2ECParameters1 = GMNamedCurves.getByName("sm2p256v1"); ECDomainParameters domainParameters1 = new ECDomainParameters(sm2ECParameters1.getCurve(), sm2ECParameters1.getG(), sm2ECParameters1.getN()); ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters1); //用私鑰解密,SM2Engine.Mode.C1C3C2得加,不然就會報錯invalid cipher text SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2); sm2Engine.init(false, privateKeyParameters); //processBlock得到Base64格式,記得解碼 byte[] arrayOfBytes = null; try { arrayOfBytes = Base64.decodeBase64(sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length)); } catch (Exception e) { e.printStackTrace(); return null; } //得到明文:SM2 Encryption Test return new String(arrayOfBytes); } /** * @Description 生成秘鑰對 * @Author msx * @return KeyPair */ public static KeyPair createECKeyPair() { //使用標(biāo)準(zhǔn)名稱創(chuàng)建EC參數(shù)生成的參數(shù)規(guī)范 final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1"); // 獲取一個橢圓曲線類型的密鑰對生成器 final KeyPairGenerator kpg; try { kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider()); // 使用SM2算法域參數(shù)集初始化密鑰生成器(默認(rèn)使用以最高優(yōu)先級安裝的提供者的 SecureRandom 的實現(xiàn)作為隨機(jī)源) // kpg.initialize(sm2Spec); // 使用SM2的算法域參數(shù)集和指定的隨機(jī)源初始化密鑰生成器 kpg.initialize(sm2Spec, new SecureRandom()); // 通過密鑰生成器生成密鑰對 return kpg.generateKeyPair(); } catch (Exception e) { e.printStackTrace(); return null; } } /** * @Description 公鑰加密 * @Author msx * @param publicKeyHex SM2十六進(jìn)制公鑰 * @param data 明文數(shù)據(jù) * @return String */ public static String encrypt1(String publicKeyHex, String data) { return encrypt(getECPublicKeyByPublicKeyHex(publicKeyHex), data, 1); } /** * @Description 公鑰加密 * @Author msx * @param data 明文數(shù)據(jù) * @return String */ public static String encrypt(String data) { return encrypt(getECPublicKeyByPublicKeyHex(publicKey), data, 1); } /** * @Description 公鑰加密 * @Author msx * @param publicKey SM2公鑰 * @param data 明文數(shù)據(jù) * @param modeType 加密模式 * @return String */ public static String encrypt(BCECPublicKey publicKey, String data, int modeType) { //加密模式 SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2; if (modeType != 1) { mode = SM2Engine.Mode.C1C2C3; } //通過公鑰對象獲取公鑰的基本域參數(shù)。 ECParameterSpec ecParameterSpec = publicKey.getParameters(); ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(), ecParameterSpec.getG(), ecParameterSpec.getN()); //通過公鑰值和公鑰基本參數(shù)創(chuàng)建公鑰參數(shù)對象 ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters); //根據(jù)加密模式實例化SM2公鑰加密引擎 SM2Engine sm2Engine = new SM2Engine(mode); //初始化加密引擎 sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom())); byte[] arrayOfBytes = null; try { //將明文字符串轉(zhuǎn)換為指定編碼的字節(jié)串 byte[] in = data.getBytes("utf-8"); //通過加密引擎對字節(jié)數(shù)串行加密 arrayOfBytes = sm2Engine.processBlock(in, 0, in.length); } catch (Exception e) { System.out.println("SM2加密時出現(xiàn)異常:" + e.getMessage()); e.printStackTrace(); } //將加密后的字節(jié)串轉(zhuǎn)換為十六進(jìn)制字符串 return Hex.toHexString(arrayOfBytes); } /** * @Description 私鑰解密 * @Author msx * @param privateKeyHex SM2十六進(jìn)制私鑰 * @param cipherData 密文數(shù)據(jù) * @return String */ public static String decrypt1(String privateKeyHex, String cipherData) { return decrypt(getBCECPrivateKeyByPrivateKeyHex(privateKeyHex), cipherData, 1); } /** * @Description 私鑰解密 * @Author msx * @param s * @param cipherData 密文數(shù)據(jù) * @return String */ public static String decrypt(String s, String cipherData) { return decrypt(getBCECPrivateKeyByPrivateKeyHex(privateKey), cipherData, 1); } /** * @Description 私鑰解密 * @Author msx * @param privateKey SM私鑰 * @param cipherData 密文數(shù)據(jù) * @param modeType 解密模式 * @return */ public static String decrypt(BCECPrivateKey privateKey, String cipherData, int modeType) { //解密模式 SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2; if (modeType != 1) { mode = SM2Engine.Mode.C1C2C3; } //將十六進(jìn)制字符串密文轉(zhuǎn)換為字節(jié)數(shù)組(需要與加密一致,加密是:加密后的字節(jié)數(shù)組轉(zhuǎn)換為了十六進(jìn)制字符串) byte[] cipherDataByte = Hex.decode(cipherData); //通過私鑰對象獲取私鑰的基本域參數(shù)。 ECParameterSpec ecParameterSpec = privateKey.getParameters(); ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(), ecParameterSpec.getG(), ecParameterSpec.getN()); //通過私鑰值和私鑰鑰基本參數(shù)創(chuàng)建私鑰參數(shù)對象 ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(), ecDomainParameters); //通過解密模式創(chuàng)建解密引擎并初始化 SM2Engine sm2Engine = new SM2Engine(mode); sm2Engine.init(false, ecPrivateKeyParameters); String result = null; try { //通過解密引擎對密文字節(jié)串進(jìn)行解密 byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length); //將解密后的字節(jié)串轉(zhuǎn)換為utf8字符編碼的字符串(需要與明文加密時字符串轉(zhuǎn)換成字節(jié)串所指定的字符編碼保持一致) result = new String(arrayOfBytes, "utf-8"); } catch (Exception e) { System.out.println("SM2解密時出現(xiàn)異常" + e.getMessage()); } return result; } //橢圓曲線ECParameters ASN.1 結(jié)構(gòu) private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1"); //橢圓曲線公鑰或私鑰的基本域參數(shù)。 private static ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN()); /** * @Description 公鑰字符串轉(zhuǎn)換為 BCECPublicKey 公鑰對象 * @Author msx * @param pubKeyHex 64字節(jié)十六進(jìn)制公鑰字符串(如果公鑰字符串為65字節(jié)首個字節(jié)為0x04:表示該公鑰為非壓縮格式,操作時需要刪除) * @return BCECPublicKey SM2公鑰對象 */ public static BCECPublicKey getECPublicKeyByPublicKeyHex(String pubKeyHex) { //截取64字節(jié)有效的SM2公鑰(如果公鑰首個字節(jié)為0x04) if (pubKeyHex.length() > 128) { pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128); } //將公鑰拆分為x,y分量(各32字節(jié)) String stringX = pubKeyHex.substring(0, 64); String stringY = pubKeyHex.substring(stringX.length()); //將公鑰x、y分量轉(zhuǎn)換為BigInteger類型 BigInteger x = new BigInteger(stringX, 16); BigInteger y = new BigInteger(stringY, 16); //通過公鑰x、y分量創(chuàng)建橢圓曲線公鑰規(guī)范 ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecDomainParameters); //通過橢圓曲線公鑰規(guī)范,創(chuàng)建出橢圓曲線公鑰對象(可用于SM2加密及驗簽) return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION); } /** * @Description 私鑰字符串轉(zhuǎn)換為 BCECPrivateKey 私鑰對象 * @Author msx * @param privateKeyHex 32字節(jié)十六進(jìn)制私鑰字符串 * @return BCECPrivateKey SM2私鑰對象 */ public static BCECPrivateKey getBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) { //將十六進(jìn)制私鑰字符串轉(zhuǎn)換為BigInteger對象 BigInteger d = new BigInteger(privateKeyHex, 16); //通過私鑰和私鑰域參數(shù)集創(chuàng)建橢圓曲線私鑰規(guī)范 ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecDomainParameters); //通過橢圓曲線私鑰規(guī)范,創(chuàng)建出橢圓曲線私鑰對象(可用于SM2解密和簽名) return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION); } public static void main(String[] args) { String publicKeyHex = null; String privateKeyHex = null; KeyPair keyPair = createECKeyPair(); PublicKey publicKey = keyPair.getPublic(); if (publicKey instanceof BCECPublicKey) { //獲取65字節(jié)非壓縮縮的十六進(jìn)制公鑰串(0x04) publicKeyHex = Hex.toHexString(((BCECPublicKey) publicKey).getQ().getEncoded(false)); System.out.println("---->SM2公鑰:" + publicKeyHex); } PrivateKey privateKey = keyPair.getPrivate(); if (privateKey instanceof BCECPrivateKey) { //獲取32字節(jié)十六進(jìn)制私鑰串 privateKeyHex = ((BCECPrivateKey) privateKey).getD().toString(16); System.out.println("---->SM2私鑰:" + privateKeyHex); } /** * 公鑰加密 */ String data = "=========待加密數(shù)據(jù)========="; //將十六進(jìn)制公鑰串轉(zhuǎn)換為 BCECPublicKey 公鑰對象 String encryptData = encrypt1(publicKeyHex, data); System.out.println("---->加密結(jié)果:" + encryptData); /** * 私鑰解密 */ //將十六進(jìn)制私鑰串轉(zhuǎn)換為 BCECPrivateKey 私鑰對象 data = decrypt1(privateKeyHex, encryptData); System.out.println("---->解密結(jié)果:" + data); } }
最后,在后端接口中調(diào)用工具類的解密方法,就可以對前端傳來的加密數(shù)據(jù)進(jìn)行解密。
擴(kuò)展資料:
國密即國家密碼局認(rèn)定的國產(chǎn)密碼算法。主要有SM1,SM2,SM3,SM4。密鑰長度和分組長度均為128位。
(1)SM1 為對稱加密。其加密強(qiáng)度與AES相當(dāng)。該算法不公開,調(diào)用該算法時,需要通過加密芯片的接口進(jìn)行調(diào)用。
(2)SM2為非對稱加密,基于ECC。該算法已公開。由于該算法基于ECC,故其簽名速度與秘鑰生成速度都快于RSA。ECC 256位(SM2采用的就是ECC 256位的一種)安全強(qiáng)度比RSA 2048位高,但運(yùn)算速度快于RSA。
(3)SM3 消息摘要??梢杂肕D5作為對比理解。該算法已公開。校驗結(jié)果為256位。
(4)SM4 無線局域網(wǎng)標(biāo)準(zhǔn)的分組數(shù)據(jù)算法。對稱加密,密鑰長度和分組長度均為128位。
至此,??ヽ(°▽°)ノ?撒花。
總結(jié)
到此這篇關(guān)于三步輕松實現(xiàn)Java的SM2前端加密后端解密的文章就介紹到這了,更多相關(guān)Java SM2前端加密后端解密內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot集群管理工具KafkaAdminClient使用方法解析
這篇文章主要介紹了Spring Boot集群管理工具KafkaAdminClient使用方法解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-02-02詳解Java synchronized關(guān)鍵字的用法
在多線程編程中常常使用鎖機(jī)制來確保同一時刻只有一個線程能夠修改共享內(nèi)存,在Java中一般是使用synchronized作為鎖機(jī)制,下面就讓我們來學(xué)習(xí)一下如何使用synchronized實現(xiàn)線程安全吧2023-08-08SpringBoot+Redis使用AOP防止重復(fù)提交的實現(xiàn)
本文主要介紹了SpringBoot+Redis使用AOP防止重復(fù)提交的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07mybatisplus?selectOne查詢,有數(shù)據(jù),但返回為null問題
這篇文章主要介紹了mybatisplus?selectOne查詢,有數(shù)據(jù),但返回為null問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11MyBatis中resultMap和resultType的區(qū)別詳解
這篇文章主要介紹了MyBatis中resultMap和resultType的區(qū)別詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07