欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

(一篇了解国密加密的文章) Vue + Springboot 前端和后端使用国密算法 SM2 SM3 SM4 进行数据加密和传输 完整解决方案

最编程 2024-07-10 15:39:28
...

(一篇文章搞懂国密加密)Vue + Springboot 前后端使用国密算法 SM2 SM3 SM4 进行数据加密传输 完整解决方案

一般国企、事业单位类项目部署到外网时候,常常会用到数据加密,目前现有技术方案, 经常会出现版本插件版本和脚手架版本不一致,导致引入失败。以下示例采用最新vue-cli及spring-boot3进行编写,可以直接进行使用,需要示例代码,可以留言或者私信。

1. 后端依赖

主要依赖

	<dependencies>
    	<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcprov-jdk15on</artifactId>
			<version>1.68</version>
		</dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.23</version>
        </dependency>
	</dependencies>

2. 前端依赖

  "dependencies": {
    "gm-crypt": "0.0.2",
    "sm-crypto": "^0.3.13",
    "vue": "^2.6.14",
    "element-ui": "^2.15.14",
  }

image-20231220002741196.png

image-20231220002801760.png

image-20231220002817889.png

3. SM3加密

3.1 直接对文件加密

前端代码:

      <template>
    	  <input type="file" ref="fileInput" @change="handleFileChange" />
             <el-button @click="encryptFile" type="primary">加密</el-button>
      </template>
	 methods: {
        handleFileChange(event) {
          this.file = event.target.files[0];
        },
        encryptFile() {
              if (!this.file) {
                alert("Please select a file.");
                return;
              }
              // 1. sm3文件加密(无密钥)
              const reader = new FileReader();
              reader.onload = (e) => {
                const fileContent = e.target.result;
                const hash = smcrypto.sm3(fileContent);
                console.log(hash); // 加密结果示例:610c2432d36e242b63297cb21afd0ead7c94ed810fd19e871ed225dc85178f9e
                this.encryptedDataSM3NoKey = hash;
              };
              reader.readAsText(this.file);
    	},
   	}

后端代码:

    @PostMapping(value = "/upload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    @Operation(summary = "直接加密")
    public String handleFileUpload(
            @RequestPart(value = "Upload files", required = false) MultipartFile file) {
        if (file.isEmpty()) {
            return "File is empty";
        }

        try (InputStream inputStream = file.getInputStream()) {
            // Save the file and get its path
            String utf8Content = convertToUTF8(inputStream);
            // Encrypt the file with SM3
            String encryptedData = encryptWithSM3(utf8Content);
            // 610c2432d36e242b63297cb21afd0ead7c94ed810fd19e871ed225dc85178f9e
            return "Encrypted Data: " + encryptedData;
        } catch (IOException e) {
            return "Failed to process the file: " + e.getMessage();
        }
    }

3.2 使用秘钥对文件加密

前端代码:

      <template>
      <input type="file" ref="fileInput" @change="handleFileChange" />
      <el-button @click="encryptFileSM3" type="primary">加M</el-button>
      </template>
         methods: {
            handleFileChange(event) {
              this.file = event.target.files[0];
            },
            encryptFileSM3() {
              if (!this.file) {
                alert("Please select a file.");
                return;
              }

              const reader = new FileReader();
              reader.onload = () => {
                const uint = new Uint8Array(reader.result);
                // 使用 SM3 加密文件内容
                const hash = smcrypto.sm3(uint, {
                  mode: "hmac",
                  key: "123456789",
                });
                this.encryptedDataSM3 = hash;
              };
              reader.readAsArrayBuffer(this.file); // 加密结果示例: c0760f71661088afbf3c56cd1f860da06d0801d625c5cf87d86f197bf67956d6
            },
   	}

后端代码:

    @PostMapping(value = "/keyUpload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    @Operation(summary = "密钥加密(123456789)")
    public String handleKeyFileUpload(
            @RequestPart(value = "Upload files", required = false) MultipartFile file)  throws FileNotFoundException {
        try (InputStream inputStream = file.getInputStream()) {
            String fileHash = SmUtil.hmacSm3(HexUtil.decodeHex("123456789")).digestHex(inputStream);
            return "Encrypted Data: " + fileHash;
        } catch (IOException e) {
            return "Failed to process the file: " + e.getMessage();
        }
    }

4. SM2

前端代码:

      const sm2 = require("sm-crypto").sm2;
      let keypair = sm2.generateKeyPairHex();
      // 公钥
      let publicKey = keypair.publicKey.toUpperCase();
      // 私钥
      let privateKey = keypair.privateKey.toUpperCase();

      this.encryptedDataSM2 = sm2.doEncrypt(this.dataSM2, publicKey);
      this.docryptedDataSM2 = sm2.doDecrypt(this.encryptedDataSM2, privateKey);

后端代码:

  /**
     * 生成秘钥对
     *
     * @return 公钥和私钥
     */
    @Operation(summary = "生成秘钥对")
    @GetMapping(value = "/generator")
    public static Map<String, String> generator() {
        SM2 sm2 = SmUtil.sm2();
        String publicKey = HexUtil.encodeHexStr(((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false)).toUpperCase();
        String privateKey = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(sm2.getPrivateKey())).toUpperCase();
        return new HashMap<String, String>(2) {{
            put("publicKey", publicKey);
            put("privateKey", privateKey);
        }};
    }
    /**
     * 加密
     *
     * @param publicKey 公钥
     * @param data 明文
     * @return 密文
     */
    @Operation(summary = "加密")
    @PostMapping(value = "/encrypt")
    public static String encrypt(@Parameter(name = "publicKey", description = "公钥", in = ParameterIn.QUERY, required = true) @RequestParam String publicKey,
                                 @Parameter(name = "data", description = "明文", in = ParameterIn.QUERY, required = true) @RequestParam String data) {
        return SmUtil.sm2(null, publicKey)
                // 不写默认就是C1C3C2
                .setMode(SM2Engine.Mode.C1C3C2)
                .encryptHex(data.getBytes(), KeyType.PublicKey)
                // 加密后,密文前面会有04,需要去掉
                .substring(2);
    }

    /**
     * 解M
     *
     * @param privateKey 私钥
     * @param data M文
     * @return 明文
     */
    @Operation(summary = "解密")
    @PostMapping(value = "/decrypt")
    public static String decrypt(@Parameter(name = "privateKey", description = "私钥", in = ParameterIn.QUERY, required = true) @RequestParam String privateKey,
                                 @Parameter(name = "data", description = "M文", in = ParameterIn.QUERY, required = true) @RequestParam String data) {
        // 确定前端不会加04,所以后端直接加(上面处理方式可能造成报错(Invalid point coordinates):原因前端加密后M文自带04开头)
        data = "04" + data;
        return SmUtil.sm2(privateKey, null)
                // 不写默认就是C1C3C2
                .setMode(SM2Engine.Mode.C1C3C2)
                .decryptStr(data, KeyType.PrivateKey);
    }

5. SM4 && SM2混合加密

加M过程梳理

  1. 后端准备sm2公钥和私钥
  2. 后端将SM2公钥传输到前端
  3. 前端生成SM4M钥
  4. 前端使用SM2公钥加MSM4秘钥,获得密文
  5. 使用SM4秘钥加密数据
  6. 将密文和加密数据传输至后端
  7. 后端使用SM2私钥解密密文获得,SM4秘钥
  8. 使用SM4秘钥解密加密数据,获取真实传输数据信息

前端代码:

   <template>
    	  <el-card shadow="hover">
              <div slot="header" class="clearfix">
                <span>sm2&&sm4示例:</span>
              </div>
              <el-button
                @click="encryptFileSM4BySM2"
                style="margin-left: 50px"
                type="primary"
                >加密</el-button
              >
              <p style="width: 90vw; word-wrap: break-word; color: red">
                密钥随机密钥(sm4): <span style="color: #333333">{{ sm4Key }}</span>
              </p>
              <p style="width: 90vw; word-wrap: break-word; color: red">
                公钥(sm2): <span style="color: #333333">{{ sm2PublicKey }}</span>
              </p>
              <p style="color: red; word-wrap: break-word">
                SM4秘钥加密结果(by-sm2):
                <span style="color: #333333">{{ docryptedDataSM4BySM2 }}</span>
              </p>
              <el-input
                placeholder="请输入加密内容"
                style="width: 200px"
                v-model="dataSM4"
              />
              <el-button
                @click="decryptFileSM4"
                style="margin-left: 50px"
                type="primary"
                >加密(sm4)</el-button
              >
              <p style="color: red; word-wrap: break-word">
                加密结果(sm4):
                <span style="color: #333333">{{ decryptedDataSM4 }}</span>
              </p>
            </el-card>
   </template>
         methods: {
           encryptFileSM4BySM2() {
              const sm2 = require("sm-crypto").sm2;
              this.docryptedDataSM4BySM2 = sm2.doEncrypt(
                this.sm4Key,
                this.sm2PublicKey
              );
            },
            decryptFileSM4() {
              const SM4 = require("gm-crypt").sm4;
              const pwdKey = this.sm4Key; //密钥 前后端一致,后端提供
              let sm4Config = {
                key: pwdKey,
                mode: "ecb", // 加密的方式有两种,ecb和cbc两种,看后端如何定义的,cbc需要iv参数,ecb不用
                iv: "1234567891011121", // 初始向量,cbc模式的第二个参数,也需要跟后端配置的一致
                cipherType: "base64",
              };
              const sm4Util = new SM4(sm4Config); // new一个sm4函数,将上面的sm4Config作为参数传递进去。
              this.decryptedDataSM4 = sm4Util.encrypt(this.dataSM4, pwdKey);
            }
   		}

后端代码:

package com.example.springboot3.controller;

import cn.hutool.core.lang.Console;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.ECKeyUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.nio.charset.StandardCharsets;


@Tag(name = "sm4")
@RestController
@Slf4j
public class EncryptSM4Controller {

    // 公钥:这个是前后端加密用的,不压缩选择带04的,不带04到时候前端会报错
    private static String  publicKey = "04F8BA2A9DFDE5977DFDE3C87A3D0298809FF3396BD908B01DE7057EE4951CF4F193EB0841DA05D7612D13A13E23C0ACB8A00902C0D409236A92C4EF3AA2C72823";        // 私钥:这个保存好,切记不要泄漏,真的泄露了就重新生成一下;
    private static String privateKey = "3AEAE64C481550DF7D50B6A693378D0C3722947DFFBD55B43880912497126620";


    /**
     * 生成秘钥对
     *
     * @return 公钥和私钥
     */
    @Operation(summary = "sm2生成秘钥对")
    @GetMapping(value = "/generateCommonKey")
    public static Map<String, String> generateCommonKey() {
        SM2 sm2 = SmUtil.sm2();

        return new HashMap<String, String>(2) {{
            put("publicKey", publicKey);
            put("privateKey", privateKey);
        }};
    }

    /**
     * 解密
     *
     * @param data 密文
     * @return 明文
     */
    @Operation(summary = "sm2解M(获取sm4的key)")
    @PostMapping(value = "/decryptSm2")
    public static String decrypt(String data) {
        data = "04" + data;
        return SmUtil.sm2(privateKey, null)
                // 不写默认就是C1C3C2
                .setMode(SM2Engine.Mode.C1C3C2)
                .decryptStr(data, KeyType.PrivateKey);
    }

    /**
     * 加密
     *
     * @param sm4Key 秘钥
     * @param data 明文
     * @return 密文
     */
    @Operation(summary = "sm4加密")
    @PostMapping(value = "/encryptSm4")
    public static String encryptSm4(String sm4Key, String data) {
        SM4 sm4 = SmUtil.sm4(sm4Key.getBytes());
        String encrypted = sm4.encryptBase64(data);
        return encrypted;
    }

    /**
     * 解密
     *
     * @param sm4Key 秘钥
     * @param data 明文
     * @return 密文
     */
    @Operation(summary = "sm4解密")
    @PostMapping(value = "/decryptSm4")
    public static String decryptSm4(String sm4Key, String data) {
        SymmetricCrypto sm4 = SmUtil.sm4(sm4Key.getBytes());
        byte[] decrypted = sm4.decrypt(data);
        // 将字节数组转换为字符串
        String decryptedString = new String(decrypted, StandardCharsets.UTF_8);

        return decryptedString;
    }

}

相关文章

 【1】 Vue + Springboot 前后端完整使用国密算法 SM2 数据加密 传输 交互 完整解决方案

【2】 Vue和Springboot实现SM4加密和解密(前、后端均可)很新

【3】java/vue使用国密sm2、sm3、sm4进行数据加密

【4】前端使用国密sm2和sm4进行加解密