SpringBoot集成阿里沙箱支付
pom依赖项
xml<!--沙箱支付完整依赖,解决链接中subject存在中文导致乱码的问题--> <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.35.150.ALL</version> </dependency> <!--ConfigurationProperties注解依赖,解决yaml或者properties配置文件注入对象时提示配置,这是官方的配置依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
SpringBoot集成jasypt对yml敏感的数据进行加密
pom依赖
xml<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.4</version> </dependency>添加yml配置信息
yamljasypt: encryptor: # 加密的秘钥 password: qnmdmyy # 加密算法 algorithm: PBEWithMD5AndDES或者PBEWITHHMACSHA512ANDAES_256 iv-generator-classname: org.jasypt.iv.RandomIvGenerator property: # 算法识别的前后缀,默认ENC(),包含在前后缀的加密信息,会使用指定算法解密 prefix: Enc( suffix: )jasypt3.0后,默认支持的算法为 PBEWITHHMACSHA512ANDAES_256 ,该算法需要JDK1.9 以上支持或者添加JCE(Java Cryptography Extension 无限强度权限策略文件)支持。
两种密码加密的方式还是有区别的,如果追求高安全性和性能,推荐使用PBEWITHHMACSHA512ANDAES_256

如何添加JCE?

获取JCE文件
JDK6密码扩展无限制权限策略文件 http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html JDK7密码扩展无限制权限策略文件 https://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html JDK8密码扩展无限制权限策略文件 https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
生成加密数据工具类
java// PBEWithMD5AndDES算法所需要的工具类 public class JasyptUtils { public static void main(String[] args) { String info = encrypt("root"); System.out.println(info); } /** * 加密 * * @param plaintext 明文 * @return */ public static String encrypt(String plaintext) { StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); EnvironmentStringPBEConfig config = new EnvironmentStringPBEConfig(); // 指定算法 config.setAlgorithm("PBEWithMD5AndDES"); // 指定秘钥,和yml配置文件中保持一致 config.setPassword("qnmdmyy"); encryptor.setConfig(config); // 生成加密数据 return encryptor.encrypt(plaintext); } /** * 解密 * * @param data 加密后数据 * @return */ public static String decrypt(String data) { StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); EnvironmentStringPBEConfig config = new EnvironmentStringPBEConfig(); config.setAlgorithm("PBEWithMD5AndDES"); config.setPassword("qnmdmyy"); encryptor.setConfig(config); // 解密数据 return encryptor.decrypt(data); } } // PBEWITHHMACSHA512ANDAES_256算法所需工具类 public class JasyptUtil { public static void main(String[] args) { } /** * 加密 * * @param plaintext 明文 * @return */ public static String encrypt(String plaintext) { // 1. 创建加解密工具实例 PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); // 2. 加解密配置 SimpleStringPBEConfig config = new SimpleStringPBEConfig(); config.setPassword("qnmdmyy"); config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256"); // 为减少配置文件的书写,以下都是 Jasyp 3.x 版本,配置文件默认配置 config.setKeyObtentionIterations( "1000"); config.setPoolSize("1"); config.setProviderName("SunJCE"); config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator"); config.setStringOutputType("base64"); encryptor.setConfig(config); // 3. 加密 return encryptor.encrypt(plaintext); } /** * 解密 * * @param data 加密后数据 * @return */ public static String decrypt(String data) { // 1. 创建加解密工具实例 PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); // 2. 加解密配置 SimpleStringPBEConfig config = new SimpleStringPBEConfig(); config.setPassword("qnmdmyy"); config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256"); // 为减少配置文件的书写,以下都是 Jasyp 3.x 版本,配置文件默认配置 config.setKeyObtentionIterations( "1000"); config.setPoolSize("1"); config.setProviderName("SunJCE"); config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator"); config.setStringOutputType("base64"); encryptor.setConfig(config); // 3. 加密 return encryptor.decrypt(data); } }执行加密算法并在yml中替换敏感信息


对加密秘钥进行处理: 加密秘钥在yml文件中明文存储是不安全的
在idea中测试时,可以进行如下配置

打包部署可以使用以下两个指令
#方式1: java -jar xxx.jar -Djasypt.encryptor.password=qnmdmyy #方式2: java -jar xxx.jar --jasypt.encryptor.password=qnmdmyy既然使用指令配置秘钥,那么在docker构建项目镜像的时候就需要对以下原命令进行相应的修改


加密后的敏感数据模糊查询
业务场景举例: 订单号在后台生成以后通过非对称加密存储在数据库中,前台通过输入部分字符对订单号进行模糊查询
实现流程
首先后台生成一个长度为Len的订单号,本次示例Len为5
将生成的订单号通过公钥进行加密存储到订单表
步骤2的同时,将生成的订单号原文通过分片对称加密存储到订单号映射表

表结构(简化)
sqlCREATE TABLE `t_order` ( `id` varchar(36) NOT NULL, `order_no` varchar(2000) NOT NULL, `subject_name` varchar(500) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 CREATE TABLE `t_orderno_match` ( `id` varchar(36) NOT NULL, `encrypt_piece_content` varchar(2000) NOT NULL, `encrypt_piece_length` int(10) DEFAULT NULL, `order_id` varchar(36) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4功能实现代码
javaimport org.apache.tomcat.util.codec.binary.Base64; import javax.crypto.Cipher; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; /** * Description: 非对称加解密工具类 * Author: ZhangHL * Version: 1.0 * Create Date Time: 2023/6/20 15:18. */ public class RsaUtil { private static String publicKey = "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1KTu4JJ70M5ZLuD1V8m2Lrq2L//FoY4TKOAlUC4kh1Nu+4J+CpUeIyKgpqGZnPIhyzCT64NXP/08wDTCUqO/SkJEmTSkLSw3ZsEY/KQ5OuasW+HQTuv8TggLmjqP1KCqLsvyfEwuyUP+P1r5dHjqa13zqMadgd5V9X8uJA3Eezcy4xJqjWkCQWKmYS5GEiY9uDLCcoB3LMG3SpO2a41K38HXkyrXAxy2+JEjbFVdACrIlykHy1navdeWjzB1bD/efaa/o6LO66Oaxg96acyhY4lyzrY6XyY2QKxC9JC5q05dqUPYDMR6tpdSLoIgJb5Wq/pZKEyAWefsLbHcxtuNhsXpl4c7gqcxWj7hhrWmqYY2V16Cc6M02BdKgBVhV8R0RGDUgrd9JsiAhdQraD5H1+FfcQZ6QpM+VQ5ESZWKnAnH2sCQEjBUj65D1GM/bD619N5FeOfjbPXSjfCDj7jzu8a/i0ovntumgZP2Cuv6r8Hy+U9R1LFt/fl+BNWBo1OxSXFYr0CSFMPBF80BeWBdi3VluJtTlW0khPVyxeJq1HgfHGXkvVKwx3nz4uhMIM1XIKFAqdpBoG8JghVnhP4oxnmM0ca2QY8f0kRYW67BNMpwLZWRHRgQHrj6ATw1Pd9jAsXdiQgQhkxTdhVnjuwRrHnRRSDVOzeLULvON1aFHr8CAwEAAQ=="; private static String privateKey = "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"; /*** * description: 公钥加密 * @author: ZhangHL * @param text * @return: */ public static String encryptByPublicKey(String text) throws Exception{ X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKey)); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2); Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] result = cipher.doFinal(text.getBytes()); return Base64.encodeBase64String(result); } /*** * description: 私钥解密 * @author: ZhangHL * @param text * @return: */ public static String decryptByPrivateKey(String text) throws Exception { PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5); Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] result = cipher.doFinal(Base64.decodeBase64(text)); return new String(result); } }javaimport javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.Key; import java.util.Base64; /** * Description: 对称加密工具类-javax.crypto实现方式 * Author: ZhangHL * Version: 1.0 * Create Date Time: 2023/10/10 11:08. */ public class SymmetryUtil { private static final String ALGORITHM = "AES"; private static final String KEY = "0123456789abcdef"; // 密钥 16 字节长 public static String encrypt(String plaintext) throws Exception { Cipher cipher = Cipher.getInstance(ALGORITHM); Key key = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] cipherBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(cipherBytes); } public static String decrypt(String encryptedString) throws Exception { Cipher cipher = Cipher.getInstance(ALGORITHM); Key key = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, key); byte[] cipherBytes = Base64.getDecoder().decode(encryptedString); byte[] plaintextBytes = cipher.doFinal(cipherBytes); return new String(plaintextBytes, StandardCharsets.UTF_8); } }java/** * Description: 加密映射表公共属性 * Author: ZhangHL * Version: 1.0 * Create Date Time: 2024/1/6 18:49. */ public class BaseEncryptMatch { private String id; private String encryptPieceContent; private Integer encryptPieceLength; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getEncryptPieceContent() { return encryptPieceContent; } public void setEncryptPieceContent(String encryptPieceContent) { this.encryptPieceContent = encryptPieceContent; } public Integer getEncryptPieceLength() { return encryptPieceLength; } public void setEncryptPieceLength(Integer encryptPieceLength) { this.encryptPieceLength = encryptPieceLength; } public BaseEncryptMatch(String id, String encryptPieceContent, Integer encryptPieceLength) { this.id = id; this.encryptPieceContent = encryptPieceContent; this.encryptPieceLength = encryptPieceLength; } @Override public String toString() { return "BaseEncryptMatch{" + "id='" + id + '\'' + ", encryptPieceContent='" + encryptPieceContent + '\'' + ", encryptPieceLength=" + encryptPieceLength + '}'; } } /** * Description: 订单号加密映射实体类 * Author: ZhangHL * Version: 1.0 * Create Date Time: 2024/1/6 19:11. */ public class OrderEncryptMatch extends BaseEncryptMatch{ private String orderId; public String getOrderId() { return orderId; } public void setOrderId(String orderId) { this.orderId = orderId; } // 注意!!!!!一定要有和BaseEncryptMatch相同参数的构造方法 public OrderEncryptMatch(String id, String encryptPieceContent, Integer encryptPieceLength) { super(id, encryptPieceContent, encryptPieceLength); } @Override public String toString() { return "OrderEncryptMatch{" + "orderId='" + orderId + '\'' + "} " + super.toString(); } }javaimport com.zhl.common.entity.BaseEncryptMatch; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.List; import java.util.UUID; /** * Description: 加密相关工具类 * Author: ZhangHL * Version: 1.0 * Create Date Time: 2024/1/6 17:28. */ public class EncryptUtil { /*** * description: 分片加密 * 因为不止订单号会加密存储,所以在对敏感内容进行加密的时候,使用泛型保证了广泛性, * 任何加密内容映射表属性除了自身业务属性以外,都必须含有BaseEncryptMatch的三个属性 * @author: ZhangHL * @param rawText * @return: */ public static <T extends BaseEncryptMatch> List<T> pieceEncrypt(String rawText, Class<T> classType) throws Exception { List<T> resultList = new ArrayList<>(); for (int i = 1; i <= rawText.length(); i++) { for (int j = 0; j < (rawText.length() - i + 1); j++) { Constructor<T> constructor = classType.getConstructor(String.class, String.class, Integer.class); T t = constructor.newInstance( UUID.randomUUID().toString(), SymmetryUtil.encrypt(rawText.substring(j, i + j)), i ); resultList.add(t); } } return resultList; } }java// 订单逻辑待补充java// 订单查询逻辑待补充