Spring Boot 项目自定义加解密实现配置文件的加密

在Spring Boot项目中, 可以结合Jasypt 快速实现对配置文件中的部分属性进行加密。 完整的介绍参照:

Spring Boot + Jasypt 实现application.yml 属性加密的快速示例

但是作为一个技术强迫症,总是想着从底层开始实现属性的加解密,以解答这背后机制的疑惑。

本篇在不使用Jasypt 的状况下,实现配置文件的加密,并且应用启动的时候自动解密加密属性以供系统使用。

1. 定义一个加解密的工具类

/**
 * Copyright (C)  Oscar Chen(XM):
 * 
 * Date: 2025-01-07
 * Author: XM
 */

package com.osxm.sb.encyproperties.util;

import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class SecurityUtil {
    private static final String AES_GCM_NOPADDING = "AES/GCM/NoPadding";
    private static final int GCM_TAG_LENGTH = 128;
    private static final int GCM_IV_LENGTH = 12;

    public static SecretKey generateKeyByPassword(String password) throws Exception {
        // 使用SHA-256哈希函数
        MessageDigest sha = MessageDigest.getInstance("SHA-256");
        byte[] keyBytes = sha.digest(password.getBytes("UTF-8"));

        // AES-256需要一个长度为256位的密钥,所以取哈希的前32字节
        keyBytes = Arrays.copyOf(keyBytes, 32);

        // 创建密钥
        SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
        return secretKey;
    }

    public static String encrypt(String data, String password) throws Exception {
        SecretKey key = generateKeyByPassword(password);
        return encrypt(data, key);
    }

    public static String encrypt(String data, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance(AES_GCM_NOPADDING);
        byte[] iv = new byte[GCM_IV_LENGTH];
        SecureRandom.getInstanceStrong().nextBytes(iv);
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);
        byte[] encryptedData = cipher.doFinal(data.getBytes());
        byte[] encryptedDataWithIv = new byte[iv.length + encryptedData.length];
        System.arraycopy(iv, 0, encryptedDataWithIv, 0, iv.length);
        System.arraycopy(encryptedData, 0, encryptedDataWithIv, iv.length, encryptedData.length);
        return Base64.getEncoder().encodeToString(encryptedDataWithIv);
    }

    public static String decrypt(String data, String password) throws Exception {
        SecretKey key = generateKeyByPassword(password);
        return decrypt(data, key);
    }

    public static String decrypt(String encryptedDataWithIv, SecretKey key) throws Exception {
        byte[] decodedData = Base64.getDecoder().decode(encryptedDataWithIv);
        Cipher cipher = Cipher.getInstance(AES_GCM_NOPADDING);
        byte[] iv = new byte[GCM_IV_LENGTH];
        System.arraycopy(decodedData, 0, iv, 0, iv.length);
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
        cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);
        byte[] encryptedData = new byte[decodedData.length - iv.length];
        System.arraycopy(decodedData, iv.length, encryptedData, 0, encryptedData.length);
        byte[] decryptedData = cipher.doFinal(encryptedData);
        return new String(decryptedData);
    }
}

2. 定义一个继承EnumerablePropertySource的类: EncryptedPropertySource

EncryptedPropertySource用于将数据库密码、API密钥等敏感配置信息以加密的形式存储。这样,即使配置文件(如.properties文件或环境变量)被未经授权的人员访问,他们也无法直接获取到敏感的明文信息,因为信息已经被加密处理。

/**
 * Copyright (C)  Oscar Chen(XM):
 * 
 * Date: 2025-01-07
 * Author: XM
 */
package com.osxm.sb.encyproperties.config;

import java.util.HashMap;
import java.util.Map;

import org.springframework.core.env.EnumerablePropertySource;

import com.osxm.sb.encyproperties.util.SecurityUtil;

public class EncryptedPropertySource extends EnumerablePropertySource<Map<String, Object>> {

    private final Map<String, Object> properties = new HashMap<>();

    public EncryptedPropertySource(String name, Map<String, Object> source) {
        super(name, source);
        source.forEach((key, value) -> {
            if (value != null) {
                try {
                    properties.put(key, decrypt(value.toString()));
                } catch (Exception e) {
                    properties.put(key, value);

                }

            }
        });
    }

    @Override
    public String[] getPropertyNames() {
        return properties.keySet().toArray(new String[0]);
    }

    @Override
    public Object getProperty(String name) {
        return properties.get(name);
    }

    private String decrypt(String encryptedValue) throws Exception {
        // 在这里实现你的解密逻辑
        // 例如,假设加密值是 "ENC(encryptedPassword)" 格式
        if (encryptedValue.startsWith("ENC(") && encryptedValue.endsWith(")")) {
            String encryptedPassword = encryptedValue.substring(4, encryptedValue.length() - 1);
            // 这里使用简单的Base64解码作为示例,你可以使用更复杂的解密算法
            return SecurityUtil.decrypt(encryptedPassword, "oscar");
        }
        return encryptedValue;
    }
}

3. 在启动类中添加初始化动作

在 Spring Boot 中,addInitializers方法通常用于向ConfigurableApplicationContext添加自定义的ApplicationContextInitializerApplicationContextInitializer是一个接口,允许你在ApplicationContext刷新之前进行一些初始化工作。这在一些高级配置和启动时设置环境变量或属性时非常有用。

在Spring Boot中,environment对象通常指的是Environment接口的一个实例,它提供了访问应用程序属性源(PropertySource)的方法。PropertySource是一个抽象,它定义了如何访问一组属性(键值对)。

environment.getPropertySources().addFirst(...)这行代码的作用是将一个新的PropertySource实例添加到当前Environment的属性源列表的最前面。这意味着当Spring Boot查找某个属性时,它会首先在这个新添加的PropertySource中查找,如果找不到,才会继续在其他属性源中查找。

这种方法通常用于覆盖或添加额外的配置属性,特别是在需要确保某些属性具有最高优先级时。例如,你可能希望在应用程序启动时从外部源(如环境变量、命令行参数或加密的配置服务)加载配置,并确保这些配置覆盖任何默认或先前加载的配置。
这里的完整启动类代码如下:

@SpringBootApplication
public class EncypropertiesApplication {

	public static void main(String[] args) {
		SpringApplication application = new SpringApplication(EncypropertiesApplication.class);
		application.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
			@Override
			public void initialize(ConfigurableApplicationContext applicationContext) {
				ConfigurableEnvironment environment = applicationContext.getEnvironment();
				try (InputStream input = new ClassPathResource("application.yml").getInputStream()) {
					Yaml yaml = new Yaml();
					Map<String, Object> yamlProperties = yaml.load(input);
					Map<String, Object> flattenedProperties = new HashMap<>();
					flattenMap(yamlProperties, flattenedProperties, null);
					EncryptedPropertySource encryptedPropertySource = new EncryptedPropertySource("encryptedProperties",
							flattenedProperties);
					environment.getPropertySources().addFirst(encryptedPropertySource);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			private void flattenMap(Map<String, Object> source, Map<String, Object> target, String path) {
				source.forEach((key, value) -> {
					String fullPath = (path == null ? key : path + "." + key);
					if (value instanceof Map) {
						flattenMap((Map<String, Object>) value, target, fullPath);
					} else {
						target.put(fullPath, value);
					}
				});
			}
		});
		application.run(args);
	}

}

4. 验证

以上配置是否生效,可以通过Debug 模式调试加密字符串是否正常被解密。
在这里插入图片描述

完整的测试是在 application.yml 配置数据库的密码,整合起来验证是否能正确连接DB。

spring:
  application:
    name: encyproperties

  datasource:
    url: jdbc:mysql://localhost/osxmdb
    username: root
    password: ENC(gfsTDJv1lgAfp4q0XcrhMp3+ijj8T2Go/UPdYbPJPXa6PQ==)
    driver-class-name: com.mysql.cj.jdbc.Driver

本篇完整示例

  • https://github.com/osxm/springbootency/tree/main/encyproperties


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/949877.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

A/B实验之置信检验(一):如何避免误判 (I类) 和漏报 (II类)

假设检验的依据&#xff1a;如何避免误判和漏报 A/B实验系列相关文章&#xff08;置顶&#xff09; 1.A/B实验之置信检验&#xff08;一&#xff09;&#xff1a;如何避免误判和漏报 2.A/B实验之置信检验&#xff08;二&#xff09;&#xff1a;置信检验精要 引言 在数据驱动…

每日一题:链表中环的入口结点

文章目录 判断链表环的入口节点描述数据范围&#xff1a;复杂度要求&#xff1a;输入输出 示例代码实现思路解析注意事项&#xff1a; 判断链表环的入口节点 描述 给定一个链表&#xff0c;判断该链表是否存在环。如果存在环&#xff0c;返回环的入口节点&#xff1b;如果不存…

深度学习blog-Meanshift均值漂移算法-最大熵模型

均值漂移&#xff08;Mean Shift&#xff09;是一种无监督的聚类算法&#xff0c;广泛应用于数据挖掘和计算机视觉任务。它通过移动样本点到其近邻的均值位置来寻找数据的高密度区域&#xff0c;最终形成聚类。 均值漂移算法原理 均值漂移算法的核心思想是通过滑动窗口&#…

51c自动驾驶~合集45

我自己的原文哦~ https://blog.51cto.com/whaosoft/13020031 #运动控制和规划控制需要掌握的技术栈~ 各大垃圾家电造车厂又要开始了~~~​ 1、ROS的通信方式 李是Lyapunov的李&#xff1a;谈谈ROS的通信机制 话题通信和服务通信&#xff0c;其中话题通信是通过发布和订阅…

Python基于jieba和wordcloud绘制词云图

【Cesium】自定义材质&#xff0c;添加带有方向的滚动路线 &#x1f356; 前言&#x1f3b6;一、实现过程✨二、代码展示&#x1f3c0;三、运行结果&#x1f3c6;四、知识点提示 &#x1f356; 前言 Python基于jieba和wordcloud绘制词云图 &#x1f3b6;一、实现过程 读取文本…

计算机网络与服务器

目录 架构体系及相关知识 三层架构&#xff1a; 四层架构&#xff1a; 常见的应用的模式&#xff1a; OSI模型 分层 数据链路层 TCP/IP模型 TCP和UDP都是传输层的协议 TCP三次握手、四次次分手 URL&HTTP协议详解 网址URL 结构化 报文行 报文头 空行 报文体…

Cursor实现go项目配置并实现仓库Gin项目运行

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f49e;当前专栏&#xff1a;知识备份 ✨特色专栏&#xff1a;知识分享 &#x…

141.环形链表 142.环形链表II

141.环形链表 & 142.环形链表II 141.环形链表 思路&#xff1a;快慢指针 or 哈希表 快慢指针代码&#xff1a; class Solution { public:bool hasCycle(ListNode *head) {if(headnullptr||head->nextnullptr)return false;ListNode *fasthead->next; //不能设置成…

信用租赁系统助力企业实现免押金租赁新模式

内容概要 在现代商业环境中&#xff0c;信用租赁正在迅速崛起。通过结合大数据与区块链技术&#xff0c;信用租赁系统彻底改变了传统的租赁流程。什么是信用租赁呢&#xff1f;简单说&#xff0c;就是不需要押金&#xff0c;你也能够租到你想要的物品&#xff0c;这对企业和消…

el-select下拉框在弹框里面错位

问题出现 Element Plus 是一个基于 Vue 3 的组件库&#xff0c;el-select 是其中一个用于选择器的组件。在 el-select 组件中&#xff0c;teleported 属性用于控制下拉菜单的渲染位置。 解决方法 teleported 属性「element-plus」 popper-append-to-body属性「element」 ‌…

IO进程day1

一、思维导图

力扣-21-合并两个有序链表

思路&#xff1a; 因为是升序的两个链表&#xff0c;我们可以进行数据域比大小&#xff0c;然后把p3(自己创建的)的指针域指向小的那个 注&#xff1a;一定要先判断两个指针为0的情况

人工智能的发展领域之GPU加速计算的应用概述、架构介绍与教学过程

文章目录 一、架构介绍GPU算力平台概述优势与特点 二、注册与登录账号注册流程GPU服务器类型配置选择指南内存和存储容量网络带宽CPU配置 三、创建实例实例创建步骤镜像选择与设置 四、连接实例SSH连接方法远程桌面配置 一、架构介绍 GPU算力平台概述 一个专注于GPU加速计算的…

QT实现 端口扫描暂停和继续功能 3

上篇QT给端口扫描工程增加线程2-CSDN博客 为按钮pushButton_Stop添加clicked事件&#xff0c;功能为暂停扫描&#xff0c;并在暂停后显示继续按钮&#xff0c;点击继续按钮之后继续扫描 1.更新UI 添加继续按钮 点击转到槽则会自动声明 2. 更新 MainWindow.h 需要新增的部分…

汽车微处理器安全机制以及测试介绍

本文介绍了三类汽车微处理器安全机制&#xff1a;硬件类、软件类和混合类&#xff0c;旨在提高系统的可靠性和安全性。硬件类安全机制包括逻辑内建自测试&#xff08;Logic-BIST&#xff09;、三重模块冗余&#xff08;TMR&#xff09;、内存内建自测试&#xff08;Memory-BIST…

【Azure Redis 缓存】Azure Redis 遇见的连接不上问题和数据丢失的情况解答

问题描述 PHP应用再连接Azure Redis服务时&#xff0c;出现Connection Timed out。当通过升级提高Azure Redis的性能时候&#xff0c;发现之前的数据丢失了。 image.png 问题解答 当Redis服务出现Timeout的情况时&#xff0c;可以从Redis服务的指标(Metrics)开始查看&#xff0…

python学习笔记—15—数据容器之列表

1. 数据容器 列表(list)、元组(tuple)、字符串(str)、集合(set)、字典(dict) 2. 列表 (1) 定义 tmp_list ["super", "carry", "doinb"] print(f"tmp_list {tmp_list}, tmp_list type is {type(tmp_list)}") tmp_list1 ["doi…

记录一次面试中被问到的问题 (HR面)

文章目录 一、你对公司的了解多少二、为什么对这个岗位感兴趣三、不能说的离职原因四、离职原因高情商回复五、你的核心优势是什么六、你认为你比其他面试候选人的优势是什么七、不要提及情感 一、你对公司的了解多少 准备要点&#xff1a; 在面试前&#xff0c;对公司进行充分…

VLMs之Agent之CogAgent:《CogAgent: A Visual Language Model for GUI Agents》翻译与解读

VLMs之Agent之CogAgent&#xff1a;《CogAgent: A Visual Language Model for GUI Agents》翻译与解读 导读&#xff1a;这篇论文介绍了CogAgent&#xff0c;一个专注于图形用户界面 (GUI) 理解和导航的视觉语言模型 (VLM)。这篇论文提出了一种新的视觉语言模型 CogAgent&#…

linux audio(1)-pulseaudio模块数据流

本文主要讨论pulseaudio模块的数据流。这里的模块(module)主要限制在sink和source这两种类型。其他类型的数据流后续有空 再撰文讨论。 pulseaudio的模块一般会启动一路线程进行数据的搬运和处理。 下面的是module-null-source模块的数据搬运线程启动代码。 进入thread_func…