传输机密性要求
传输机密性是指确保信息在传输过程中不被未授权方获取或泄露。主要通过加密算法实现,如SM1、SM2及AES 等。在密评中,传输机密性的评估主要关注以下几个方面:
- 加密技术的应用:是否采用了加密算法(如SM1、SM2、AES等)对敏感信息进行加密传输,确保信息在传输过程中的保密性。
- 密钥管理:密钥的生成、存储、分发、更新和销毁等过程是否安全可控,以防止密钥泄露或被未授权使用。
- 传输协议的安全性:是否采用了安全的传输协议(如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.p12
的 PKCS12
格式的密钥库文件,有效期为 10 年(3650 天)。
2、设置密码: 在生成过程中,keytool 会提示你输入密钥库密码和密钥密码。请记住这些密码,因为稍后在 Spring Boot 配置中会用到。
配置 Spring Boot 应用程序
在 Spring Boot 应用程序中配置 SSL 需要修改 application.properties
或 application.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.properties
或application.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-14 21:58