您现在的位置是:首页 > 技术教程 正文

三步实现Java的SM2前端加密后端解密

admin 阅读: 2024-03-27
后台-插件-广告管理-内容页头部广告(手机)

秦医如毒,无药可解。

话不多说,先上需要用到的js文件下载链接  和  jsp前端代码

第一步:下载两个必备的js文件—— crypto-js.jssm2.js 。

它们的下载链接如下↓(该网页不魔法上网的话会很卡,毕竟github,建议卡的话就多重新加载几次,我差不多试了8次才进去):

https://github.com/Saberization/SM2

进入网页,下载后拿取下方↓图片上的两个js就行:

f9f5e8b673d246fcbdf9a08d95b7598a.png

然后,将这两js放到存放静态资源(static)的目录下,我放的是static中的sm2目录(sm2这个目录是我创的)

后面,在jsp的中引入这两个js文件

  1. <script type="text/javascript" src="${pageContext.request.contextPath}/static/sm2/crypto-js.js" charset="utf-8">script>
  2. <script type="text/javascript" src="${pageContext.request.contextPath}/static/sm2/sm2.js" charset="utf-8">script>

第二步:前端jsp代码实现,下方的代码写在jsp页面的中。

作用是,当调用下方↓函数的时候,会给你传入的值进行加密并返回。

注:【你生成的公钥】的生成方式可以在文章末尾的SM2Utils.java工具类中可以找到,是个main方法,一键生成,简单粗暴

  1. function sm2EncryptPwd(data) {
  2. var publicKey = '你生成的公钥';
  3. return sm2Encrypt(data, publicKey, 1);
  4. }

至此,前端加密结束,进入后端解密环节——

后端需要的jar包是:bcprov-jdk15on-1.68.jar

注意!!!该jar包必须要1.60版本以上的,不然解密的时候会缺少必要的条件!!!并且我用的时候,本地环境是JDK1.8的,其他环境没试过,建议如有不同,先查一下。

  1.   org.bouncycastle
  2.   bcprov-jdk15on
  3.   1.68

第三步:后端代码实现。

导入jar包后,就是必要的工具类实体类:SM2Utils.java

用工具类中我写的 getJieMiJieGuo() 方法解密。

该方法操作起来更简单些,只需要传入加密后的字符串,就可以返回解密的数据。

注: 除getJieMiJieGuo()方法外,其它方法都不是我所写,不保证能解密成功。

其中【你生成的公钥】和【你生成的私钥】可以通过工具类中的方法生成,生成完复制粘贴进去就行

注!后端和前端的公钥要保持一致!因为公钥私钥是配对使用的!

  1. package com.test.common.utils;
  2. import org.apache.commons.net.util.Base64;
  3. import org.bouncycastle.asn1.gm.GMNamedCurves;
  4. import org.bouncycastle.asn1.x9.X9ECParameters;
  5. import org.bouncycastle.crypto.InvalidCipherTextException;
  6. import org.bouncycastle.crypto.engines.SM2Engine;
  7. import org.bouncycastle.crypto.params.ECDomainParameters;
  8. import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
  9. import org.bouncycastle.crypto.params.ECPublicKeyParameters;
  10. import org.bouncycastle.crypto.params.ParametersWithRandom;
  11. import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
  12. import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
  13. import org.bouncycastle.jce.provider.BouncyCastleProvider;
  14. import org.bouncycastle.jce.spec.ECParameterSpec;
  15. import org.bouncycastle.jce.spec.ECPrivateKeySpec;
  16. import org.bouncycastle.jce.spec.ECPublicKeySpec;
  17. import org.bouncycastle.util.encoders.Hex;
  18. import java.math.BigInteger;
  19. import java.security.*;
  20. import java.security.spec.ECGenParameterSpec;
  21. public class SM2Utils {
  22. private static final String publicKey = "你生成的公钥";
  23. private static final String privateKey = "你生成私钥";
  24. /**
  25. * 解密,并且获取解密后的字符串
  26. *
  27. * @return 秦无解
  28. */
  29. public static String getJieMiJieGuo(String cipherData) {
  30. byte[] cipherDataByte = Hex.decode(cipherData);
  31. BigInteger privateKeyD = new BigInteger(privateKey, 16);
  32. X9ECParameters sm2ECParameters1 = GMNamedCurves.getByName("sm2p256v1");
  33. ECDomainParameters domainParameters1 = new ECDomainParameters(sm2ECParameters1.getCurve(), sm2ECParameters1.getG(), sm2ECParameters1.getN());
  34. ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters1);
  35. //用私钥解密,SM2Engine.Mode.C1C3C2得加,不然就会报错invalid cipher text
  36. SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
  37. sm2Engine.init(false, privateKeyParameters);
  38. //processBlock得到Base64格式,记得解码
  39. byte[] arrayOfBytes = null;
  40. try {
  41. arrayOfBytes = Base64.decodeBase64(sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length));
  42. } catch (Exception e) {
  43. e.printStackTrace();
  44. return null;
  45. }
  46. //得到明文:SM2 Encryption Test
  47. return new String(arrayOfBytes);
  48. }
  49. /**
  50. * @Description 生成秘钥对
  51. * @Author msx
  52. * @return KeyPair
  53. */
  54. public static KeyPair createECKeyPair() {
  55. //使用标准名称创建EC参数生成的参数规范
  56. final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
  57. // 获取一个椭圆曲线类型的密钥对生成器
  58. final KeyPairGenerator kpg;
  59. try {
  60. kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
  61. // 使用SM2算法域参数集初始化密钥生成器(默认使用以最高优先级安装的提供者的 SecureRandom 的实现作为随机源)
  62. // kpg.initialize(sm2Spec);
  63. // 使用SM2的算法域参数集和指定的随机源初始化密钥生成器
  64. kpg.initialize(sm2Spec, new SecureRandom());
  65. // 通过密钥生成器生成密钥对
  66. return kpg.generateKeyPair();
  67. } catch (Exception e) {
  68. e.printStackTrace();
  69. return null;
  70. }
  71. }
  72. /**
  73. * @Description 公钥加密
  74. * @Author msx
  75. * @param publicKeyHex SM2十六进制公钥
  76. * @param data 明文数据
  77. * @return String
  78. */
  79. public static String encrypt1(String publicKeyHex, String data) {
  80. return encrypt(getECPublicKeyByPublicKeyHex(publicKeyHex), data, 1);
  81. }
  82. /**
  83. * @Description 公钥加密
  84. * @Author msx
  85. * @param data 明文数据
  86. * @return String
  87. */
  88. public static String encrypt(String data) {
  89. return encrypt(getECPublicKeyByPublicKeyHex(publicKey), data, 1);
  90. }
  91. /**
  92. * @Description 公钥加密
  93. * @Author msx
  94. * @param publicKey SM2公钥
  95. * @param data 明文数据
  96. * @param modeType 加密模式
  97. * @return String
  98. */
  99. public static String encrypt(BCECPublicKey publicKey, String data, int modeType) {
  100. //加密模式
  101. SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
  102. if (modeType != 1) {
  103. mode = SM2Engine.Mode.C1C2C3;
  104. }
  105. //通过公钥对象获取公钥的基本域参数。
  106. ECParameterSpec ecParameterSpec = publicKey.getParameters();
  107. ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
  108. ecParameterSpec.getG(), ecParameterSpec.getN());
  109. //通过公钥值和公钥基本参数创建公钥参数对象
  110. ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);
  111. //根据加密模式实例化SM2公钥加密引擎
  112. SM2Engine sm2Engine = new SM2Engine(mode);
  113. //初始化加密引擎
  114. sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
  115. byte[] arrayOfBytes = null;
  116. try {
  117. //将明文字符串转换为指定编码的字节串
  118. byte[] in = data.getBytes("utf-8");
  119. //通过加密引擎对字节数串行加密
  120. arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
  121. } catch (Exception e) {
  122. System.out.println("SM2加密时出现异常:" + e.getMessage());
  123. e.printStackTrace();
  124. }
  125. //将加密后的字节串转换为十六进制字符串
  126. return Hex.toHexString(arrayOfBytes);
  127. }
  128. /**
  129. * @Description 私钥解密
  130. * @Author msx
  131. * @param privateKeyHex SM2十六进制私钥
  132. * @param cipherData 密文数据
  133. * @return String
  134. */
  135. public static String decrypt1(String privateKeyHex, String cipherData) {
  136. return decrypt(getBCECPrivateKeyByPrivateKeyHex(privateKeyHex), cipherData, 1);
  137. }
  138. /**
  139. * @Description 私钥解密
  140. * @Author msx
  141. * @param s
  142. * @param cipherData 密文数据
  143. * @return String
  144. */
  145. public static String decrypt(String s, String cipherData) {
  146. return decrypt(getBCECPrivateKeyByPrivateKeyHex(privateKey), cipherData, 1);
  147. }
  148. /**
  149. * @Description 私钥解密
  150. * @Author msx
  151. * @param privateKey SM私钥
  152. * @param cipherData 密文数据
  153. * @param modeType 解密模式
  154. * @return
  155. */
  156. public static String decrypt(BCECPrivateKey privateKey, String cipherData, int modeType) {
  157. //解密模式
  158. SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
  159. if (modeType != 1) {
  160. mode = SM2Engine.Mode.C1C2C3;
  161. }
  162. //将十六进制字符串密文转换为字节数组(需要与加密一致,加密是:加密后的字节数组转换为了十六进制字符串)
  163. byte[] cipherDataByte = Hex.decode(cipherData);
  164. //通过私钥对象获取私钥的基本域参数。
  165. ECParameterSpec ecParameterSpec = privateKey.getParameters();
  166. ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
  167. ecParameterSpec.getG(), ecParameterSpec.getN());
  168. //通过私钥值和私钥钥基本参数创建私钥参数对象
  169. ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(),
  170. ecDomainParameters);
  171. //通过解密模式创建解密引擎并初始化
  172. SM2Engine sm2Engine = new SM2Engine(mode);
  173. sm2Engine.init(false, ecPrivateKeyParameters);
  174. String result = null;
  175. try {
  176. //通过解密引擎对密文字节串进行解密
  177. byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
  178. //将解密后的字节串转换为utf8字符编码的字符串(需要与明文加密时字符串转换成字节串所指定的字符编码保持一致)
  179. result = new String(arrayOfBytes, "utf-8");
  180. } catch (Exception e) {
  181. System.out.println("SM2解密时出现异常" + e.getMessage());
  182. }
  183. return result;
  184. }
  185. //椭圆曲线ECParameters ASN.1 结构
  186. private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
  187. //椭圆曲线公钥或私钥的基本域参数。
  188. private static ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
  189. /**
  190. * @Description 公钥字符串转换为 BCECPublicKey 公钥对象
  191. * @Author msx
  192. * @param pubKeyHex 64字节十六进制公钥字符串(如果公钥字符串为65字节首个字节为0x04:表示该公钥为非压缩格式,操作时需要删除)
  193. * @return BCECPublicKey SM2公钥对象
  194. */
  195. public static BCECPublicKey getECPublicKeyByPublicKeyHex(String pubKeyHex) {
  196. //截取64字节有效的SM2公钥(如果公钥首个字节为0x04)
  197. if (pubKeyHex.length() > 128) {
  198. pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128);
  199. }
  200. //将公钥拆分为x,y分量(各32字节)
  201. String stringX = pubKeyHex.substring(0, 64);
  202. String stringY = pubKeyHex.substring(stringX.length());
  203. //将公钥x、y分量转换为BigInteger类型
  204. BigInteger x = new BigInteger(stringX, 16);
  205. BigInteger y = new BigInteger(stringY, 16);
  206. //通过公钥x、y分量创建椭圆曲线公钥规范
  207. ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecDomainParameters);
  208. //通过椭圆曲线公钥规范,创建出椭圆曲线公钥对象(可用于SM2加密及验签)
  209. return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
  210. }
  211. /**
  212. * @Description 私钥字符串转换为 BCECPrivateKey 私钥对象
  213. * @Author msx
  214. * @param privateKeyHex 32字节十六进制私钥字符串
  215. * @return BCECPrivateKey SM2私钥对象
  216. */
  217. public static BCECPrivateKey getBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) {
  218. //将十六进制私钥字符串转换为BigInteger对象
  219. BigInteger d = new BigInteger(privateKeyHex, 16);
  220. //通过私钥和私钥域参数集创建椭圆曲线私钥规范
  221. ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecDomainParameters);
  222. //通过椭圆曲线私钥规范,创建出椭圆曲线私钥对象(可用于SM2解密和签名)
  223. return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
  224. }
  225. public static void main(String[] args) {
  226. String publicKeyHex = null;
  227. String privateKeyHex = null;
  228. KeyPair keyPair = createECKeyPair();
  229. PublicKey publicKey = keyPair.getPublic();
  230. if (publicKey instanceof BCECPublicKey) {
  231. //获取65字节非压缩缩的十六进制公钥串(0x04)
  232. publicKeyHex = Hex.toHexString(((BCECPublicKey) publicKey).getQ().getEncoded(false));
  233. System.out.println("---->SM2公钥:" + publicKeyHex);
  234. }
  235. PrivateKey privateKey = keyPair.getPrivate();
  236. if (privateKey instanceof BCECPrivateKey) {
  237. //获取32字节十六进制私钥串
  238. privateKeyHex = ((BCECPrivateKey) privateKey).getD().toString(16);
  239. System.out.println("---->SM2私钥:" + privateKeyHex);
  240. }
  241. /**
  242. * 公钥加密
  243. */
  244. String data = "=========待加密数据=========";
  245. //将十六进制公钥串转换为 BCECPublicKey 公钥对象
  246. String encryptData = encrypt1(publicKeyHex, data);
  247. System.out.println("---->加密结果:" + encryptData);
  248. /**
  249. * 私钥解密
  250. */
  251. //将十六进制私钥串转换为 BCECPrivateKey 私钥对象
  252. data = decrypt1(privateKeyHex, encryptData);
  253. System.out.println("---->解密结果:" + data);
  254. }
  255. }

最后,在后端接口中调用工具类的解密方法,就可以对前端传来的加密数据进行解密。

至此,✿✿ヽ(°▽°)ノ✿撒花。

此章暂时完结。

 

标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索
排行榜