当前位置: 首页 > news >正文

Spring Boot + AOP + Jasypt,3 步实现敏感数据脱敏

在企业级应用开发中,用户敏感信息(如手机号、邮箱、密码等)的保护至关重要。Spring Boot生态提供了Jasypt加密框架与AOP编程模型的组合方案,可在不侵入业务逻辑的前提下实现数据全生命周期的脱敏管理。

加密工具类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;import java.util.regex.Matcher;
import java.util.regex.Pattern;public class Sm4Utils {private static Pattern p = Pattern.compile("\\s*|\t|\r|\n");private static final Logger logger = LoggerFactory.getLogger(Sm4Utils.class);private Sm4Utils() {}public static Sm4Utils getInstance() {return new Sm4Utils();}public static String encryptSub(String plaintext, String seed, String offset, String sm2Type) {logger.info("SM4加密开始...");if ("CBC".equals(sm2Type)) {return encryptDataCbc(plaintext, seed, offset);} elseif ("ECB".equals(sm2Type)) {return encryptDataEcb(plaintext, seed);} else {logger.error("不存在此加密方式");return"";}}public static String decryptSub(String ciphertext, String seed, String offset, String sm4Type) {logger.info("SM4解密开始...");if ("CBC".equals(sm4Type)) {return decryptDataCbc(ciphertext, seed, offset);} elseif ("ECB".equals(sm4Type)) {return decryptDataEcb(ciphertext, seed);} else {logger.error("不存在此解密方式");return"";}}public static String encryptDataEcb(String plainText, String key) {try {Sm4Context ctx = new Sm4Context();ctx.isPadding = true;ctx.mode = 1;byte[] keyBytes = key.getBytes();Sm4 sm4 = new Sm4();sm4.sm4_setkey_enc(ctx, keyBytes);byte[] encrypted = sm4.sm4_crypt_ecb(ctx, plainText.getBytes("UTF-8"));String cipherText = (new BASE64Encoder()).encode(encrypted);if (cipherText != null && cipherText.trim().length() > 0) {Matcher m = p.matcher(cipherText);cipherText = m.replaceAll("");}return cipherText;} catch (Exception var9) {logger.error("ECB加密失败", var9);return"";}}public static String decryptDataEcb(String cipherText, String key) {try {Sm4Context ctx = new Sm4Context();ctx.isPadding = true;ctx.mode = 0;byte[] keyBytes = key.getBytes();Sm4 sm4 = new Sm4();sm4.sm4_setkey_dec(ctx, keyBytes);byte[] decrypted = sm4.sm4_crypt_ecb(ctx, (new BASE64Decoder()).decodeBuffer(cipherText));return new String(decrypted, "UTF-8");} catch (Exception var7) {logger.error("ECB解密失败", var7);return"";}}public static String encryptDataCbc(String plainText, String key, String offset) {try {Sm4Context ctx = new Sm4Context();ctx.isPadding = true;ctx.mode = 1;byte[] keyBytes = key.getBytes();byte[] ivBytes = offset.getBytes();Sm4 sm4 = new Sm4();sm4.sm4_setkey_enc(ctx, keyBytes);byte[] encrypted = sm4.sm4_crypt_cbc(ctx, ivBytes, plainText.getBytes("UTF-8"));String cipherText = (new BASE64Encoder()).encode(encrypted);if (cipherText != null && cipherText.trim().length() > 0) {Matcher m = p.matcher(cipherText);cipherText = m.replaceAll("");}return cipherText;} catch (Exception var11) {logger.error("CBC加密失败", var11);return"";}}public static String decryptDataCbc(String cipherText, String key, String offset) {try {Sm4Context ctx = new Sm4Context();ctx.isPadding = true;ctx.mode = 0;byte[] keyBytes = key.getBytes();byte[] ivBytes = offset.getBytes();Sm4 sm4 = new Sm4();sm4.sm4_setkey_dec(ctx, keyBytes);byte[] decrypted = sm4.sm4_crypt_cbc(ctx, ivBytes, (new BASE64Decoder()).decodeBuffer(cipherText));return new String(decrypted, "UTF-8");} catch (Exception var9) {logger.error("CBC解密失败", var9);return"";}}
}

自定义算法

public class Sm4Context {public int mode = 1;public long[] sk = new long[32];public boolean isPadding = true;public Sm4Context() {}
}
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;public class Sm4 {public static final byte[] SboxTable = new byte[]{-42, -112, -23, -2, -52, -31, 61, -73, 22, -74, 20, -62, 40, -5, 44, 5, 43, 103, -102, 118, 42, -66, 4, -61, -86, 68, 19, 38, 73, -122, 6, -103, -100, 66, 80, -12, -111, -17, -104, 122, 51, 84, 11, 67, -19, -49, -84, 98, -28, -77, 28, -87, -55, 8, -24, -107, -128, -33, -108, -6, 117, -113, 63, -90, 71, 7, -89, -4, -13, 115, 23, -70, -125, 89, 60, 25, -26, -123, 79, -88, 104, 107, -127, -78, 113, 100, -38, -117, -8, -21, 15, 75, 112, 86, -99, 53, 30, 36, 14, 94, 99, 88, -47, -94, 37, 34, 124, 59, 1, 33, 120, -121, -44, 0, 70, 87, -97, -45, 39, 82, 76, 54, 2, -25, -96, -60, -56, -98, -22, -65, -118, -46, 64, -57, 56, -75, -93, -9, -14, -50, -7, 97, 21, -95, -32, -82, 93, -92, -101, 52, 26, 85, -83, -109, 50, 48, -11, -116, -79, -29, 29, -10, -30, 46, -126, 102, -54, 96, -64, 41, 35, -85, 13, 83, 78, 111, -43, -37, 55, 69, -34, -3, -114, 47, 3, -1, 106, 114, 109, 108, 91, 81, -115, 27, -81, -110, -69, -35, -68, 127, 17, -39, 92, 65, 31, 16, 90, -40, 10, -63, 49, -120, -91, -51, 123, -67, 45, 116, -48, 18, -72, -27, -76, -80, -119, 105, -105, 74, 12, -106, 119, 126, 101, -71, -15, 9, -59, 110, -58, -124, 24, -16, 125, -20, 58, -36, 77, 32, 121, -18, 95, 62, -41, -53, 57, 72};public static final int[] FK = new int[]{-1548633402, 1453994832, 1736282519, -1301273892};public static final int[] CK = new int[]{462357, 472066609, 943670861, 1415275113, 1886879365, -1936483679, -1464879427, -993275175, -521670923, -66909679, 404694573, 876298825, 1347903077, 1819507329, -2003855715, -1532251463, -1060647211, -589042959, -117504499, 337322537, 808926789, 1280531041, 1752135293, -2071227751, -1599623499, -1128019247, -656414995, -184876535, 269950501, 741554753, 1213159005, 1684763257};public Sm4() {}private long GET_ULONG_BE(byte[] b, int i) {long n = (long)(b[i] & 255) << 24 | (long)((b[i + 1] & 255) << 16) | (long)((b[i + 2] & 255) << 8) | (long)(b[i + 3] & 255) & 4294967295L;return n;}private void PUT_ULONG_BE(long n, byte[] b, int i) {b[i] = (byte)((int)(255L & n >> 24));b[i + 1] = (byte)((int)(255L & n >> 16));b[i + 2] = (byte)((int)(255L & n >> 8));b[i + 3] = (byte)((int)(255L & n));}private long SHL(long x, int n) {return (x & -1L) << n;}private long ROTL(long x, int n) {return this.SHL(x, n) | x >> 32 - n;}private void SWAP(long[] sk, int i) {long t = sk[i];sk[i] = sk[31 - i];sk[31 - i] = t;}private byte sm4Sbox(byte inch) {int i = inch & 255;byte retVal = SboxTable[i];return retVal;}private long sm4Lt(long ka) {long bb = 0L;long c = 0L;byte[] a = new byte[4];byte[] b = new byte[4];this.PUT_ULONG_BE(ka, a, 0);b[0] = this.sm4Sbox(a[0]);b[1] = this.sm4Sbox(a[1]);b[2] = this.sm4Sbox(a[2]);b[3] = this.sm4Sbox(a[3]);bb = this.GET_ULONG_BE(b, 0);c = bb ^ this.ROTL(bb, 2) ^ this.ROTL(bb, 10) ^ this.ROTL(bb, 18) ^ this.ROTL(bb, 24);return c;}private long sm4F(long x0, long x1, long x2, long x3, long rk) {return x0 ^ this.sm4Lt(x1 ^ x2 ^ x3 ^ rk);}private long sm4CalciRK(long ka) {long bb = 0L;long rk = 0L;byte[] a = new byte[4];byte[] b = new byte[4];this.PUT_ULONG_BE(ka, a, 0);b[0] = this.sm4Sbox(a[0]);b[1] = this.sm4Sbox(a[1]);b[2] = this.sm4Sbox(a[2]);b[3] = this.sm4Sbox(a[3]);bb = this.GET_ULONG_BE(b, 0);rk = bb ^ this.ROTL(bb, 13) ^ this.ROTL(bb, 23);return rk;}private void sm4_setkey(long[] SK, byte[] key) {long[] MK = new long[4];long[] k = new long[36];int i = 0;MK[0] = this.GET_ULONG_BE(key, 0);MK[1] = this.GET_ULONG_BE(key, 4);MK[2] = this.GET_ULONG_BE(key, 8);MK[3] = this.GET_ULONG_BE(key, 12);k[0] = MK[0] ^ (long)FK[0];k[1] = MK[1] ^ (long)FK[1];k[2] = MK[2] ^ (long)FK[2];for(k[3] = MK[3] ^ (long)FK[3]; i < 32; ++i) {k[i + 4] = k[i] ^ this.sm4CalciRK(k[i + 1] ^ k[i + 2] ^ k[i + 3] ^ (long)CK[i]);SK[i] = k[i + 4];}}private void sm4_one_round(long[] sk, byte[] input, byte[] output) {int i = 0;long[] ulbuf = new long[36];ulbuf[0] = this.GET_ULONG_BE(input, 0);ulbuf[1] = this.GET_ULONG_BE(input, 4);ulbuf[2] = this.GET_ULONG_BE(input, 8);for(ulbuf[3] = this.GET_ULONG_BE(input, 12); i < 32; ++i) {ulbuf[i + 4] = this.sm4F(ulbuf[i], ulbuf[i + 1], ulbuf[i + 2], ulbuf[i + 3], sk[i]);}this.PUT_ULONG_BE(ulbuf[35], output, 0);this.PUT_ULONG_BE(ulbuf[34], output, 4);this.PUT_ULONG_BE(ulbuf[33], output, 8);this.PUT_ULONG_BE(ulbuf[32], output, 12);}private byte[] padding(byte[] input, int mode) {if (input == null) {return null;} else {byte[] ret = (byte[])null;if (mode == 1) {int p = 16 - input.length % 16;ret = new byte[input.length + p];System.arraycopy(input, 0, ret, 0, input.length);for(int i = 0; i < p; ++i) {ret[input.length + i] = (byte)p;}} else {int p = input[input.length - 1];ret = new byte[input.length - p];System.arraycopy(input, 0, ret, 0, input.length - p);}return ret;}}public void sm4_setkey_enc(Sm4Context ctx, byte[] key) throws Exception {if (ctx == null) {throw new Exception("ctx is null!");} elseif (key != null && key.length == 16) {ctx.mode = 1;this.sm4_setkey(ctx.sk, key);} else {throw new Exception("key error!");}}public void sm4_setkey_dec(Sm4Context ctx, byte[] key) throws Exception {if (ctx == null) {throw new Exception("ctx is null!");} elseif (key != null && key.length == 16) {ctx.mode = 0;this.sm4_setkey(ctx.sk, key);for(int i = 0; i < 16; ++i) {this.SWAP(ctx.sk, i);}} else {throw new Exception("key error!");}}public byte[] sm4_crypt_ecb(Sm4Context ctx, byte[] input) throws Exception {if (input == null) {throw new Exception("input is null!");} else {if (ctx.isPadding && ctx.mode == 1) {input = this.padding(input, 1);}int length = input.length;ByteArrayInputStream bins = new ByteArrayInputStream(input);ByteArrayOutputStream bous;byte[] output;for(bous = new ByteArrayOutputStream(); length > 0; length -= 16) {output = new byte[16];byte[] out = new byte[16];bins.read(output);this.sm4_one_round(ctx.sk, output, out);bous.write(out);}output = bous.toByteArray();if (ctx.isPadding && ctx.mode == 0) {output = this.padding(output, 0);}bins.close();bous.close();return output;}}public byte[] sm4_crypt_cbc(Sm4Context ctx, byte[] iv, byte[] input) throws Exception {if (iv != null && iv.length == 16) {if (input == null) {throw new Exception("input is null!");} else {if (ctx.isPadding && ctx.mode == 1) {input = this.padding(input, 1);}int length = input.length;ByteArrayInputStream bins = new ByteArrayInputStream(input);ByteArrayOutputStream bous = new ByteArrayOutputStream();byte[] temp;byte[] out;int i;if (ctx.mode != 1) {for(temp = new byte[16]; length > 0; length -= 16) {out = new byte[16];out = new byte[16];byte[] out1 = new byte[16];bins.read(out);System.arraycopy(out, 0, temp, 0, 16);this.sm4_one_round(ctx.sk, out, out);for(i = 0; i < 16; ++i) {out1[i] = (byte)(out[i] ^ iv[i]);}System.arraycopy(temp, 0, iv, 0, 16);bous.write(out1);}} else {while(length > 0) {temp = new byte[16];out = new byte[16];out = new byte[16];bins.read(temp);for(i = 0; i < 16; ++i) {out[i] = (byte)(temp[i] ^ iv[i]);}this.sm4_one_round(ctx.sk, out, out);System.arraycopy(out, 0, iv, 0, 16);bous.write(out);length -= 16;}}temp = bous.toByteArray();if (ctx.isPadding && ctx.mode == 0) {temp = this.padding(temp, 0);}bins.close();bous.close();return temp;}} else {throw new Exception("iv error!");}}
}

测试

String s = Sm4Utils.encryptSub("一安未来", "@yianweilai#$%^&", "1234567890123456", "CBC");
System.out.println(s);
System.out.println(Sm4Utils.decryptSub(s, "@yianweilai#$%^&", "1234567890123456", "CBC"));cpudYch9orpFFc0uSxhg7g==
一安未来String s1 = Sm4Utils.encryptSub("一安未来", "@yianweilai#$%^&", "", "ECB");
System.out.println(s1);
System.out.println(Sm4Utils.decryptSub(s1, "@yianweilai#$%^&", "", "ECB"));RxV6VK9bNKEBqNFq0uVuqQ==
一安未来

自定义注解
字段加密注解(用于标记字段是否加密)


@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptFields {String[] value() default "";
}

方法加密注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedEncrypt {
}

方法解密注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedDecrypt {
}

实体类

@AllArgsConstructor
@Data
public class User {private Long id;private String username;private String password;@EncryptFieldsprivate String email;
}

AOP 切面实现
加密切面

@Aspect
@Component
public class EncryptAspect {@Autowiredprivate StringEncryptor encryptor;@Around("@annotation(NeedEncrypt)")public Object encryptData(ProceedingJoinPoint joinPoint) throws Throwable {processParameters(joinPoint.getArgs());return joinPoint.proceed();}private void processParameters(Object[] args) {Arrays.stream(args).filter(Objects::nonNull).forEach(this::encryptObject);}private void encryptObject(Object object) {ReflectionUtils.doWithFields(object.getClass(), field -> {if (field.isAnnotationPresent(EncryptFields.class)) {field.setAccessible(true);ReflectionUtils.setField(field, object, encryptor.encrypt(field.get(object).toString()));}});}
}

解密切面

@Aspect
@Component
public class DecryptAspect {@Around("@annotation(NeedDecrypt)")public Object decryptData(ProceedingJoinPoint joinPoint) throws Throwable {Object result = joinPoint.proceed();return processResult(result);}private Object processResult(Object result) {if (result instanceof Collection) {((Collection<?>) result).forEach(this::decryptObject);} elseif (result != null) {decryptObject(result);}return result;}private void decryptObject(Object object) {ReflectionUtils.doWithFields(object.getClass(), field -> {if (field.isAnnotationPresent(EncryptFields.class)) {field.setAccessible(true);ReflectionUtils.setField(field, object, Sm4Utils.decryptSub(field.get(object).toString(), "@yianweilai#$%^&", "", "ECB"));}});}
}

使用示例

@RestController
@RequestMapping("/api")
public class Sm4Controller {@NeedEncrypt@PostMapping("/user")public User saveUser(@RequestBody User user) {log.info("saveUser params:{}", user);// 业务处理逻辑return user;}@NeedDecrypt@GetMapping("/user")public User getUser() {// 从数据库获取加密数据User user = new User();user.setId(1L);user.setUsername("一安未来");user.setEmail(Sm4Utils.encryptSub("yianweilai@163.com", "@yianweilai#$%^&", "", "ECB"));log.info("getUser user:{}", user);return user;}
}

相关文章:

  • Java 实现后端调用 Chromium 浏览器无头模式截图的方案
  • Java web非Maven项目中引入EasyExcel踩坑记录
  • 批量创建tmux tmux批量
  • 深入解析 Java List 实现类的底层原理
  • 腾讯云TCCA认证考试报名 - TDSQL数据库交付运维工程师(PostgreSQL版)
  • 12.9 定时任务
  • SkyWalking 部署与应用(Windows)
  • 3DS 转换为 STP 全攻略:迪威模型网在线转换详解
  • OpenAI 如何在激烈的AI人才争夺战中抢占先机?
  • 视频或视频流和帧的关系?怎么理解?
  • MATLAB R2025a安装教程
  • 2025 MWC 上海盛大开幕,聚焦AI、5G-Advanced及开放API
  • Git工作流程及使用规范
  • GoByExample简单应用
  • Vue3 项目国际化实践
  • 标杆确立!永洪科技位于IDC报告Data Analytics领域象限排头位!
  • Oracle 查看所有表的字段名、数据类型及长度
  • Android软件适配遥控器需求-案例经验分享
  • opencv的setDefaultAllocator使用
  • MySQL 数据处理函数全面详解
  • 图书馆网站制作/seo原创工具
  • 卖网店哪个平台可靠/网站优化外包费用
  • 写一个像wordpress/免费seo工具汇总
  • 建设无障碍网站/外贸独立站推广
  • 开封市住房和城乡建设网站/360网站推广怎么做
  • 什么企业网站能自己做/无代码建站