← Back to Blog

积分商城用户数据加密改造实战技术博客

  • 实现策略: 使用 AES 加密算法,将敏感字段加密后存储,同时保留原始数据处理方式以实现同时兼容。
  • 解决方案: 通过 MyBatis TypeHandler 解耦存取过程,自动将某些指定字段进行加密和解密操作,无需修改上应应用逻辑。
  • 分步执行:
    • 分析需加密字段
    • 编写AES加密/解密公共类
    • 实现MyBatis TypeHandler
    • 数据移植工具脚本,实现旧数据加密

3. 具体技术进程

3.1 分析字段

根据数据存储结构,确定哪些字段需要加密:

  • 用户姓名
  • 电话号码
  • 邮箱
  • 身份证号

3.2 AES加密工具

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AesUtil {
    private static final String ALGORITHM = "AES";
    private static final String KEY = "1234567890abcdef"; // 16位密钥

    public static String encrypt(String value) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        byte[] encrypted = cipher.doFinal(value.getBytes());
        return Base64.getEncoder().encodeToString(encrypted);
    }

    public static String decrypt(String encrypted) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        byte[] decoded = Base64.getDecoder().decode(encrypted);
        byte[] original = cipher.doFinal(decoded);
        return new String(original);
    }
}

3.3 实现MyBatis TypeHandler

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class EncryptTypeHandler extends BaseTypeHandler<String> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        try {
            ps.setString(i, AesUtil.encrypt(parameter));
        } catch (Exception e) {
            throw new SQLException("Encryption failed", e);
        }
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        try {
            return AesUtil.decrypt(rs.getString(columnName));
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        try {
            return AesUtil.decrypt(rs.getString(columnIndex));
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        try {
            return AesUtil.decrypt(cs.getString(columnIndex));
        } catch (Exception e) {
            return null;
        }
    }
}

配合 MyBatis Mapper 使用:

<result property="phone" column="phone" typeHandler="xxx.EncryptTypeHandler" />

3.4 数据移植脚本

写一个移植脚本,将旧数据转为加密形式。

基本步骤:

  • 找出所有旧数据
  • 调用AesUtil进行加密
  • 更新回原数据表

示例:

List<User> users = userDao.findAll();
for (User user : users) {
    String encryptedPhone = AesUtil.encrypt(user.getPhone());
    userDao.updatePhone(user.getId(), encryptedPhone);
}

4. 实践结果

完成数据加密改造后,系统正常运行,上线用户体验无明显变化,同时提升了数据安全性,成功满足后续安全管控需求。


整体过程中,最关键的是定制好规范,遵循加密协议,确保各组件互相兼容。实际工程中,还可考虑增加片段加密(如通过指定段名来分类加密),更精精刻控制实施效果。