正文
一、Jasypt介绍
Jasypt是Java加密工具包,能支持对密码的哈希加密,对文本和二进制数据的对称加解密,还能集成SpringBoot项目对配置文件中的密钥进行加密存储。
引入依赖如下:
<!-- https://mvnrepository.com/artifact/com.github.ulisesbocchio/jasypt-spring-boot-starter --> <dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.4</version> </dependency>
二、Jasypt手动使用
2.1 密码加密场景
用户注册账户的时候需要输入密码,我们将密码加密后保存到数据库中,保证用户的敏感数据的安全性。当用户再次登录的时候,我们需要将登录密码和注册时保存的密文密码进行比对,只有比对一致才能完成登录。
密码加密工具类主要有三个,它们都是实现了PasswordEncryptor接口,下面我们逐步来看。
@Slf4j @RestController public class SignController { private final BasicPasswordEncryptor basicPasswordEncryptor = new BasicPasswordEncryptor(); private String encryptedPassword = null; @GetMapping("/signup/{password}") public String signup(@PathVariable String password){ log.info("用户注册密码为:{}", password); encryptedPassword = basicPasswordEncryptor.encryptPassword(password); log.info("用户注册密码加密后为:{}", encryptedPassword); return encryptedPassword; } @GetMapping("/signin/{password}") public String signin(@PathVariable String password){ log.info("用户登录密码为:{}", password); if(basicPasswordEncryptor.checkPassword(password, encryptedPassword)){ log.info("用户登录成功!"); return "success"; } log.info("用户登录失败!"); return "fail"; } }
启动项目后,我们首先注册用户密码localhost:8080/signup/123456,就能得到密文5b32ygn5pbBvphjIKco6X8Z2VfWqwEUw,并将其保存到类变量中暂存,当我们再次登录localhost:8080/signin/123456,就能登录成功了。相反的,如果登录时密码随意输错,就会登录失败。
2022-10-11 15:41:57.038 INFO 26268 --- [nio-8080-exec-1] c.e.myapp.controller.SignController : 用户注册密码为:123456 2022-10-11 15:41:57.039 INFO 26268 --- [nio-8080-exec-1] c.e.myapp.controller.SignController : 用户注册密码加密后为:5b32ygn5pbBvphjIKco6X8Z2VfWqwEUw 2022-10-11 15:42:07.405 INFO 26268 --- [nio-8080-exec-3] c.e.myapp.controller.SignController : 用户登录密码为:123456 2022-10-11 15:42:07.406 INFO 26268 --- [nio-8080-exec-3] c.e.myapp.controller.SignController : 用户登录成功! 2022-10-11 15:42:12.767 INFO 26268 --- [nio-8080-exec-4] c.e.myapp.controller.SignController : 用户登录密码为:123457 2022-10-11 15:42:12.767 INFO 26268 --- [nio-8080-exec-4] c.e.myapp.controller.SignController : 用户登录失败!
那么这种加密方式是什么呢?我们可以打开BasicPasswordEncryptor的源码,看到类上面的注释:
- Algorithm: MD5.
- Salt size: 8 bytes.
- Iterations: 1000.
意思就是使用的MD5这种哈希算法,并且使用8字节(64位)的盐值,迭代计算1000次得到的密文。
除了使用如上的BasicPasswordEncryptor工具之外,还有StrongPasswordEncryptor工具类,它的加密登记更加的安全:
- Algorithm: SHA-256.
- Salt size: 16 bytes.
- Iterations: 100000.
如果这些加密算法都不能满足你的要求,就可以使用ConfigurablePasswordEncryptor来自定义加密工具类ConfigurablePasswordEncryptor,可以设置自己需要使用的算法。
总结:
接口类PasswordEncryptor主要有如下三个实现类:
- BasicPasswordEncryptor,使用MD5算法;
- StrongPasswordEncryptor,使用SHA-256算法;
- ConfigurablePasswordEncryptor,可自定义指定哈希算法;
哈希算法是不可逆的,因此只有加密encryptPassword和检查checkPassword两个方法。
2.2 文本加密场景
用户的手机号、身份证号等敏感信息在存储的时候需要进行加密,但是这些敏感数据在需要使用的时候是需要明文解密的,因此不适合使用2.1节的哈希算法,而是使用对称加密的形式。
文本加密工具类主要有三个,它们都是实现了TextEncryptor接口,下面我们逐步来看。
@Slf4j @RestController public class TextController { private static final BasicTextEncryptor basicTextEncryptor = new BasicTextEncryptor(); private static final String SECRET = "hello"; private String encryptedText = null; static { basicTextEncryptor.setPassword(SECRET); } @GetMapping("/encryptText/{plainText}") public String encryptText(@PathVariable String plainText){ log.info("用户输入明文:{}", plainText); encryptedText = basicTextEncryptor.encrypt(plainText); log.info("用户加密密文:{}", encryptedText); return encryptedText; } @GetMapping("/decryptText") public String decryptText(){ String plainText = basicTextEncryptor.decrypt(encryptedText); log.info("用户原始明文:{}", plainText); return plainText; } }
项目启动后,我们分别访问localhost:8080/encryptText/hello进行加密,访问localhost:8080/decryptText进行解密。
2022-10-11 15:52:36.949 INFO 21652 --- [nio-8080-exec-1] c.e.myapp.controller.TextController : 用户输入明文:hello 2022-10-11 15:52:36.950 INFO 21652 --- [nio-8080-exec-1] c.e.myapp.controller.TextController : 用户加密密文:u/qYluhyFpyOA6xMD3z3JA== 2022-10-11 15:52:46.345 INFO 21652 --- [nio-8080-exec-2] c.e.myapp.controller.TextController : 用户原始明文:hello
我们同样打开BasicTextEncryptor可以看到它的加密原理:
- Algorithm: PBEWithMD5AndDES.
- Key obtention iterations: 1000.
同样的,我们可以使用安全性更高的StrongTextEncryptor:
- Algorithm: PBEWithMD5AndTripleDES.
- Key obtention iterations: 1000.
还有安全性更高的AES256TextEncryptor:
- Algorithm: PBEWithHMACSHA512AndAES_256".
- Key obtention iterations: 1000.
2.3 数值加密场景
如果需要对整数或者小数进行加密,就可以分别使用IntegerNumberEncryptor接口和DecimalNumberEncryptor接口的实现类。同样的,这种场景的加密也都是对称加密,用法完全一样。
IntegerNumberEncryptor:主要用来对整数进行加解密。
- BasicIntegerNumberEncryptor
Algorithm: PBEWithMD5AndDES.
Key obtention iterations: 1000.
- StrongIntegerNumberEncryptor
Algorithm: PBEWithMD5AndTripleDES.
Key obtention iterations: 1000.
- AES256IntegerNumberEncryptor
Algorithm: PBEWithHMACSHA512AndAES_256.
Key obtention iterations: 1000.
DecimalNumberEncryptor:主要用来对小数进行加解密。
- BasicDecimalNumberEncryptor
Algorithm: PBEWithMD5AndDES.
Key obtention iterations: 1000
- StrongDecimalNumberEncryptor
Algorithm: PBEWithMD5AndTripleDES.
Key obtention iterations: 1000.
- AES256DecimalNumberEncryptor
Algorithm: PBEWithHMACSHA512AndAES_256.
Key obtention iterations: 1000.
2.4 二进制数据加密场景
暂未遇到需要加密二进制数据的业务场景,此处略过,使用方法可以参考官网。
三、Jasypt整合SpringBoot
SpringBoot应用中有很多密钥和密码都是存储在配置文件中的,我们需要将它们以密文的方式存储起来。
# 服务器配置 server: port: 8080 # Spring配置 spring: # 数据源配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&&serverTimezone=Asia/Shanghai&&useSSL=false username: root # 此处是密码的密文,要用ENC()进行包裹 password: ENC(KZeGx0ixuy4UrBp1HuhiDNnKB0cJr0cW) # mybatis配置 mybatis: mapper-locations: classpath:mapper/*Mapper.xml # 加密配置 jasypt: encryptor: # 指定加密密钥,生产环境请放到启动参数里面 password: your-secret # 指定解密算法,需要和加密时使用的算法一致 algorithm: PBEWithMD5AndDES # 指定initialization vector类型 iv-generator-classname: org.jasypt.iv.NoIvGenerator
如上是对数据库密码进行加密存储,密文是怎么的来的?可以写一个测试类,使用第二节介绍的内容自己手动加密。
@Slf4j public final class JasyptUtils { /** * 加密使用密钥 */ private static final String PRIVATE_KEY = "lybgeek"; private static BasicTextEncryptor basicTextEncryptor = new BasicTextEncryptor(); static { basicTextEncryptor.setPassword(PRIVATE_KEY); } /** * 私有构造方法,防止被意外实例化 */ private JasyptUtils() { } /** * 明文加密 * * @param plaintext 明文 * @return String */ public static String encrypt(String plaintext) { log.info("明文字符串为:{}", plaintext); // 使用的加密算法参考2.2节内容,也可以在源码的类注释中看到 String ciphertext = basicTextEncryptor.encrypt(plaintext); log.info("密文字符串为:{}", ciphertext); return ciphertext; } /** * 解密 * * @param ciphertext 密文 * @return String */ public static String decrypt(String ciphertext) { log.info("密文字符串为:{}", ciphertext); ciphertext = "ENC(" + ciphertext + ")"; if (PropertyValueEncryptionUtils.isEncryptedValue(ciphertext)) { String plaintext = PropertyValueEncryptionUtils.decrypt(ciphertext, basicTextEncryptor); log.info("明文字符串为:{}", plaintext); return plaintext; } log.error("解密失败!"); return ""; } }
@Slf4j public class JasyptUtilsTest { @Test public void testEncrypt(){ String plainText = "Glrs@1234"; String ciperText = JasyptUtils.encrypt(plainText); log.info("加密后的密文为:{}", ciperText); } @Test public void testDecrypt(){ String ciperText = "KZeGx0ixuy4UrBp1HuhiDNnKB0cJr0cW"; String plainText = JasyptUtils.decrypt(ciperText); log.info("解密后的明文为:{}", plainText); } }
经过如上的配置,启动项目,如下从数据库获取数据的应用逻辑就能正常使用了。
@Slf4j @RestController public class HelloController { @Autowired UserMapper userMapper; @GetMapping("/getHello") public String getHello(){ log.info("myapp works!"); List<User> users = userMapper.listUsers(); return users.toString(); } }
@Mapper public interface UserMapper { List<User> listUsers(); }
<mapper namespace="com.example.myapp.mapper.UserMapper"> <select id="listUsers" resultType="com.example.myapp.bean.User"> select zu.user_id userId, zu.user_name userName, zu.age age, zu.user_email userEmail from zx_user zu; </select> </mapper>
四、生成环境启动
生产环境密钥作为启动参数:
java -jar -Djasypt.encryptor.password=your-secret
五、参考文档
Jasypt: Java simplified encryption - Jasypt: Java simplified encryption - Mainhttps://links.jianshu.com/go?to=http%3A%2F%2Fwww.jasypt.org%2F