目录
1 Jasypt简介…
2 基础知识回顾…
3 Jasypt+基本加密器…
4 Jasypt+PBE加密器…
5 Jasypt+池化加密器…
6 Jasypt+客户端工具…
7 Jasypt+Springboot基本用法…
8 Jasypt+Springboot自定义加密器…
9 Jasypt+Springboot配置文件加密…
1、Jasypt简介
Jasypt是一个java的加密库,通过对java自带加密算法的二次包装,提供了十分简洁的加密接口,在保证安全的同时,降低了密码算法的使用门槛,并且提供多种数据类型的加密。在Jasypt-spring-boot的加持下,Jasypt可以在Spring应用中轻易集成,充分利用Spring的依赖注入,属性检测等特性。本文提供Jasypt用法的介绍,基本覆盖Jasypt绝大部分的使用场景。
2、基础知识回顾
对称加密(Symmetric encryption):加密和解密用同一个密钥的方法,即为对称加密算法。
哈希算法(Hash):也叫散列、杂凑、摘要,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值,哈希算法是单向算法,即无法通过哈希值倒推原始数据。
口令散列(Password Hashing):是将用户输入的密码通过一系列算法处理后,生成一个固定长度的散列值。口令散列常用语口令存储和密钥生成。在软件系统中,出于安全需求,用户密码不允许直接在数据库中明文存储的,通过对口令做哈希在入库,这样即是数据库泄露了,用户密码依然不会泄露。对于密钥生成的场景,需区分口令和密钥的区别,密钥是指加密过程中实际作用于加密算法的数据串,也叫key,key一般长度较长(16或32字节以上),实际场景中不太可能让用户去设置和记住这么长的密码,因此一般使用用户设置的短密码(也叫口令),用hash算法对口令做散列,用散列出的key加密数据。由于用户口令普遍较短,密码空间较小,重复率比较高,如果数据库发生泄漏,攻击者通过密码和散列值的映射表(又称彩虹表)用于多个用户口令的碰撞,于是在实际散列的时候需加入其它“值”合并计算。该值又称盐或salt,且每个用户的salt都不一样。如果攻击者试图通过彩虹表对口令做碰撞,需要针对每个用户生成口令长度(散列值隐去了实际口令长度)不同的彩虹表,大大增加了破解的难度。口令散列可简化为:hash(口令+salt)。
3、Jasypt+基本加密器
1)摘要计算
Digester = new Digester();
digester.setAlgorithm("MD5");
byte[] digest = digester.digest("123".getBytes());
输出:202cb962ac59075b964b07152d234b70
使用外部工具验证,结果符合预期:
Digester仅提供两个setter,setAlgorithm用户设置算法类型,支持的算法类型有:MD2, MD5, SHA, SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256, SHA3-224, SHA3-256, SHA3-384, SHA3-512,setProvider,设置算法提供的厂商Provider,基本不会用到,缺省即可。
2)密码验证
xxxPasswordEncryptor接口用于密码的加密存储和验证,加密存储即对口令做散列用于存储,验证接口用于用户再次提供口令比对验证,用法如下:
BasicPasswordEncryptor passwordEncryptor = new BasicPasswordEncryptor();
String password= "123456";
String encryptedPassword = passwordEncryptor.encryptPassword(password);
System.out.println(String.format("密文是:%s,输出接口可用于数据库存储", encryptedPassword));
if (passwordEncryptor.checkPassword(password, encryptedPassword)) {System.out.println("口令比对通过!");
} else {System.out.println("口令比对不通过!");
}
Jasypt提供了两个不同算法强度的接口:BasicPasswordEncryptor和StrongPasswordEncryptor,BasicPasswordEncryptor基于MD5,StrongPasswordEncryptor基于SHA-256,使用方法一致。
3)文本加密
Jasypt提供了不同强度的文本(String)加密接口,根据强度排序依次为:
- BasicTextEncryptor(PBEWithMD5AndDES)
- StrongTextEncryptor (PBEWithMD5AndTripeDES)
- AES256TextEncryptor (PBEWithHMACSHA512AndAES_256)
使用方法均为:先调用setPassword设置密码,再调用encrypt或decrypt做加解密(其他加密器的使用替换BasicTextEncryptor类型即可):
BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
textEncryptor.setPassword("passwd");
String myEncryptedText = textEncryptor.encrypt("123456");
System.out.println(myEncryptedText);
String plainText = textEncryptor.decrypt(myEncryptedText);
System.out.println(plainText);
4)二进制加密
二进制加密和文本加密的区别仅在于入参不同,其他基本一致,根据算法强度二进制加密提供如下接口:
- BasicBinaryEncryptor (PBEWithMD5AndDES)
- StrongBinaryEncryptor (PBEWithMD5AndTripeDES)
- AES256TextEncryptor (PBEWithHMACSHA512AndAES_256)
基本用法为:
BasicBinaryEncryptor binaryEncryptor = new BasicBinaryEncryptor();
binaryEncryptor.setPassword("passwd");
byte[] bytes = {'1', '2', '3'};
byte[] myEncryptedByte = binaryEncryptor.encrypt(bytes);
System.out.println(String.format("加密后的密文是:%s",Base64.getEncoder().encodeToString(myEncryptedByte)));
byte[] plainText = binaryEncryptor.decrypt(myEncryptedByte);
System.out.println(String.format("解密后的明文是:%s", new String(plainText)));
输出:
加密后的密文是:1a8117840c945323dc91a7ecf09583bd
解密后的明文是:123
5)数值加密
Jasypt提供了BigInteger和BigDecimal类型数据的加密接口,此类型加密器使用较少,本文不做详细介绍,基本用法如下:
BigInteger myNumber = new BigInteger("123");
BasicIntegerNumberEncryptor numberEncryptor = new BasicIntegerNumberEncryptor();
numberEncryptor.setPassword("passwd");
BigInteger encryptMyNumber = numberEncryptor.encrypt(myNumber);
System.out.println(String.format("加密后的密文是:%d", encryptMyNumber));
BigInteger decryptMyNumber = numberEncryptor.decrypt(encryptMyNumber);
System.out.println(String.format("解密后的明文是:%d", decryptMyNumber));
输出:
加密后的密文是:442164205133206440127638602826610783717381111824
解密后的明文是:123
4、Jasypt+PBE加密器
PBE,基于口令的密码运算,即在实际加密前,先用用户口令散列出加密key,再用key做加解密运算。上节中所涉及的接口均为Jasypt PBE算法的包装类,旨在隐去一些不常用的细节,为用户提供简洁的接口。Jasypt提供四个不同类型的PBE加密器,均以StandardPBE<类型>Encryptor命名,通过追踪调用链可发现,这4个不同类型的加密器中,以StandardPBEByteEncryptor为基本接口,这也容易理解,程序计算的基本单位是字节,不管什么数据类型,最终都得转为字节类型再做运算,下列仅以StandardPBEByteEncryptor做举例:
StandardPBEStringEncryptor = new StandardPBEStringEncryptor();
standardPBEStringEncryptor.setPassword("passwd");
String cihperText = standardPBEStringEncryptor.encrypt("123456");
System.out.println(String.format("StandardPBEStringEncryptor的加密结果:%s", cihperText));
BasicTextEncryptor = new BasicTextEncryptor();
basicTextEncryptor.setPassword("passwd");
System.out.println(String.format("basicTextEncryptor的解密结果:%s", basicTextEncryptor.decrypt(cihperText)));
输出:
StandardPBEStringEncryptor的加密结果:ZCWgTEa2pWGXPnI23NFI8Q==
basicTextEncryptor的解密结果:123456
StandardPBEByteEncryptor的配置项如下:
PBEWITHHMACSHA1ANDAES_128
PBEWITHHMACSHA1ANDAES_256
PBEWITHHMACSHA224ANDAES_128
PBEWITHHMACSHA224ANDAES_256
PBEWITHHMACSHA256ANDAES_128
PBEWITHHMACSHA256ANDAES_256
PBEWITHHMACSHA384ANDAES_128
PBEWITHHMACSHA384ANDAES_256
PBEWITHHMACSHA512ANDAES_128
PBEWITHHMACSHA512ANDAES_256
PBEWITHMD5ANDDES
PBEWITHMD5ANDTRIPLEDES
PBEWITHSHA1ANDDESEDE
PBEWITHSHA1ANDRC2_128
PBEWITHSHA1ANDRC2_40
PBEWITHSHA1ANDRC4_128
PBEWITHSHA1ANDRC4_40
PBE接口均提供了以下settter:
1) setAlgorithm
设置算法,缺省为PBEWithMD5AndDES,可选算法有:
2)setPassword
设置密码,不可缺省
3)setKeyObtentionIterations
设置口令散列的迭代次数,缺省为1000
4)setSaltGenerator
设置盐的生成器,缺省为org.jasypt.salt.RandomSaltGenerator,也是对JCE提供的随机数生成器的二次包装
5)setIvGenerator
用于生成IV(Initial vector)值用于CBC、CFB等其他需要初始向量的加密模式,IV生成器的本质也是随机数生成器,缺省为org.jasypt.iv.RandomIvGenerator,也是对JCE提供的随机数生成器的二次包装
6)setProviderName
设置JCE的Provider,默认是SunJCE。
对于不需要特别定制的场景,使用PBE接口仍可以保持和上节中的加密接口一样间接的使用方式,在使用前仅配置密码,其他项均保持缺省。
5、Jasypt+池化加密器
Jasypt引入了池化加密器的概念,旨在提高在多核处理器下的计算性能。在应用程序中频繁使用加密和解密操作时,每次创建和销毁加密器实例都会带来一定的性能开销。为了避免这种开销,Jasypt将多个加密器实例保存在一个池,按需从池中获取加密器实例并重复使用。这种方式可以避免重复创建和销毁加密器实例,提高应用程序的性能。Jasypt的方法均为线程安全方法,但是StandardPBEByteEncryptor的encrytor和decryptor中存在线程同步代码,这意味着如果多个线程同时使用一个加密器,存在同步竞争问题,影响系统性能,所以Jasypt池化加密器的本质是通过池化为并发下的每个线程提供不同的加密器,避免竞争问题。应用需自己保证池化的加密器为单例模式。
Jasypt对PBE接口均实现了相应的池化,命名格式为PooledPBE<类型>Encryptor,使用方法除了多了个setPoolSize,其他配置完全一样。setPoolSize建议和CPU核心数一样或略高于核心数,示例代码如下:
// pooledPBEStringEncryptor 全局变量
PooledPBEStringEncryptor = new PooledPBEStringEncryptor();
pooledPBEStringEncryptor.setPoolSize(4);
pooledPBEStringEncryptor.setPassword("passwd");
String cihperText = pooledPBEStringEncryptor.encrypt("123456");
System.out.println(String.format("PooledPBEStringEncryptor的加密结果:%s", cihperText));//使用基本接口对池化的计算接口做验证
BasicTextEncryptor = new BasicTextEncryptor();
basicTextEncryptor.setPassword("passwd");
System.out.println(String.format("BasicTextEncryptor的解密结果:%s", basicTextEncryptor.decrypt(cihperText)));
输出:
PooledPBEStringEncryptor的加密结果:D0rSSuL79lNooMEyHRdB/g==
basicTextEncryptor的解密结果:123456
6、Jasypt+客户端工具
Jasypt提供了客户端工具可直接用于加密运算,下载地址:链接。解压后的bin的目录为Jasypt客户端工具脚本,工具包包含加密、解密和摘要计算脚本,脚本的本质均为调用Jasypt依赖包下的指定类完成密码运算。直接运行相关脚本可查看帮助信息:
下面使用客户端工具做数据加密,并使用代码对结果做解密验证:
客户端工具默认使用BasicTextEncryptor加密器,因此可用代码做验证:
BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
textEncryptor.setPassword("passwd");
String myEncryptedText = textEncryptor.decrypt("8zfUPVd/ruyBC3Ah4lRziQ==");
System.out.println(String.format("解密结果为:%s", myEncryptedText) );
输出:
解密结果为:123456
结果符合预期。
下面是用客户端工具做摘要计算的验证过程:
输出结果为base64编码,对输出做base64解码后并以HEX格式显示时,实际结果为:
当盐的长度为0,迭代次数为1(默认为1000)时,等同于一次普通的摘要计算,因此可以使用其他MD5工具对结果做验证比对:
结果符合预期。
7、Jasypt+Springboot基本用法
使用starter完成引入:
<dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>3.0.5</version>
</dependency>
jasypt-spring-boot是Jasypt的springboot框架,引入框架之后即可根据配置自动注入相应的加密器,该框架还提供属性检测功能,可用于敏感配置的加密,在使用的时候自动完成解密。Jasypt-spring-boot基本配置如下:
Key |
Required |
Default Value |
jasypt.encryptor.password |
TRUE |
|
jasypt.encryptor.algorithm |
FALSE |
PBEWITHHMACSHA512ANDAES_256 |
jasypt.encryptor.key-obtention-iterations |
FALSE |
1000 |
jasypt.encryptor.pool-size |
FALSE |
1 |
jasypt.encryptor.provider-name |
FALSE |
SunJCE |
jasypt.encryptor.provider-class-name |
FALSE |
null |
jasypt.encryptor.salt-generator-classname |
FALSE |
org.jasypt.salt.RandomSaltGenerator |
jasypt.encryptor.iv-generator-classname |
FALSE |
org.jasypt.iv.RandomIvGenerator |
jasypt.encryptor.string-output-type |
FALSE |
base64 |
jasypt.encryptor.proxy-property-sources |
FALSE |
FALSE |
jasypt.encryptor.skip-property-sources |
FALSE |
empty list |
以上配置除了后面两项,其他配置和PBExxx类的setter方法基本一致。其中jasypt.encryptor.password为必须用户指定,其他配置均提供缺省值,algorithm的缺省配置弃用PBExxx默认的PBEWithMD5AndDES,改为强度更高的PBEWITHHMACSHA512ANDAES_256, 因此对Springboot中的加密结果做Jasypt手工解密时,需同时做password和algorithm的配置。下面案例展示使用默认数据加密,并使用Jasypt“原生”接口做解密验证:
application.properties添加配置:
jasypt.encryptor.password=passwd@Autowired
StringEncryptor;String cypherText = stringEncryptor.encrypt("123456");
System.out.println(String.format("密文为:%s", cypherText));
输出:
密文为:iw7cURRMFU2Wl7/k31mG0Sn8CVjdJ10xu4cb9vodOjbB7efNLui3CI6xgv4DSItY
使用“原生”接口对输出做解密:
AES256TextEncryptor textEncryptor = new AES256TextEncryptor();
textEncryptor.setPassword("passwd");
String myEncryptedText = textEncryptor.decrypt("iw7cURRMFU2Wl7/k31mG0Sn8CVjdJ10xu4cb9vodOjbB7efNLui3CI6xgv4DSItY");
System.out.println(String.format("解密结果为:%s", myEncryptedText) );
输出:
解密结果为:123456
8、Jasypt+Springboot自定义加密器
Jasypt-spring-boo默认注入StringEncryptor加密器的bean-id为”jasyptStringEncryptor”,如需覆盖该加密器,只需在配置类中重新声明新的类型为StringEncryptor且bean-id为”jasyptStringEncryptor”的加密器即可,jasypt-spring-boot-starter通过是否已存在该名称的bean决定是否注入默认的加密器,案例如下:
//添加配置类
@Configuration
public class JasyptConfig {@Bean(name="jasyptStringEncryptor")@Primarystatic public StringEncryptor stringEncryptor() {StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();encryptor.setPassword("passwd");encryptor.setAlgorithm("PBEWITHMD5ANDDES");return encryptor;}
}
输出:
加密结果为:H3QMRyMGPNzYW8QcdhFg1w==
解密结果为:123456
从加密结果长度可看出,@Autowired注入的为自定义的加密器,结果符合预期。自定义加密器仅要求返回StringEncryptor接口,如果开发者需要实现自己的加密算法,只需要实现该接口即可。
9、Jasypt+Springboot配置文件加密
jasypt-spring-boot提供属性检测的功能,对于配置文件的配置项,如果符合jasypt-spring-boot指定的规则,则会自动解密还原为明文注入到对应的代码中,默认规则为ENC(),举例:
//配置文件为
jasypt.encryptor.password=passwd
my.password=ENC(H3QMRyMGPNzYW8QcdhFg1w==)@Value("${my.password:}")private String password;
System.out.println(String.format("my.password:%s", password));
输出:
my.password: 123456
为避免jasypt.encryptor.password本文直接写在配置文件里,jasypt-spring-boot允许通过环境变量或启动参数传入所以配置,因此可以通过环境变量隐藏口令,步骤如下:
启动前:
export jasypt.encryptor.password=xxx启动应用:
java -jar xxx.jar待应用java完成配置注入后,删除环境变量:
export jasypt.encryptor.password=