java-信息安全(十九)加密工具Jasypt
一、概述
Jasypt 这个Java类包为开发人员提供一种简单的方式来为项目增加加密功能,包括:密码Digest认证,文本和对象加密,集成 hibernate,Spring Security(Acegi)来增强密码管理。
Jasypt是一个Java库,可以使开发者不需太多操作来给Java项目添加基本加密功能,而且不需要知道加密原理。
根据Jasypt文档,该技术可用于加密任务与应用程序,例如加密密码、敏感信息和数据通信、创建完整检查数据的sums. 其他性能包括高安全性、基于标准的加密技术、可同时单向和双向加密的加密密码、文本、数字和二进制文件。Jasypt也可以与Acegi Security整合也即Spring Security。Jasypt亦拥有加密应用配置的集成功能,而且提供一个开放的API从而任何一个Java Cryptography Extension都可以使用Jasypt。
Jasypt还符合RSA标准的基于密码的加密,并提供了无配置加密工具以及新的、高可配置标准的加密工具。
1、该开源项目可用于加密任务与应用程序,例如加密密码、敏感信息和数据通信
2、还包括高安全性、基于标准的加密技术、可同时单向和双向加密的加密密码、文本、数字和二进制文件。
3、Jasypt还符合RSA标准的基于密码的加密,并提供了无配置加密工具以及新的、高可配置标准的加密工具。
4、加密属性文件(encryptable properties files)、Spring work集成、加密Hibernate数据源配置、新的命令行工具、URL加密的Apache wicket集成以及升级文档。
5、Jasypt也可以与Acegi Security整合也即Spring Security。Jasypt亦拥有加密应用配置的集成功能,而且提供一个开放的API从而任何一个Java Cryptography Extension都可以使用Jasypt。
二、使用
2.1、jar使用
2.1.1、shell下jar使用加密
Maven下载好的jar包加密\Maven\org\jasypt\jasypt\1.9.3\jasypt-1.9.3.jar
cd ~/.m2/repository/org/jasypt/jasypt/1.9.3
java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI password=G0CvDz7oJn6 algorithm=PBEWithMD5AndDES input=root
输出:
----ARGUMENTS------------------- input: root algorithm: PBEWithMD5AndDES password: G0CvDz7oJn6 ----OUTPUT---------------------- wo9sTA8V7t+kKHKtwzOVSw==
2.1.2、shell下jar使用解密
java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringDecryptionCLI input=wo9sTA8V7t+kKHKtwzOVSw== password=G0CvDz7oJn6 algorithm=PBEWithMD5AndDES
解密值
----ARGUMENTS------------------- algorithm: PBEWithMD5AndDES input: wo9sTA8V7t+kKHKtwzOVSw== password: G0CvDz7oJn6 ----OUTPUT---------------------- root
2.1.3、详细说明
加密入口类:org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI
解密入口类:org.jasypt.intf.cli.JasyptPBEStringDecryptionCLI
input:你要加、解密的字符串
password:密钥口令
algorithm:加密算法
OUTPUT:下面的字符串就是生成的密文、明文
配置项
Key(配置项) | Required(必须) | Default Value(默认值) |
---|---|---|
jasypt.encryptor.password | True | - |
jasypt.encryptor.algorithm | False | PBEWithMD5AndDES |
jasypt.encryptor.keyObtentionIterations | False | 1000 |
jasypt.encryptor.poolSize | False | 1 |
jasypt.encryptor.providerName | False | SunJCE |
jasypt.encryptor.providerClassName | False | null |
jasypt.encryptor.saltGeneratorClassname | False | org.jasypt.salt.RandomSaltGenerator |
jasypt.encryptor.ivGeneratorClassname | False |
org.jasypt.iv.NoIvGenerator,如果想使用java8+后的PBEWITHHMACSHA512ANDAES_256 则要设置成: org.jasypt.salt.RandomIVGenerator. |
jasypt.encryptor.stringOutputType | False | base64 |
jasypt.encryptor.proxyPropertySources | False | false |
2.2、代码方式1-原生
2.2.1、加密解密-文本原生使用
<dependency> <groupId>org.jasypt</groupId> <artifactId>jasypt</artifactId> <version>1.9.3</version> </dependency>
示例
public class BasicTextEncryptorTest { BasicTextEncryptor textEncryptor; @Before public void setUp() { textEncryptor = new BasicTextEncryptor(); textEncryptor.setPassword("EbfYkitulv73I2p0mXI50JMXoaxZTKJ7"); } @Test public void encrypt() { // 加密 System.out.println(textEncryptor.encrypt("root@1234")); //TJetNWzmC4os1CCb+gHtz+5MpL9NFMML //KCTSu/Dv1elE1A/ZyppCHgJAAwKiez/p } @Test public void decyptPwd() { // 解密 // root@1234 System.out.println(textEncryptor.decrypt("TJetNWzmC4os1CCb+gHtz+5MpL9NFMML")); // root@1234 System.out.println(textEncryptor.decrypt("KCTSu/Dv1elE1A/ZyppCHgJAAwKiez/p")); } }
2.2.2、单向散列
一般在做用户认证的时候,通常会使用MD5做简单的散列,然后登录时必须MD5值实现。同样的需求,也可以使用Jasypt来实现。
BasicPasswordEncryptor
@Test public void encrypt() { BasicPasswordEncryptor textEncryptor = new BasicPasswordEncryptor(); String encryptPassword = textEncryptor.encryptPassword("EbfYkitulv73I2p0mXI50JMXoaxZTKJ7"); System.out.println(encryptPassword); boolean checkPassword = textEncryptor.checkPassword("EbfYkitulv73I2p0mXI50JMXoaxZTKJ7", encryptPassword); System.out.println(checkPassword); }
2.2.3、多线程加解密
在多核机器上运行时,我们希望并行处理解密处理。为了获得良好的性能,我们可以使用PooledPBEStringEncryptor 和setPoolSize() API来创建一个解密线程池。它们中的每一个都可以由不同的线程并行使用:
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); encryptor.setPoolSize(4); encryptor.setPassword("some-random-data"); encryptor.setAlgorithm("PBEWithMD5AndTripleDES");
最好将池大小设置为等于机器的核心数。加密和解密的代码与以前的代码相同。
2.3、代码方式2-springboot结合
源码地址:https://github.com/ulisesbocchio/jasypt-spring-boot
pom
<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.1</version> </dependency>
增加JasyptConfig 配置类
@Configuration public class JasyptConfig { @Bean("jasyptStringEncryptor") public StringEncryptor stringEncryptor() { PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); SimpleStringPBEConfig config = new SimpleStringPBEConfig(); config.setPassword("EbfYkitulv73I2p0mXI50JMXoaxZTKJ7"); // config.setAlgorithm("PBEWithMD5AndDES");//默认配置 // config.setKeyObtentionIterations("1000");//默认配置 config.setPoolSize("4"); // config.setProviderName("SunJCE");//默认配置 // config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");//默认配置 // config.setStringOutputType("base64");//默认配置 encryptor.setConfig(config); return encryptor; } }
配置文件的写入和Spring XML的基本类似。application.yml相当于applicationContext.xml,security.properties就是要进行属性替换的配置文件。
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/abc?useSSL=false spring.datasource.username=root spring.datasource.password=${jdbc.password}
security.properties
jdbc.password=ENC(TJetNWzmC4os1CCb+gHtz+5MpL9NFMML)
启动类增加
@SpringBootApplication @EnableEncryptableProperties @PropertySource(value = {"classpath:security.properties"},ignoreResourceNotFound = false) public class SpringRunnerMain { public static void main(String[] args) { SpringApplication.run(SpringRunnerMain.class, args); } }
2.4、关于这个口令的配置方式
jasypt的作者建议是把这个盐值放在系统属性、命令行或是环境变量来使用,而不是放在配置文件
还有种常用方式,直接配置方式(这样口令就暴露在这个配置文件里面,不建议);密文使用ENC(……),密钥放在代码中,配置分开
1、启动命令
jar: 命令:java -Djasypt.encryptor.password=jasypt -jar xxx.jar
war:到Tomcat的bin目录下,打开文件catalina.bat/catalina.sh,添加如下参数,然后保存:window:set JAVA_OPTS="-Djasypt.encryptor.password=jasypt" , Linux:JAVA_OPTS="-Djasypt.encryptor.password=jasypt"
或者直接在tomcat bin 目录新建setenv.bat setenv.sh
文件内容如下
Windows:set JAVA_OPTS="-Djasypt.encryptor.password=jasypt"
Linux:export JAVA_OPTS="-Djasypt.encryptor.password=jasypt"
程序会默认使用。
2、获取环境变量
Properties properties = System.getProperties(); Set<Object> objects = properties.keySet(); for (Object object : objects) { System.out.println("key:" + object + "---:" + properties.get(object)); } Map<String, String> getenv = System.getenv(); for (Map.Entry<String, String> entry : getenv.entrySet()) { System.out.println(entry.getKey() + "---:" + entry.getValue()); }
三、核心类库说明
3.1、加密工具类【以文本text为例】
普通文本实现了如下三种方式
以及参看Strong,AES,只是算法不一致
BasicTextEncryptor→PBEWithMD5AndDES
StrongTextEncryptor→PBEWithMD5AndTripleDES
AES256TextEncryptor→PBEWithHMACSHA512AndAES_256
主要是【参看BasicTextEncryptor】
public final class BasicTextEncryptor implements TextEncryptor { private final StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); public BasicTextEncryptor() { this.encryptor.setAlgorithm("PBEWithMD5AndDES"); } public void setPassword(String password) { this.encryptor.setPassword(password); } public void setPasswordCharArray(char[] password) { this.encryptor.setPasswordCharArray(password); } public String encrypt(String message) { return this.encryptor.encrypt(message); } public String decrypt(String encryptedMessage) { return this.encryptor.decrypt(encryptedMessage); } }
更多pbe算法可以参看:java-信息安全(三)-PBE加密算法
参看上述工具类编写方式,以及上述PBE加密算法,编写:PBEWITHSHA1ANDRC4_128 工具类:RC128TextEncryptor
public class RC128TextEncryptor implements TextEncryptor { private final StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); public RC128TextEncryptor() { this.encryptor.setAlgorithm("PBEWITHSHA1ANDRC4_128"); } public void setPassword(String password) { this.encryptor.setPassword(password); } public void setPasswordCharArray(char[] password) { this.encryptor.setPasswordCharArray(password); } public String encrypt(String message) { return this.encryptor.encrypt(message); } public String decrypt(String encryptedMessage) { return this.encryptor.decrypt(encryptedMessage); } }
测试:
public class RC128TextEncryptorTest { RC128TextEncryptor textEncryptor; @Before public void setUp() { textEncryptor = new RC128TextEncryptor(); textEncryptor.setPassword("EbfYkitulv73I2p0mXI50JMXoaxZTKJ7"); } @Test public void encrypt() { // 加密 System.out.println(textEncryptor.encrypt("root@1234")); //zjhmIP38jmvob56qyNevHjs= //iMX2aR70CkLGdtlAdhe2XKI= } @Test public void decyptPwd() { // 解密 // root@1234 System.out.println(textEncryptor.decrypt("zjhmIP38jmvob56qyNevHjs=")); // root@1234 System.out.println(textEncryptor.decrypt("iMX2aR70CkLGdtlAdhe2XKI=")); } }
综上所述,所有算法核心是调用:StandardPBEStringEncryptor
3.2、StandardPBEStringEncryptor说明
public final class StandardPBEStringEncryptor implements PBEStringCleanablePasswordEncryptor { private static final String MESSAGE_CHARSET = "UTF-8"; private static final String ENCRYPTED_MESSAGE_CHARSET = "US-ASCII"; public static final String DEFAULT_STRING_OUTPUT_TYPE = "base64"; private StringPBEConfig stringPBEConfig = null; private String stringOutputType = "base64"; private boolean stringOutputTypeBase64 = true; private boolean stringOutputTypeSet = false; private final StandardPBEByteEncryptor byteEncryptor; private final Base64 base64; public StandardPBEStringEncryptor() { this.byteEncryptor = new StandardPBEByteEncryptor(); this.base64 = new Base64(); } private StandardPBEStringEncryptor(StandardPBEByteEncryptor standardPBEByteEncryptor) { this.byteEncryptor = standardPBEByteEncryptor; this.base64 = new Base64(); } public synchronized void setConfig(PBEConfig config) { this.byteEncryptor.setConfig(config); if (config != null && config instanceof StringPBEConfig) { this.stringPBEConfig = (StringPBEConfig)config; } } public void setAlgorithm(String algorithm) { this.byteEncryptor.setAlgorithm(algorithm); } public void setPassword(String password) { this.byteEncryptor.setPassword(password); } public void setPasswordCharArray(char[] password) { this.byteEncryptor.setPasswordCharArray(password); } public void setKeyObtentionIterations(int keyObtentionIterations) { this.byteEncryptor.setKeyObtentionIterations(keyObtentionIterations); } public void setSaltGenerator(SaltGenerator saltGenerator) { this.byteEncryptor.setSaltGenerator(saltGenerator); } public void setIvGenerator(IvGenerator ivGenerator) { this.byteEncryptor.setIvGenerator(ivGenerator); } public void setProviderName(String providerName) { this.byteEncryptor.setProviderName(providerName); } public void setProvider(Provider provider) { this.byteEncryptor.setProvider(provider); } public synchronized void setStringOutputType(String stringOutputType) { CommonUtils.validateNotEmpty(stringOutputType, "String output type cannot be set empty"); if (this.isInitialized()) { throw new AlreadyInitializedException(); } else { this.stringOutputType = CommonUtils.getStandardStringOutputType(stringOutputType); this.stringOutputTypeSet = true; } } synchronized StandardPBEStringEncryptor[] cloneAndInitializeEncryptor(int size) { StandardPBEByteEncryptor[] byteEncryptorClones = this.byteEncryptor.cloneAndInitializeEncryptor(size); this.initializeSpecifics(); StandardPBEStringEncryptor[] clones = new StandardPBEStringEncryptor[size]; clones[0] = this; for(int i = 1; i < size; ++i) { clones[i] = new StandardPBEStringEncryptor(byteEncryptorClones[i]); if (CommonUtils.isNotEmpty(this.stringOutputType)) { clones[i].setStringOutputType(this.stringOutputType); } } return clones; } public boolean isInitialized() { return this.byteEncryptor.isInitialized(); } public synchronized void initialize() { if (!this.isInitialized()) { this.initializeSpecifics(); this.byteEncryptor.initialize(); } } private void initializeSpecifics() { if (this.stringPBEConfig != null) { String configStringOutputType = this.stringPBEConfig.getStringOutputType(); this.stringOutputType = !this.stringOutputTypeSet && configStringOutputType != null ? configStringOutputType : this.stringOutputType; } this.stringOutputTypeBase64 = "base64".equalsIgnoreCase(this.stringOutputType); } public String encrypt(String message) { if (message == null) { return null; } else { if (!this.isInitialized()) { this.initialize(); } try { byte[] messageBytes = message.getBytes("UTF-8"); byte[] encryptedMessage = this.byteEncryptor.encrypt(messageBytes); String result = null; if (this.stringOutputTypeBase64) { encryptedMessage = this.base64.encode(encryptedMessage); result = new String(encryptedMessage, "US-ASCII"); } else { result = CommonUtils.toHexadecimal(encryptedMessage); } return result; } catch (EncryptionInitializationException var5) { throw var5; } catch (EncryptionOperationNotPossibleException var6) { throw var6; } catch (Exception var7) { throw new EncryptionOperationNotPossibleException(); } } } public String decrypt(String encryptedMessage) { if (encryptedMessage == null) { return null; } else { if (!this.isInitialized()) { this.initialize(); } try { byte[] encryptedMessageBytes = null; byte[] encryptedMessageBytes; if (this.stringOutputTypeBase64) { encryptedMessageBytes = encryptedMessage.getBytes("US-ASCII"); encryptedMessageBytes = this.base64.decode(encryptedMessageBytes); } else { encryptedMessageBytes = CommonUtils.fromHexadecimal(encryptedMessage); } byte[] message = this.byteEncryptor.decrypt(encryptedMessageBytes); return new String(message, "UTF-8"); } catch (EncryptionInitializationException var4) { throw var4; } catch (EncryptionOperationNotPossibleException var5) { throw var5; } catch (Exception var6) { throw new EncryptionOperationNotPossibleException(); } } } }
这里涉及了具体加密解密实现逻辑。
直接初始化调用
public class StandardPBEStringEncryptorTest { StandardPBEStringEncryptor textEncryptor; @Before public void setUp() { textEncryptor = new StandardPBEStringEncryptor(); // textEncryptor.setAlgorithm("");//自行指定 textEncryptor.setPassword("EbfYkitulv73I2p0mXI50JMXoaxZTKJ7"); } @Test public void encrypt() { // 加密 System.out.println(textEncryptor.encrypt("root@1234")); //Han0rFt6K2jhvrK5swPpD/ctoUMPckIO //upkr4Rc6bhmpUXhdRoT9qqkhiSfEhTvS } @Test public void decyptPwd() { // 解密 // root@1234 System.out.println(textEncryptor.decrypt("Han0rFt6K2jhvrK5swPpD/ctoUMPckIO")); // root@1234 System.out.println(textEncryptor.decrypt("upkr4Rc6bhmpUXhdRoT9qqkhiSfEhTvS")); } }
3.3、上述2.3使用池化方式