加密与安全

素颜马尾好姑娘i 2023-06-27 03:25 61阅读 0赞

文章目录

  • 一,URL编码
    • 为什么要使用URL编码?
  • 二,Base64编码
  • 三,MD5加密
    • 常用的摘要算法
  • 四,SHA-1算法
  • 五,对称加密算法
    • AES加密
  • 六,非对称加密算法
    • RSA加密
    • RSA分段加解密
  • 七,SM4加密
    • SM4Util工具类
    • SM3Util工具类
    • 测试类

一,URL编码

为什么要使用URL编码?

我们都知道Http协议中参数的传输是"key=value"这种简直对形式的,如果要传多个参数就需要用“&”符号对键值对进行分割。如 "?name1=value1&name2=value2",这样在服务端在收到这种字符串的时候,会用“&”分割出每一个参数,然后再用“=”来分割出参数值。

现在有这样一个问题,如果我的参数值中就包含=或&这种特殊字符的时候该怎么办。
比如说“name1=value1”,其中value1的值是“va&lu=e1”字符串,那么实际在传输过程中就会变成这样“name1=va&lu=e1”。我们的本意是就只有一个键值对,但是服务端会解析成两个键值对,这样就产生了奇异。

如何解决上述问题带来的歧义呢?解决的办法就是对参数进行URL编码?

URL编码只是简单的在特殊字符的各个字节前加上%,例如,我们对上述会产生奇异的字符进行URL编码后结果:“name1=va%26lu%3D”,这样服务端会把紧跟在“%”后的字节当成普通的字节,就是不会把它当成各个参数或键值对的分隔符。

类似于这种

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yyMpbzea-1577957094515)(加密与安全/1577150995264.png)]

实际操作

  1. public static void main(String[] args) {
  2. //url编码
  3. String str="测试";
  4. String urlStr=URLEncoder.encode(str);
  5. System.out.println("url编码之后:"+urlStr);
  6. String deStr=URLDecoder.decode(urlStr);
  7. System.out.println("url解码之后:"+deStr);
  8. }

打印

url编码之后:%E6%B5%8B%E8%AF%95
url解密之后:测试

二,Base64编码

实际操作

  1. String str="测试";
  2. String b64=Base64.getEncoder().encodeToString(str.getBytes("utf-8"));
  3. System.out.println("base64编码之后:"+b64);
  4. String ori=new String(Base64.getDecoder().decode(b64),"utf-8");
  5. System.out.println("base64解码之后:"+ori);

打印

base64编码之后:5rWL6K+V
base64解码之后:测试

urlbase64编码

只需要把Base64.getEncoder()改为Base64.getUrlEncoder(),Base64.getDecoder()改为Base64.getUrlDecoder()即可。

Base64算法是编码算法,不是加密算法。Base64编码的目的是把任意二进制数据编码为文本(长度增加 1/3 ).

三,MD5加密

MD5是一种摘要算法,所谓摘要算法,就是输入任意长度数据,而输出的是固定长度数据。

举例:javaObject.hashCode()方法就是摘要算法。

有没有可能两个不同的输入得到了相同的输出?是有可能的,这也称之为碰撞!

常用的摘要算法





























算法 长度一 长度二
MD5 128bits 16bits
SHA-1 160bits 20bits
SHA-256 256bits 32bits
RipeMD 160bits 20bits

实际操作

  1. String str="测试";
  2. //得到md5的实例
  3. MessageDigest md=MessageDigest.getInstance("MD5");
  4. //update的是byte类型的数组
  5. md.update(str.getBytes());
  6. //加密后返回
  7. byte[] r=md.digest();
  8. System.out.println(String.format("%032x", new BigInteger(1,r)));

打印

24f93872e1db308f75eb317e3649e814

说明:MD5的输入参数是byte数组,不是字符串。

四,SHA-1算法

SHA-1MD5其实加密过程是一样的,不过前者比后者安全一些,但是运行速度削微慢了那么一丢丢(约25%)。

只是把上面的MD5改为SHA-1即可。

实际操作

  1. String str="测试";
  2. //得到md5的实例
  3. MessageDigest md=MessageDigest.getInstance("SHA-1");
  4. //update的是byte类型的数组
  5. md.update(str.getBytes());
  6. //加密后返回
  7. byte[] r=md.digest();
  8. System.out.println(String.format("%040x", new BigInteger(1,r)));

打印

0b5d7ed54bee16756a7579c6718ab01e3d1b75eb

五,对称加密算法

什么是对称加密?对称加密就是加密和解密使用的是同一个密钥。

通过传入一个密钥key和代加密的字符串进行加密。

解密:encrypt(key,message)—>得到加密后的结果

解密:decrypt(key,加密后的结果)——>解密后的结果

常用的对称加密算法
















算法 密钥长度
DES 56/64
AES 128/192/256

密钥长度越长,该算法的加密力度越大,说明越安全!由于DES加密算法的密钥长度太短,以至于可以被人暴力破解,因此被淘汰弃用。

AES加密

实际操作

  1. String str="测试";
  2. //################## 加密 ######################
  3. //加密模式
  4. String CIPHER_NAME="AES/ECB/PKCS5Padding";
  5. //得到cipher实例
  6. Cipher cipher=Cipher.getInstance(CIPHER_NAME);
  7. //设置密钥
  8. byte[] keys="1234567890abcdef".getBytes();
  9. //得到key实例 参数是密钥(byte[]类型的) 和"AES"加密算法名
  10. SecretKeySpec key=new SecretKeySpec(keys, "AES");
  11. cipher.init(Cipher.ENCRYPT_MODE, key);
  12. //进行加密,得到的byte类型的
  13. byte[] b=cipher.doFinal(str.getBytes());
  14. System.out.println("加密后的密文(转化为Base64显示):"+Base64.getEncoder().encodeToString(b));
  15. //################### 解密 ########################
  16. //得到cipher实例
  17. Cipher ciphers=Cipher.getInstance(CIPHER_NAME);
  18. //得到key实例
  19. SecretKeySpec key1=new SecretKeySpec(keys, "AES");
  20. cipher.init(Cipher.DECRYPT_MODE, key1);
  21. //解密
  22. byte[] c=cipher.doFinal(b);
  23. System.out.println("解密后的明文:"+new String(c));

打印

加密后的密文(转化为Base64显示):6BIemWfPl6FUC/dqx79LVw==
解密后的明文:测试

六,非对称加密算法

RSA加密

RSA非对称加密认证

前提:A和B在私底下要互换对方的公钥,而私钥则自己持有!

加密:A给B发送消息,使用B的公钥加密,然后B收到消息后使用自己的私钥解密。

签名:A使用自己的私钥对数据进行签名,然后发送给B,然后B使用A的公钥进行验签,校验数据的完整性。

说明

使用公钥加密,使用私钥解密;使用私钥签名,使用公钥验签。

签名的作用:

A给B发送消息,加密之后发送给B,此时,一个黑客使用B的公钥加密了一些数据也发送给了B,那么B怎么才能知道收到的加密数据是A发送的而不是黑客?

这时就需要签名了,A加密后并对加密后的数据进行签名,使用A自己的私钥进行签名,发送到B之后,B再使用A的公钥进行验签,如果验签通过则说明加密数据是由A发送过来的。

朕给你们提供一个RSAUtil工具类,一切加密的起源都可以源于这个工具类。

本工具类说明书

方法一:string2PublicKey()将Base64编码的公钥转化为公钥对象

参数:(Base64格式的)公钥字符串 返回值:公钥对象

方法二:string2PrivateKey()将Base64编码的私钥转化为私钥对象

参数:(Base64格式的)私钥字符串 返回值:私钥对象

方法三:encrypt() 加密方法

参数:待加密的内容(字符串) 加密使用的密钥(对方公钥) 加密使用的模式(RSA)

返回值:内容加密后的Base64编码形式的字符串

方法四:decrypt() 解密方法

参数:待解密的内容的Base64编码形式的字符串 解密使用的密钥(自己私钥) 解密使用的模式(RSA)

返回值:原文字符串

方法五:handleData()分段处理加解密数据,放到输出流中(这里不做解释)

方法六:sign() 签名算法

参数:要签名的数据(原文或密文均可) 签名使用的密钥(自己私钥) 签名使用的模式(SHA1withRSA)

返回值:签名字符串

方法七:verifySign() 验签算法

参数:签名字符串 验签使用的密钥(对方公钥) 签名使用的模式(SHA1withRSA)

返回值:布尔类型

  1. public class RSAUtil {
  2. //加密算法
  3. private static final String KEY_ALGORITHM = "RSA";
  4. //密钥长度
  5. private static final int KEY_SIZE = 1024;
  6. //分块加密,单块明文的最大字节数 (KEY_SIZE/8) - 11
  7. private static final int MAX_ENCRYPT_BLOCK = 117;
  8. //分块解密,单块密文的最大字节数 KEY_SIZE/8
  9. private static final int MAX_DECRYPT_BLOCK = 128;
  10. //加密算法
  11. public static final String encryptAlgorithm="RSA/ECB/PKCS1Padding";
  12. //签名算法
  13. public static final String signAlgorithm="SHA1withRSA";
  14. /** * 将Base64编码的公钥转化为公钥对象 * create time: 2019年4月18日下午3:46:01 * @param publicKeyStr * @return * @throws IOException * @throws NoSuchAlgorithmException * @throws InvalidKeySpecException */
  15. public static PublicKey string2PublicKey(String publicKeyStr) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
  16. //把base64格式的公钥进行base64解码转为byte类型
  17. byte[] keyBytes = Base64.decodeBase64(publicKeyStr);
  18. //根据给定的编码密钥创建一个新的 X509EncodedKeySpec对象
  19. X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
  20. //getInstance api解释:返回转换指定算法的 public/private 关键字的 KeyFactory 对象
  21. //keyFactory是密钥规范
  22. KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);//参数是加密算法RSA
  23. //根据提供的密钥规范(密钥材料)生成公钥对象
  24. PublicKey publicKey = keyFactory.generatePublic(keySpec);
  25. return publicKey;
  26. }
  27. /** * 将Base64编码的私钥转化为私钥对象 * create time: 2019年4月18日下午3:46:32 * @param privateKeystr * @return * @throws IOException * @throws NoSuchAlgorithmException * @throws InvalidKeySpecException */
  28. public static PrivateKey string2PrivateKey(String privateKeystr) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
  29. //把base64格式的私钥进行base64解码转为byte类型
  30. byte[] keyBytes = Base64.decodeBase64(privateKeystr);
  31. //根据给定的编码密钥创建一个新的 PKCS8EncodedKeySpec对象
  32. PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
  33. //getInstance api解释:返回转换指定算法的 public/private 关键字的 KeyFactory 对象
  34. //keyFactory是密钥规范
  35. KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);//参数是加密算法RSA
  36. //根据提供的密钥规范(密钥材料)生成私钥对象
  37. PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
  38. return privateKey;
  39. }
  40. /** * RSA加密方法 * create time: 2019年4月18日下午3:38:18 * @param plainData 待加密的内容 * @param key 加密使用的秘钥(对方的公钥) * @param algorithm 加密使用的模式 * @return 内容加密后的Base64编码形式 * @throws Exception */
  41. public static String encrypt(String plainData, Key key, String algorithm) throws Exception {
  42. //字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中
  43. ByteArrayOutputStream out = new ByteArrayOutputStream();
  44. byte[] chiperDataBytes;
  45. try {
  46. //得到cipher实例
  47. Cipher cipher = Cipher.getInstance(algorithm);//参数:加密算法RSA
  48. //初始化 ENCRYPT_MODE:用于将 Cipher 初始化为加密模式的常量 key:密钥(对方公钥)
  49. cipher.init(Cipher.ENCRYPT_MODE, key);
  50. //把要加密的字符串转换为byte数组
  51. byte[] plainDataBytes = plainData.getBytes("UTF-8");
  52. //调用分段加密方法进行加密
  53. //MAX_ENCRYPT_BLOCK:单块明文最大字节数
  54. //out 字节数组缓冲区,和stringbuffer类似用法
  55. handleData(out, cipher, plainDataBytes, MAX_ENCRYPT_BLOCK);
  56. //把字节数组输出流转为byte数组
  57. chiperDataBytes = out.toByteArray();
  58. } catch(Exception e) {
  59. throw new Exception(e);
  60. } finally {
  61. out.close();
  62. }
  63. //再把byte数组转为Base64编码格式的字符串并返回(加密后的Base64字符串)
  64. return Base64.encodeBase64String(chiperDataBytes);
  65. }
  66. /** * RSA分段解密方法 * create time: 2019年4月18日下午3:40:52 * @param cipherData 待解密的内容的Base64编码形式的数据 * @param key 解密使用的秘钥 * @param algorithm 解密使用的模式 * @return 解密后的内容 * @throws Exception */
  67. public static String decrypt(String cipherData, Key key, String algorithm) throws Exception, IOException {
  68. //字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中
  69. ByteArrayOutputStream out = new ByteArrayOutputStream();
  70. byte[] plainDataBytes;
  71. try {
  72. //得到cipher实例
  73. Cipher cipher = Cipher.getInstance(algorithm);//参数:加密算法RSA
  74. //初始化 DECRYPT_MODE:用于将 Cipher 初始化为解密模式的常量 key:密钥(自己私钥)
  75. cipher.init(Cipher.DECRYPT_MODE, key);
  76. //把要解密的字符串转换为byte数组
  77. byte[] chiperDataBytes = Base64.decodeBase64(cipherData);
  78. //调用分段解密方法进行解密
  79. handleData(out, cipher, chiperDataBytes, MAX_DECRYPT_BLOCK);
  80. //把byte 数组输出流转为byte数组
  81. plainDataBytes = out.toByteArray();
  82. } catch(Exception e) {
  83. throw new Exception(e);
  84. } finally {
  85. out.close();
  86. }
  87. //再把byte数组转为字符串并返回(解密后的字符串)
  88. return new String(plainDataBytes);
  89. }
  90. /** * 分段处理加解密数据,放到输出流中 * create time: 2019年4月18日下午3:42:37 * @param out 输出流 * @param cipher 加解密工具 * @param dataBytes 要处理的数据 * @param maxBlock 处理的块的最大字节数 * @throws IllegalBlockSizeException * @throws BadPaddingException */
  91. private static void handleData(ByteArrayOutputStream out, Cipher cipher, byte[] dataBytes, int maxBlock) throws IllegalBlockSizeException, BadPaddingException {
  92. //计算待加密的byte数组的长度
  93. int inputLen = dataBytes.length;
  94. int offSet = 0;
  95. int i = 0;
  96. byte[] cache;
  97. //循环减去已经被加密的长度(剩余长度)
  98. while (inputLen - offSet > 0) {
  99. if (inputLen - offSet > maxBlock) {
  100. //加密操作
  101. //处理dataBytes字节数组的缓冲区从offset开始(包含)的前maxBlock个字节
  102. cache = cipher.doFinal(dataBytes, offSet, maxBlock);
  103. } else {
  104. cache = cipher.doFinal(dataBytes, offSet, inputLen - offSet);
  105. }
  106. //将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此 byte 数组输出流
  107. out.write(cache, 0, cache.length);
  108. i++;
  109. offSet = i * maxBlock;
  110. }
  111. }
  112. /** * 签名算法 * @param attributes 属性数据 要签名的字符串 * @param privateKey 签名使用的密钥 自己的私钥 * @param algorithm 签名使用的模式 SHA1withRSA * @return * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws SignatureException */
  113. public static String sign(String attributes, PrivateKey privateKey, String algorithm)
  114. throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
  115. //Signature对象可用来生成和验证数字签名
  116. Signature signature = Signature.getInstance(algorithm);
  117. //初始化这个用于签名的对象
  118. signature.initSign(privateKey);
  119. //使用指定的 byte 数组更新要签名或验证的数据
  120. signature.update(attributes.getBytes());
  121. //sign()方法:返回所有已更新数据的签名字节
  122. byte[] signedData = signature.sign();
  123. //把签名字节转为Base64编码格式的字符串
  124. return Base64.encodeBase64String(signedData);
  125. }
  126. /** * 验签算法 * @param attributes 属性数据明文 * @param signData 签名 * @param publicKey 验签使用的公钥 * @param algorithm 验签使用的模式 * @return * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws SignatureException */
  127. public static boolean verifySign(String attributes, String signData, PublicKey publicKey, String algorithm) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
  128. //Signature对象可用来生成和验证数字签名
  129. Signature signCheck = Signature.getInstance(algorithm);
  130. //使用来自给定证书的公钥初始化此用于验证的对象
  131. signCheck.initVerify(publicKey);
  132. //使用指定的 byte 数组更新要签名或验证的数据。
  133. signCheck.update(attributes.getBytes());
  134. //验证传入的签名
  135. return signCheck.verify(Base64.decodeBase64(signData));
  136. }
  137. }

如果使用 RSA,对消息摘要算法则会有多种选择,因此,可以将签名算法指定为 MD2withRSAMD5withRSASHA1withRSA。因为没有默认的算法名称,所以必须为其指定名称。

开始加密签名

首先定义一下自己的私钥和对方的公钥。

  1. //自己的私钥
  2. public static final String privateKey="MIICdwIBADANBg.....";
  3. //对方的公钥
  4. public static final String thirdPublicKey="MIGfMA0GCSq.....";

加密和签名(传进的参数是待加密的数据【json串】)

  1. public Map jmqm(String jcxx) throws Exception {
  2. //得到PublicKey对象
  3. PublicKey publicKey = RSAUtil.string2PublicKey(thirdPublicKey);
  4. //进行加密并得到加密后的字符串
  5. String encryptParam = RSAUtil.encrypt(jcxx, publicKey, "RSA");
  6. //签名(这里对原文签名)
  7. PrivateKey privateKey = RSAUtil.string2PrivateKey(privateKey);
  8. String signature = RSAUtil.sign(jcxx, privateKey, "SHA1withRSA");
  9. Map map=new HashMap();
  10. map.put("encryptParam",encryptParam);
  11. map.put("signature",signature);
  12. return map;
  13. }

解密和验签

  1. public String decryptData(String encryptData) throws Exception {
  2. JSONObject jsonObject = JSONObject.parseObject(encryptData);
  3. String encryptParam=(String) jsonObject.get("encryptParam");
  4. String signature=(String) jsonObject.get("signature");
  5. //第三方公钥转为Key对象
  6. PublicKey publicKey = RSAUtil.string2PublicKey(thirdPublicKey);
  7. //自己私钥转为Key对象
  8. PrivateKey privateKey = RSAUtil.string2PrivateKey(privateKey);
  9. //进行解密,返回解密后的原文字符串
  10. String plainData = RSAUtil.decrypt(encryptParam, privateKey, "RSA");
  11. log.info("反馈信息解密内容:"+plainData);
  12. //对原文进行验签
  13. boolean isTrue = RSAUtil.verifySign(plainData, signature, publicKey, "SHA1withRSA");
  14. if(isTrue){
  15. log.info("验签通过");
  16. return plainData;
  17. }else {
  18. log.info("验签失败");
  19. return "";
  20. }
  21. }

结束语:其实只要利用好RSAUtil这一个工具类即可,然后加解密以及签名验签的过程可以任意封装。

RSAUtil工具类中定义了一些静态变量,这些变量是要视情况而改变的。

KEY_ALGORITHM=“RSA” 这是加解密算法

KEY_SIZE=1024 这是使用密钥的长度(1024 or 2048)

MAX_ENCRYPT_BLOCK 这是单块明文的最大字节数 (KEY_SIZE 除以 8 然后减去 11 )

MAX_DECRYPT_BLOCK 这是单块密文的最大字节数 (KEY_SIZE 除以 8 )

encryptAlgorithm=“RSA/ECB/PKCS1Padding” 加密算法(这个没用到,用到的是第一个加密算法【RSA】)

signAlgorithm=“SHA1withRSA” 这是签名算法

以上的所有静态变量都是调用加解密方法和签名验签方法时的参数,具体的RSA加密机制可以找时间抽空了解下,包括分段加解密,不过在这个RSAUtil工具类中已经写好了,不用过多操心这个问题了。

重点:当密钥使用的是1024位长度时,KEY_SIZE要设为1024,MAX_ENCRYPT_BLOCK要设为117,MAX_DECRYPT_BLOCK要设为128;当密钥使用的是2048位长度时,KEY_SIZE要设为2048,MAX_ENCRYPT_BLOCK要设为245,MAX_DECRYPT_BLOCK要设为256;

RSA加密明文最大长度117字节,解密要求密文最大长度为128字节,所以在加密和解密的过程中需要分块进行。RSA加密对明文的长度是有限制的,如果加密数据过大会抛出如下异常:

  1. Exception in thread "main" javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes
  2. at com.sun.crypto.provider.RSACipher.a(DashoA13*..)
  3. at com.sun.crypto.provider.RSACipher.engineDoFinal(DashoA13*..)
  4. at javax.crypto.Cipher.doFinal(DashoA13*..)

转自网上的一篇帖子,对最大明文长度和最大密文长度做了详细的解释!

不管明文长度是多少,RSA 生成的密文长度总是固定的。但是明文长度不能超过密钥长度。比如 Java 默认的 RSA 加密实现不允许明文长度超过密钥长度减去 11(单位是字节,也就是 byte)。也就是说,如果我们定义的密钥(我们可以通过 java.security.KeyPairGenerator.initialize(int keysize)来定义密钥长度)长度为 1024(单位是位,也就是 bit),生成的密钥长度就是 1024位 / 8位/字节 = 128字节,那么我们需要加密的明文长度不能超过 128字节 -11 字节 = 117字节。也就是说,我们最大能将 117 字节长度的明文进行加密,否则会出问题(抛诸如 javax.crypto.IllegalBlockSizeException: Data must not be longer than 53 bytes的异常)。

自己总结

相反,如果密钥长度使用的是2048位 /(8位/字节) = 256字节 的话,那么最大单块密文长度就是256字节,最大单块明文长度就是256 - 11 = 245(字节)。

这里提供一个密钥对生成工具,在 <我的下载> 列表中,可以下载使用!

RSA分段加解密

上面引入网上的帖子可以看出,RSA加密一次最多只能加密明文的117字节(如果key长度为1024位的话),那么明文太多怎么办?只能分段加密了,很好懂的道理,把明文结成一段一段的,分别进行加密,加密之后把加密后的结果再进行拼接。

下面是分段加解密的过程。

  1. /** * 分段处理加解密数据,放到输出流中 * create time: 2019年4月18日下午3:42:37 * @param out 输出流 * @param cipher 加解密工具 * @param dataBytes 要处理的数据 * @param maxBlock 处理的块的最大字节数 * @throws IllegalBlockSizeException * @throws BadPaddingException */
  2. private static void handleData(ByteArrayOutputStream out, Cipher cipher, byte[] dataBytes, int maxBlock) throws IllegalBlockSizeException, BadPaddingException {
  3. //计算待加密的byte数组的长度
  4. int inputLen = dataBytes.length;
  5. int offSet = 0;
  6. int i = 0;
  7. byte[] cache;
  8. //循环减去已经被加密的长度(剩余长度)
  9. while (inputLen - offSet > 0) {
  10. if (inputLen - offSet > maxBlock) {
  11. //加密操作
  12. //处理dataBytes字节数组的缓冲区从offset开始(包含)的前maxBlock个字节
  13. cache = cipher.doFinal(dataBytes, offSet, maxBlock);
  14. } else {
  15. cache = cipher.doFinal(dataBytes, offSet, inputLen - offSet);
  16. }
  17. //将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此 byte 数组输出流
  18. out.write(cache, 0, cache.length);
  19. i++;
  20. offSet = i * maxBlock;
  21. }
  22. }

了解原理即可,会使用工具类就好啦!

七,SM4加密

SM4使用的国密算法。

SM4Util工具类

  1. public class SM4Util {
  2. public static final String ALGORITHM_NAME = "SM4";
  3. public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
  4. public static final int DEFAULT_KEY_SIZE = 128;
  5. public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data)
  6. throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
  7. NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
  8. Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
  9. return cipher.doFinal(data);
  10. }
  11. public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText)
  12. throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
  13. NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {
  14. Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
  15. return cipher.doFinal(cipherText);
  16. }
  17. private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key)
  18. throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
  19. InvalidKeyException {
  20. Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
  21. Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
  22. cipher.init(mode, sm4Key);
  23. return cipher;
  24. }
  25. public static byte[] generateKey() throws NoSuchAlgorithmException, NoSuchProviderException {
  26. return generateKey(DEFAULT_KEY_SIZE);
  27. }
  28. public static byte[] generateKey(int keySize) throws NoSuchAlgorithmException, NoSuchProviderException {
  29. KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
  30. kg.init(keySize, new SecureRandom());
  31. return kg.generateKey().getEncoded();
  32. }
  33. }

SM3Util工具类

  1. public class SM3Util {
  2. public static byte[] hash(byte[] srcData) {
  3. SM3Digest digest = new SM3Digest();
  4. digest.update(srcData, 0, srcData.length);
  5. byte[] hash = new byte[digest.getDigestSize()];
  6. digest.doFinal(hash, 0);
  7. return hash;
  8. }
  9. }

测试类

  1. public class DemoTest
  2. {
  3. public static final String demo = "SM4加解密测试DEMO";
  4. public static final String sortparam = "参数根据key的 ASCII 码从小到大排序(字典序)";//排序使用treemap,key1=value1&key2=value2
  5. public static final String appsecret = "ba22726d-14aa-11ea-9b2d-b888e3ebf769";
  6. public static void main(String[] args) throws Exception {
  7. // SM4加密原文
  8. byte[] bKey = SM4Util.generateKey();
  9. byte[] sm4 = SM4Util.encrypt_Ecb_Padding(bKey,demo.getBytes("UTF-8"));
  10. String encData = Base64.encodeBase64String(sm4);
  11. System.out.println("密文:" + encData);
  12. byte[] dd = SM4Util.decrypt_Ecb_Padding(bKey, Base64.decodeBase64(encData));
  13. String datainfo = new String(dd, "UTF-8");
  14. System.out.println("解密后的原文:" + datainfo);
  15. //sign签名
  16. sortparam+="&appsecret="+appsecret;
  17. byte[] signHash=SM3Util.hash(sortparam.getBytes("UTF-8"));
  18. StringBuilder signature = new StringBuilder();
  19. for (byte b : signHash) {
  20. signature.append(byteToHexString(b));
  21. }
  22. String sign=signature.toString();
  23. System.out.println("签名String值为:" + sign);
  24. }
  25. public static String byteToHexString(byte ib) {
  26. char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
  27. char[] ob = new char[2];
  28. ob[0] = Digit[(ib >>> 4) & 0X0f];
  29. ob[1] = Digit[ib & 0X0F];
  30. String str = new String(ob);
  31. return str;
  32. }
  33. }

发表评论

表情:
评论列表 (有 0 条评论,61人围观)

还没有评论,来说两句吧...

相关阅读