传输机密性要求

传输机密性是指确保信息在传输过程中不被未授权方获取或泄露。主要通过加密算法实现,如SM1、SM2及AES 等。在密评中,传输机密性的评估主要关注以下几个方面:

  1. 加密技术的应用:是否采用了加密算法(如SM1、SM2、AES等)对敏感信息进行加密传输,确保信息在传输过程中的保密性。
  2. 密钥管理:密钥的生成、存储、分发、更新和销毁等过程是否安全可控,以防止密钥泄露或被未授权使用。
  3. 传输协议的安全性:是否采用了安全的传输协议(如HTTPS、SSL/TLS等),以确保信息在传输过程中的机密性和完整性。

业务流程图

实现&场景应用

客户端请求服务端

客户端
  • 对含有敏感数据的请求入参整体参数流进行 对称加密;
  • 对 对称加密后的密文 进行 自定义混淆 处理并得到 Data 参数;
  • 对 Data 参数按约定好的格式设置到 Request管道参数流;
  • Header 添加请求参数加密标识 X-IsEncrypt。
服务端
  • 服务端 Request 请求管道根据 Header 客户端给的标识来判断并处理参数流;

  • 标识为明文,则直接跳过,假设 Header 的 X-IsEncrypt 为密文标识则出现以下处理:

  • 1.获取管道参数流、并序列化得到 Data 参数;

  • 2.对不为空的 Data 参数进行 自定义混淆 处理并得到待解密的 对称加密后的密文 ;

  • 3.对 对称加密后的密文 进行 对称解密 得到明文参数流;

  • 4.把明文参数流设置到 Request管道参数流。

  • GET:

GET请求方式参数流长度有限制,直接选择POST。

  • POST

{
“Data”: “PTBUUlVoRldHRm1kSmxYWVlCWFJObFdSd2dFT1BGbVdRRlRPaTEwYjRVVlN2WjNja1ZHUlJwMlJRWmtN”
}

服务端响应客户端

业务状态码200
  • 服务端
    • 对含有敏感数据的业务整体参数流进行 对称加密;
    • 对 对称加密后的密文 进行 自定义混淆 处理并得到 Data 参数;
    • 对 Data 参数按约定好的格式设置到 Request管道参数流;
    • 返回值基类 IsEncrypt 设置参数加密标识。
  • 客户端
    • 客户端 Response 响应管道根据返回值 IsEncrypt 客户端给的标识来判断并处理参数流;
    • 标识为明文,则直接跳过,假设返回值 IsEncrypt 为密文标识则出现以下处理:
    • 1.获取 Data 参数,并对不为空的 Data 参数进行 自定义混淆 处理并得到待解密的 对称加密后的密文 ;
    • 2.对 对称加密后的密文 进行 对称解密 得到明文参数流;

技术方案

  • 1、使用 HTTPS: 确保所有的 HTTP 请求都通过 HTTPS 进行,以加密传输中的数据。
  • 2、TLS/SSL: 使用 TLS(传输层安全)或 SSL(安全套接字层)协议来加密数据传输。
  • 3、加密数据: 在传输前对敏感数据进行加密。

方案-1: 配置Nginx负载均衡的SSL证书(适用于使用Nginx进行负载均衡的环境)

#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
#error_page 404/404.html;
#HTTP_TO_HTTPS_START
if ($server_port !~ 443){
  rewrite ^(/.*)$ https://$host$1 permanent;
}
#HTTP_TO_HTTPS_END
ssl_certificate    /www/server/panel/vhost/cert/zhpjrz.91118.com/fullchain.pem;
ssl_certificate_key    /www/server/panel/vhost/cert/zhpjrz.91118.com/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
error_page 497  https://$host$request_uri;
#SSL-END

方案-2: Spring Boot 应用程序配置 SSL/TLS(适用于使用单体无负载均衡的环境)

在 Spring Boot 应用程序中配置 SSL(Secure Sockets Layer)或 TLS(Transport Layer Security)可以确保数据在客户端和服务器之间传输时的安全性。SSL/TLS 通过加密通信来保护数据的机密性和完整性。

生成自签名证书

在开发环境中,通常使用自签名证书来配置 SSL。以下是生成自签名证书的步骤:

1、使用 JDK 的 keytool 工具生成证书:
keytool -genkeypair -alias myapp -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore myapp.p12 -validity 3650

这条命令会生成一个名为 myapp.p12PKCS12 格式的密钥库文件,有效期为 10 年(3650 天)。

2、设置密码: 在生成过程中,keytool 会提示你输入密钥库密码和密钥密码。请记住这些密码,因为稍后在 Spring Boot 配置中会用到。
配置 Spring Boot 应用程序

在 Spring Boot 应用程序中配置 SSL 需要修改 application.propertiesapplication.yml 文件。

使用 application.properties
# HTTPS通常使用443端口,但8443是常用的测试端口
server.port=8443
server.ssl.key-store=classpath:myapp.p12
server.ssl.key-store-password=your_keystore_password
server.ssl.key-password=your_key_password
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=myapp
使用 application.yml
server:
  # HTTPS通常使用443端口,但8443是常用的测试端口
  port: 8443
  ssl:
    key-store: classpath:myapp.p12
    key-store-password: your_keystore_password
    key-password: your_key_password
    key-store-type: PKCS12
    key-alias: myapp
验证 SSL 配置

启动 Spring Boot 应用程序后,可以通过以下方式验证 SSL 配置是否成功:

  • 1、浏览器访问: 打开浏览器并访问 https://localhost:8443 。由于使用的是自签名证书,浏览器会提示证书不受信任,但你可以继续访问。
  • 2、使用 curl 命令
    curl -k https://localhost:8443
    使用 -k 选项可以忽略证书验证错误。
生产环境中的 SSL 配置

在生产环境中,建议使用由受信任的证书颁发机构(CA)签发的证书。以下是配置步骤:

  • 1、获取证书: 从受信任的 CA 获取 SSL 证书。
  • 2、配置证书: 将证书文件(通常是 .crt.pem 格式)和私钥文件(通常是 .key 格式)放置在 Spring Boot 应用程序的资源目录中。
  • 3、更新配置文件: 根据证书文件的格式和位置,更新 application.propertiesapplication.yml 文件。

方案-3: 前端在传输前对敏感数据进行加密,后端服务进行解密(SM4 算法)

考虑到国内的情况,优先使用国密 SM4 算法,主要思路是前端在传输前对 Query 参数 和 Body 进行SM4 加密,后端服务在 Gateway 统一进行SM4解密

前端加密
  • 1、引入 gm-crypt
npm install gm-crypt
  • 2、封装js

sm4Util.js

// import { SM4, SM3, SM2 } from 'gm-crypto';
const SM4 = require('gm-crypt').sm4;
const pwdKey = 'xxx'; // 密钥 前后端一致即可,后端提供
let sm4Config = {
  key: pwdKey, // key值要与后端的一致,后端解密是根据这个key
  mode: 'cbc', // 加密的方式有两种,ecb和cbc两种,也是看后端如何定义的,cbc的话下面还要加一个iv的参数,ecb不用
  iv: pwdKey, // iv是cbc模式的第二个参数,也需要跟后端配置的一致 iv是initialization vector的意思,就是加密的初始化矢量,初始化加密函数的变量,也叫初始向量
  cipherType: 'base64',
};

/*
 * 加密工具函数
 * @param {String} text 待加密文本
 * @param key string 加密key(16位)
 * @param iv string 偏移向量(16位)
 */
export function SM4Encrypt(text, key = pwdKey, iv = pwdKey) {
  sm4Config.key = key;
  sm4Config.iv = iv;
  const sm4Util = new SM4(sm4Config); // new一个sm4函数,将sm4Config作为参数传递进去
  return sm4Util.encrypt(text, key);
}

/*
 * 解密工具函数
 * @param {String} text 待解密密文
 * @param key string 加密key(16位)
 * @param iv string 偏移向量(16位)
 */
export function SM4Decrypt(text, key = pwdKey, iv = pwdKey) {
  sm4Config.key = key;
  sm4Config.iv = iv;
  const sm4Util = new SM4(sm4Config); // new一个sm4函数,将sm4Config作为参数传递进去
  return sm4Util.decrypt(text, key);
}
  • 3、使用
import { SM4Encrypt, SM4Decrypt } from '@/util/sm4Util.js';

const key = 'xxx'; // 密钥,由后端定义,可以从接口获取
const iv = 'xxx'; // 偏移向量,由后端定义,可以从接口获取
const enPw = SM4Encrypt(this.form.password, key, iv); // 加密
const dePw = SM4Decrypt (enPw, key, iv); // 解密
后端解密
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.crypto.symmetric.SymmetricCrypto;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * SM4 对称加密工具类
 */
public class SM4Util {

    private static Map<String, SM4> sm4Map = new ConcurrentHashMap<>();

    /**
     * 获取SM4
     * @param key 密钥,支持三种密钥长度:128、192、256位
     * @param iv 偏移向量,加盐
     * @return AES
     */
    public static SM4 getSm4( String key, String iv) {
        return sm4Map.computeIfAbsent(key + iv, k -> new SM4(Mode.CBC, Padding.PKCS5Padding, key.getBytes(CharsetUtil.CHARSET_UTF_8),
                iv.getBytes(CharsetUtil.CHARSET_UTF_8)));
    }

    /**
     * SM4-cbc加密
     */
    public static String encrypt(String key, String iv, String plainTxt) {
        SymmetricCrypto sm4 = getSm4(key, iv);
        byte[] encrypHex = sm4.encrypt(plainTxt);
        return Base64.encode(encrypHex);
    }

    /**
     * SM4-cbc解密
     */
    public static String decrypt(String key, String iv, String cipherTxt) {
        SymmetricCrypto sm4 = getSm4(key, iv);
        byte[] cipherHex = Base64.decode(cipherTxt.trim());
        return sm4.decryptStr(cipherHex, CharsetUtil.CHARSET_UTF_8);
    }

}

方案-4: 前端在传输前对敏感数据进行加密,后端服务进行解密(AES 算法)

除了使用国密 SM4 算法,还可以使用 AES 算法,进行加解密操作。主要思路是前端在传输前对 Query 参数 和 Body 进行AES 加密,后端服务在 Gateway 统一进行AES 解密

前端加密
  • 1、引入 crypto-js
npm install crypto-js
  • 2、封装js

aesUtil.js

import CryptoJS from 'crypto-js';

let aesConfig = {
  mode: CryptoJS.mode.ECB, // 加密的方式有两种,ecb和cbc两种,也是看后端如何定义的,cbc的话下面还要加一个iv的参数,ecb不用
  iv: pwdKey, // iv是cbc模式的第二个参数,也需要跟后端配置的一致 iv是initialization vector的意思,就是加密的初始化矢量,初始化加密函数的变量,也叫初始向量
  cipherType: 'base64',
};

/*
 * 加密工具函数
 * @param {String} text 待加密文本
 * @param key string 加密key
 */
export function AESEncrypt(text, key) {
  return CryptoJS.AES.encrypt(text, key).toString();
  // 法一:加密后转化为base64
  // let srcs = CryptoJS.enc.Utf8.parse(text);
  // let encrypted = CryptoJS.AES.encrypt(srcs, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 });
  // return encrypted.ciphertext.toString();
CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
  // 法二:不转
  let srcs = CryptoJS.enc.Utf8.parse(text);
  let encrypted = CryptoJS.AES.encrypt(srcs, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 });
  return encrypted.toString();
}

/*
 * 解密工具函数
 * @param {String} text 待解密密文
 * @param key string 加密key
 */
export function AESDecrypt(text, key) {
  const bytes = CryptoJS.AES.decrypt(text, key);
  return bytes.toString(CryptoJS.enc.Utf8);

  // 法一:对应解密
  // let encryptedHexStr = CryptoJS.enc.Hex.parse(word);
  // let srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr);
  // let decrypt = CryptoJS.AES.decrypt(srcs, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 });
  // let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
  // return decryptedStr.toString();

  // 法二:
  var decrypt = CryptoJS.AES.decrypt(word, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
  return CryptoJS.enc.Utf8.stringify(decrypt).toString();
}
  • 3、使用
import { AESEncrypt, AESDecrypt } from '@/util/aesUtil.js';

 // 加密
const encryptedData = AESEncrypt(data, key);
console.log('Encrypted:', encryptedData);

// 解密
const decryptedData = AESDecrypt(encryptedData, key);
console.log('Decrypted:', decryptedData);
后端解密
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.SymmetricCrypto;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * AES 对称加密工具类
 */
public class AESUtil {

    private static Map<String, AES> aesMap = new ConcurrentHashMap<>();

    /**
     * 获取aes
     * @param key 密钥,支持三种密钥长度:128、192、256位
     * @param iv 偏移向量,加盐
     * @return AES
     */
    public static AES getAes( String key, String iv) {
        return aesMap.computeIfAbsent(key + iv, k -> new AES(Mode.CBC, Padding.PKCS5Padding, key.getBytes(CharsetUtil.CHARSET_UTF_8),
                iv.getBytes(CharsetUtil.CHARSET_UTF_8)));
    }

    /**
     * AES-cbc加密
     */
    public static String encrypt(String key, String iv, String plainTxt) {
        SymmetricCrypto aes = getAes(key, iv);
        byte[] encrypHex = aes.encrypt(plainTxt);
        return Base64.encode(encrypHex);
    }

    /**
     * AES-cbc解密
     */
    public static String decrypt(String key, String iv, String cipherTxt) {
        SymmetricCrypto aes = getAes(key, iv);
        byte[] cipherHex = Base64.decode(cipherTxt.trim());
        return aes.decryptStr(cipherHex, CharsetUtil.CHARSET_UTF_8);
    }

}

方案-5: 前端在传输前对敏感数据进行加密,后端服务进行解密(外部加解密服务)

在方案-3、方案-4 中都是系统内部,前后端进行加密解密的操作,在一些场景中,加解密服务有专门的服务负责,前后端仅需求去调用接口即可

前端加密

在请求之前调用加密接口,进行加密

// TODO
后端解密

CryptoUtil


import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import okhttp3.OkHttpClient;
import okhttp3.spring.boot.OkHttp3Template;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * 外部加解密工具类
 */
public class CryptoUtil {

    private static OkHttpClient client;
    private static OkHttp3Template okHttp3Template;
    private static ObjectMapper objectMapper;

    static {

        objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.enable(MapperFeature.USE_GETTERS_AS_SETTERS);
        objectMapper.enable(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS);
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        client = new OkHttpClient().newBuilder().connectTimeout(5000, TimeUnit.MILLISECONDS)
                // .hostnameVerifier(okhttpHostnameVerifier)
                // .followRedirects(properties.isFollowRedirects())
                // .followSslRedirects(properties.isFollowSslRedirects())
                .pingInterval(1, TimeUnit.MILLISECONDS).readTimeout(3000, TimeUnit.MILLISECONDS)
                .retryOnConnectionFailure(true)
                // .sslSocketFactory(trustedSSLSocketFactory, trustManager)
                .writeTimeout(3, TimeUnit.SECONDS)
                // Application Interceptors、Network Interceptors :
                // https://segmentfault.com/a/1190000013164260
                // .addNetworkInterceptor(loggingInterceptor)
                // .addInterceptor(headerInterceptor)
                .build();

        okHttp3Template = new OkHttp3Template(client, objectMapper);
    }

    /**
     * 调用加密接口
     */
    public static String encrypt(String plainTxt) {
        try {
            return okHttp3Template.get("http://localhost:8080/encrypt?plainTxt=" + plainTxt, String.class);
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 调用解密接口
     */
    public static String decrypt(String cipherTxt) {
        try {
            return okHttp3Template.get("http://localhost:8080/decrypt?plainTxt=" + cipherTxt, String.class);
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 调用签名接口
     */
    public static String digest(String plainTxt) {
        try {
            return okHttp3Template.get("http://localhost:8080/digest?plainTxt=" + plainTxt, String.class);
        } catch (IOException e) {
            return null;
        }
    }

}
作者:Jeebiz  创建时间:2024-11-05 19:31
最后编辑:Jeebiz  更新时间:2024-11-14 21:58