数字档案馆加密存档不安全?实战透明加密解决方案

一、核心方案设计:AES-256-GCM + MyBatis拦截

数字档案馆系统若仅依赖简单的Base64编码或AES-ECB模式,存在极大的安全隐患。ECB模式无法隐藏明文模式,相同的明文会产生相同的密文,极易被统计分析攻击。本文将采用AES-256-GCM算法,该算法提供认证加密功能,不仅能保证机密性,还能保证数据完整性,防止密文被篡改。

为了实现“零代码侵入”的落地效果,我们将利用MyBatis的TypeHandler机制。在数据写入数据库前自动加密,读取后自动解密,业务层无需修改任何逻辑,直接操作明文字段即可。

二、项目环境与Maven依赖配置

本方案基于Spring Boot 2.x/3.x环境,无需引入额外的重型加密库,直接使用JDK自带的JCE(Java Cryptography Extension)。请确保你的JDK版本为1.8及以上。在pom.xml中添加以下必要依赖:

```xml org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.2 com.baomidou mybatis-plus-boot-starter 3.5.2 org.projectlombok lombok 1.18.24 provided ```

三、生成高强度的加密密钥

AES-256要求密钥长度必须为32字节(256位)。绝对禁止在代码中硬编码密钥字符串。我们需要生成一个随机的32字节密钥,并将其配置在application.yml或环境变量中。运行以下Java代码生成密钥:

```java import javax.crypto.KeyGenerator; import java.security.NoSuchAlgorithmException; import java.util.Base64; public class SecretKeyGenerator { public static void main(String[] args) throws NoSuchAlgorithmException { // 初始化 KeyGenerator,指定使用 AES 算法,密钥长度 256 KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); keyGenerator.init(256); byte[] keyBytes = keyGenerator.generateKey().getEncoded(); // 将二进制密钥转换为 Base64 字符串,方便配置 String base64Key = Base64.getEncoder().encodeToString(keyBytes); System.out.println("请将此密钥配置到 application.yml 的 encrypt.secret-key 字段中:"); System.out.println(base64Key); } } ```

运行上述代码,复制输出的Base64字符串。例如:Xm8s9N...(此处省略)...=

四、编写AES-GCM加解密工具类

创建一个工具类AesGcmUtil.java。GCM模式需要一个IV(初始化向量),IV不需要保密,但每次加密必须唯一。为了方便数据库存储,我们将IV和密文拼接在一起存储(格式:IV + Ciphertext)。

```java import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.ByteBuffer; import java.security.SecureRandom; import java.util.Base64; public class AesGcmUtil { private static final String ALGORITHM = "AES"; private static final String TRANSFORMATION = "AES/GCM/NoPadding"; private static final int GCM_TAG_LENGTH = 128; // 必须是 128, 120, 112, 104, 96, 64, 或 32 private static final int GCM_IV_LENGTH = 12; // 推荐使用 12 字节的 IV / 加密方法 @param plaintext 明文 @param base64Key Base64编码的密钥 @return Base64编码的密文(包含IV) / public static String encrypt(String plaintext, String base64Key) throws Exception { if (plaintext == null || plaintext.isEmpty()) { return plaintext; } // 1. 恢复密钥 byte[] keyBytes = Base64.getDecoder().decode(base64Key); SecretKeySpecKeySpec = new SecretKeySpec(keyBytes, ALGORITHM); // 2. 生成随机 IV byte[] iv = new byte[GCM_IV_LENGTH]; SecureRandom secureRandom = new SecureRandom(); secureRandom.nextBytes(iv); // 3. 初始化 Cipher Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.ENCRYPT_MODE, keySpec, new GCMParameterSpec(GCM_TAG_LENGTH, iv)); // 4. 加密 byte[] ciphertext = cipher.doFinal(plaintext.getBytes()); // 5. 组合 IV 和 密文 以便存储 ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + ciphertext.length); byteBuffer.put(iv); byteBuffer.put(ciphertext); // 6. 返回 Base64 编码 return Base64.getEncoder().encodeToString(byteBuffer.array()); } / 解密方法 @param encryptedText Base64编码的密文(包含IV) @param base64Key Base64编码的密钥 @return 明文 / public static String decrypt(String encryptedText, String base64Key) throws Exception { if (encryptedText == null || encryptedText.isEmpty()) { return encryptedText; } // 1. 恢复密钥 byte[] keyBytes = Base64.getDecoder().decode(base64Key); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, ALGORITHM); // 2. 解码 Base64 密文 byte[] decodedMessage = Base64.getDecoder().decode(encryptedText); // 3. 分离 IV 和 密文 ByteBuffer byteBuffer = ByteBuffer.wrap(decodedMessage); byte[] iv = new byte[GCM_IV_LENGTH]; byteBuffer.get(iv); byte[] ciphertext = new byte[byteBuffer.remaining()]; byteBuffer.get(ciphertext); // 4. 初始化 Cipher Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.DECRYPT_MODE, keySpec, new GCMParameterSpec(GCM_TAG_LENGTH, iv)); // 5. 解密 byte[] plaintext = cipher.doFinal(ciphertext); return new String(plaintext); } } ```

五、实现MyBatis透明加密TypeHandler

数字档案馆加密存档不安全?实战透明加密解决方案

这是实现“零门槛”落地的核心。我们需要自定义一个TypeHandler,让MyBatis在将Java String存入数据库时调用encrypt,从数据库取出时调用decrypt

创建类CryptoTypeHandler.java

```java import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedTypes; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; // 指定该 Handler 处理 String 类型的字段 @Component @MappedTypes(String.class) public class CryptoTypeHandler extends BaseTypeHandler { // 从配置文件中读取密钥,使用 @Value 注入 // 注意:如果在非 Spring 环境下使用 MyBatis,需通过其他方式获取配置 private static String secretKey; @Value("${encrypt.secret-key}") public void setSecretKey(String key) { CryptoTypeHandler.secretKey = key; } @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { try { // 存入数据库时:加密 String encrypted = AesGcmUtil.encrypt(parameter, secretKey); ps.setString(i, encrypted); } catch (Exception e) { throw new SQLException("Failed to encrypt parameter: " + parameter, e); } } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { String value = rs.getString(columnName); return decryptValue(value); } @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String value = rs.getString(columnIndex); return decryptValue(value); } @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String value = cs.getString(columnIndex); return decryptValue(value); } private String decryptValue(String value) { if (value == null || value.isEmpty()) { return value; } try { // 从数据库读取时:解密 return AesGcmUtil.decrypt(value, secretKey); } catch (Exception e) { // 防止解密失败导致系统崩溃,可根据需求记录日志或抛出异常 // 此处简单返回原值或抛出 RuntimeException throw new RuntimeException("Failed to decrypt value from DB", e); } } } ```

六、配置文件与实体类映射

application.yml中配置刚才生成的密钥:

```yaml encrypt: 替换为第三步生成的真实密钥 secret-key: "Xm8s9N/yourGeneratedBase64KeyHere..." ```

接下来,在需要加密的实体类字段上,指定使用我们刚刚创建的CryptoTypeHandler。假设我们有一个ArchiveFile实体,其中content字段(档案正文)需要加密存储:

```java import com.baomidou.mybatisplus.annotation.TableName; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.TypeReference; import lombok.Data; @Data @TableName("t_archive_file") public class ArchiveFile { private Long id; private String fileName; // 核心配置:typeHandler 指定全限定类名 // jdbcType = VARCHAR 对应数据库中的字段类型 // 只要查询这个字段,MyBatis 就会自动调用 CryptoTypeHandler 进行解密 @org.apache.ibatis.annotations.JdbcType(JdbcType.VARCHAR) @org.apache.ibatis.annotations.TypeHandler(CryptoTypeHandler.class) private String content; private String creator; } ```

对应的数据库表结构 SQL(MySQL示例):

```sql CREATE TABLE t_archive_file ( id BIGINT PRIMARY KEY AUTO_INCREMENT, file_name VARCHAR(255) NOT NULL, content TEXT, -- 加密后的密文也是字符串,存储在 TEXT 或 VARCHAR 中 creator VARCHAR(100) ); ```

七、验证与测试

编写一个单元测试或Controller接口来验证效果。注意观察数据库中实际存储的内容。

```java import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class ArchiveServiceTest { @Autowired private ArchiveMapper archiveMapper; // 假设你有一个基础的 Mapper @Test public void testEncryption() { ArchiveFile file = new ArchiveFile(); file.setFileName("绝密档案001.txt"); file.setCreator("admin"); // 业务层操作:直接赋值,不需要手动加密 String originalContent = "这是机密档案内容:身份证号123456789"; file.setContent(originalContent); // 插入数据库 archiveMapper.insert(file); System.out.println("插入成功,ID: " + file.getId()); } @Test public void testDecryption() { // 查询数据库 ArchiveFile file = archiveMapper.selectById(1L); // 输出结果 System.out.println("文件名: " + file.getFileName()); // 这里输出的应该是解密后的明文 System.out.println("档案内容: " + file.getContent()); // 断言验证 assert file.getContent().contains("身份证号123456789"); } } ```

验证步骤:

  1. 执行testEncryption 程序运行成功后,直接连接数据库,查看t_archive_file表的content字段。你应该看到一串乱码般的Base64字符串(例如:8fJk...==),绝不是明文。
  2. 执行testDecryption 观察控制台输出。程序打印出的file.getContent()应该完整还原为“这是机密档案内容:身份证号123456789”,证明解密逻辑自动生效。
  3. 篡改测试: 手动修改数据库中content的一个字符,再次运行查询。程序应抛出解密异常(因为GCM校验Tag失败),证明了数据的防篡改能力。
AI咨询
热线电话

028-85154420

15388110056

全国售前咨询电话

扫码咨询
安答联动微信公众号二维码

微信扫码关注安答联动

申请试用
热线电话
申请试用

安答联动档案管理系统