Java 使用 CBC 的 Blowfish 加密
Java Blowfish Encryption with CBC
我正在尝试使用 Blowfish
和 CBC
制作此网站正在做的事情 https://codebeautify.org/encrypt-decrypt
我不确定实际的术语是什么,但我想实现的加密方法将产生不一致的加密字符串,尽管使用相同的内容和密钥,
例如,如果我用密钥 key123
加密 Hello
两次,第一个结果可能显示 abcde
,第二个应该显示其他内容,例如 fghij
。但是用 key123
解密 abcde
和 fghij
应该 return 相同 Hello
.
我还可以知道他们用来生成最终结果的编码类型是什么吗?比如hex/base64,因为我都试过了,但是好像结果不一样。
这是我正在使用的:
加密货币 class:
public static String enc(String content, String key) {
String encCon = "";
try {
String IV = "12345678";
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
String secret = content;
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new javax.crypto.spec.IvParameterSpec(IV.getBytes("UTF-8")));
byte[] encoding = cipher.doFinal(secret.getBytes("UTF-8"));
System.out.println("-- Encrypted -----------");
encCon = DatatypeConverter.printBase64Binary(encoding);
System.out.println("-- encCon : " + encCon);
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
}
return encCon;
}
public static String dec(String content, String key) {
String decCon = "";
try {
String IV = "12345678";
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
// Decode Base64
byte[] ciphertext = DatatypeConverter.parseBase64Binary(content);
// Decrypt
cipher.init(Cipher.DECRYPT_MODE, keySpec, new javax.crypto.spec.IvParameterSpec(IV.getBytes("UTF-8")));
byte[] message = cipher.doFinal(ciphertext);
System.out.println("-- Decrypted -----------");
decCon = new String(message, "UTF-8");
System.out.println("-- decCon : " + decCon);
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
}
return decCon;
}
调用class(如Main.java)
// This is what I get from codebeautify site, encrypting Hello with key123
// However, I'm getting javax.crypto.BadPaddingException: Given final block not properly padded
Crypto.dec("08GCpwyZc+qGNuxSvXAD2A==", "key123");
// Below 2 lines works fine, the only problem is the result isn't randomized
String encContent = Crypto.enc("Hello", "key123");
Crypto.dec(encContent, "key123");
将不同的输出映射回同一输入的唯一方法是向输入添加额外数据,然后将其从解密输出中剥离。使用 PKCS5Padding 是不够的,因为这不是随机的,而且在最坏的情况下,只增加 1 个字节。使用 IV 没有用,因为它需要在解密时知道。
最简单的方法是加密时加入一定字节数(例如等于块大小)的随机数据,解密时忽略这些字节。这个随机数据的名称是 'nonce' 来自 Number Used Once。 (不要与密切相关的 'salt' 混淆,后者是您保留以备后用的数字)。
顺便说一句,我做这个并不是为了匹配网站。我不知道网站是如何加密的,因为它将所有输入值发送到服务器并显示响应。谈论安全...
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
public static String enc(String content, String key) {
String encCon = "";
try {
String IV = "12345678";
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
byte[] nonce = new byte[cipher.getBlockSize()];
SECURE_RANDOM.nextBytes(nonce);
// Construct plaintext = nonce + secret
byte[] secret = content.getBytes(StandardCharsets.UTF_8);
byte[] plaintext = new byte[nonce.length + secret.length];
System.arraycopy(nonce, 0, plaintext, 0, nonce.length);
System.arraycopy(secret, 0, plaintext, nonce.length, secret.length);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8)));
byte[] encoding = cipher.doFinal(plaintext);
encCon = DatatypeConverter.printBase64Binary(encoding);
} catch (Exception ex) {
ex.printStackTrace();
}
return encCon;
}
public static String dec(String content, String key) {
String decCon = "";
try {
String IV = "12345678";
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
// Decode Base64
byte[] ciphertext = DatatypeConverter.parseBase64Binary(content);
// Decrypt
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8)));
byte[] message = cipher.doFinal(ciphertext);
decCon = new String(message,
cipher.getBlockSize(),
message.length - cipher.getBlockSize(),
StandardCharsets.UTF_8);
} catch (Exception ex) {
ex.printStackTrace();
}
return decCon;
}
Ps。您知道将秘密存储在字符串中是个坏主意吗?字符串是最终的,所以内容不能被删除。可以擦除字节数组(为简洁起见,本例中未执行)。您是否也知道您可以制作任何 Windows 程序来查看任何其他 Windows 程序的完整内存占用?
更新 2019-04-21 09:49 P.M。 UTC
在@MaartenBodewes 和@MarkJeronimus 指出了一些需要考虑的事项后,我正在更新答案以使其更正确。但是因为这个问题是关于实现的,而不是关于让它更安全的,所以这个和旧版本应该足以至少提供一点洞察力。同样,可以通过修改以下代码来实现更安全的解决方案。
更新日志
- 密钥派生
- 处理异常及其详细信息
- 对每个数据使用单个 SecureRandom 实例(iv[8 字节] 和 salt[32 字节])
- 待加密明文和待解密密文检查空值和空值
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.xml.bind.DatatypeConverter;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
public class Crypto {
private static final char[] tempKey = new char[] {'T', 'E', 'M', 'P', '_', 'G', 'E', 'N', '_', 'K', 'E', 'Y'};
private static final SecureRandom secureRandomForSalt = new SecureRandom();
private static final SecureRandom secureRandomForIV = new SecureRandom();
private static byte[] generateSalt() throws RuntimeException {
try{
byte[] saltBytes = new byte[32];
secureRandomForSalt.nextBytes(saltBytes);
return saltBytes;
}
catch(Exception ex){
ex.printStackTrace();
throw new RuntimeException("An error occurred in salt generation part. Reason: " + ex.getMessage());
}
}
public static String enc(String content) throws RuntimeException {
String encClassMethodNameForLogging = Crypto.class.getName() + ".enc" + " || ";
byte[] salt;
byte[] encodedTmpSecretKey;
SecretKeySpec keySpec;
Cipher cipher;
byte[] iv;
IvParameterSpec ivParameterSpec;
String finalEncResult;
if(content == null || content.trim().length() == 0) {
throw new RuntimeException("To be encrypted text is null or empty");
}
System.out.println("-- Encrypting -----------");
try {
salt = generateSalt();
}
catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in salt generation part. Reason: " + ex.getMessage());
}
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(Crypto.tempKey, salt, 65536, 256);
SecretKey tmpSecretKey = factory.generateSecret(spec);
encodedTmpSecretKey = tmpSecretKey.getEncoded();
System.out.println("-- Secret Key Derivation in Encryption: " + Base64.getEncoder().encodeToString(encodedTmpSecretKey));
}
catch (NoSuchAlgorithmException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
}
catch (InvalidKeySpecException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: Key length may not be correct");
}
catch (Exception ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage());
}
try {
keySpec = new SecretKeySpec(encodedTmpSecretKey, "Blowfish");
cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
}
catch (NoSuchAlgorithmException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
}
catch (NoSuchPaddingException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation: The particular padding mechanism is requested but is not available in the environment");
}
catch (Exception ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage());
}
try {
iv = new byte[cipher.getBlockSize()];
secureRandomForIV.nextBytes(iv);
ivParameterSpec = new IvParameterSpec(iv);
}
catch (Exception ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in iv creation part. Reason: " + ex.getMessage());
}
try {
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
byte[] encoding = cipher.doFinal(content.getBytes("UTF-8"));
String encCon = DatatypeConverter.printBase64Binary(encoding);
String ivStr = DatatypeConverter.printBase64Binary(iv);
String saltStr = DatatypeConverter.printBase64Binary(salt);
System.out.println("-- encCon : " + encCon);
System.out.println("-- iv : " + ivStr);
System.out.println("-- salt : " + saltStr);
finalEncResult = encCon + ":" + ivStr + ":" + saltStr;
System.out.println("-- finalEncRes : " + finalEncResult + "\n");
}
catch (InvalidKeyException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: Most probably you didn't download and copy 'Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files'");
}
catch (InvalidAlgorithmParameterException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: IV length may not be correct");
}
catch (IllegalBlockSizeException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: The length of data provided to a block cipher is incorrect, i.e., does not match the block size of the cipher");
}
catch (BadPaddingException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: A particular padding mechanism is expected for the input data but the data is not padded properly (Most probably wrong/corrupt key caused this)");
}
catch (UnsupportedEncodingException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: The Character Encoding is not supported");
}
catch (Exception ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage());
}
return finalEncResult;
}
public static String dec(String encContent) throws RuntimeException {
String decClassMethodNameForLogging = Crypto.class.getName() + ".dec" + " || ";
String decCon;
byte[] salt;
byte[] encodedTmpSecretKey;
SecretKeySpec keySpec;
Cipher cipher;
byte[] iv;
if(encContent == null || encContent.trim().length() == 0) {
throw new RuntimeException("To be decrypted text is null or empty");
}
System.out.println("-- Decrypting -----------");
try {
salt = DatatypeConverter.parseBase64Binary(encContent.substring(encContent.lastIndexOf(":") + 1));
}
catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in salt retrieving part. Reason: " + ex.getMessage());
}
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(Crypto.tempKey, salt, 65536, 256);
SecretKey tmpSecretKey = factory.generateSecret(spec);
encodedTmpSecretKey = tmpSecretKey.getEncoded();
System.out.println("-- Secret Key Gathering in Decryption: " + Base64.getEncoder().encodeToString(encodedTmpSecretKey));
}
catch (NoSuchAlgorithmException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
}
catch (InvalidKeySpecException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: Key length may not be correct");
}
catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage());
}
try {
keySpec = new SecretKeySpec(encodedTmpSecretKey, "Blowfish");
cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
}
catch (NoSuchAlgorithmException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
}
catch (NoSuchPaddingException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation : The particular padding mechanism requested is not available in the environment");
}
catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage());
}
try {
iv = DatatypeConverter.parseBase64Binary(encContent.substring(encContent.indexOf(":") + 1, encContent.lastIndexOf(":")));
}
catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in iv creation part. Reason: " + ex.getMessage());
}
try {
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
byte[] decoding = cipher.doFinal(Base64.getDecoder().decode(encContent.substring(0, encContent.indexOf(":"))));
decCon = new String(decoding, "UTF-8");
System.out.println("-- decCon : " + decCon + "\n");
}
catch (InvalidKeyException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: Most probably you didn't download and copy 'Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files'");
}
catch (InvalidAlgorithmParameterException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: IV length may not be correct");
}
catch (IllegalBlockSizeException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: The length of data provided to a block cipher is incorrect, i.e., does not match the block size of the cipher");
}
catch (BadPaddingException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: A particular padding mechanism is expected for the input data but the data is not padded properly (Most probably wrong/corrupt key caused this)");
}
catch (UnsupportedEncodingException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: The Character Encoding is not supported");
}
catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage());
}
return decCon;
}
public static void main(String args[]) {
System.out.println("-- Original -------------");
String plainText = "hello world";
System.out.println("-- origWord : " + plainText + "\n");
String e = Crypto.enc(plainText);
String d = Crypto.dec(e);
System.out.println("-- Results --------------");
System.out.println("-- PlainText: " + plainText);
System.out.println("-- EncryptedText: " + e);
System.out.println("-- DecryptedText: " + d);
}
}
此外,可执行版本在下面;
https://www.jdoodle.com/a/19HT
原始答案
我看到书面评论满足您的需求,但我想分享以下解决方案作为代码示例以满足您的需求,也供将来参考;
** 使用随机 IV(针对 IV 大小给出了密码块大小,但也可以定义静态字节大小,例如“16 字节”)
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import javax.xml.bind.DatatypeConverter;
import java.security.SecureRandom;
import javax.crypto.spec.IvParameterSpec;
public class Crypto {
public static String enc(String content, String key) {
String encCon = "";
String ivStr = "";
try {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
byte[] iv = new byte[cipher.getBlockSize()];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
String secret = content;
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
byte[] encoding = cipher.doFinal(secret.getBytes("UTF-8"));
System.out.println("-- Encrypted -----------");
encCon = DatatypeConverter.printBase64Binary(encoding);
ivStr = DatatypeConverter.printBase64Binary(iv);
System.out.println("-- encCon : " + encCon);
System.out.println("-- iv : " + ivStr);
} catch (Exception ex) {
ex.printStackTrace();
}
return encCon + ":" + ivStr;
}
public static String dec(String encContent, String key) {
String decCon = "";
try {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
byte[] iv = DatatypeConverter.parseBase64Binary(encContent.substring(encContent.indexOf(":") + 1));
String secret = encContent.substring(0, encContent.indexOf(":"));
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
byte[] decoding = cipher.doFinal(Base64.getDecoder().decode(secret));
System.out.println("-- Decrypted -----------");
decCon = new String(decoding, "UTF-8");
System.out.println("-- decCon : " + decCon);
} catch (Exception ex) {
ex.printStackTrace();
}
return decCon;
}
public static void main(String args[]) {
String e = Crypto.enc("hello world", "key123");
String d = Crypto.dec(e, "key123");
}
}
注意:当然可以实现更安全的方案。以上解决方案仅供参考。
我正在尝试使用 Blowfish
和 CBC
我不确定实际的术语是什么,但我想实现的加密方法将产生不一致的加密字符串,尽管使用相同的内容和密钥,
例如,如果我用密钥 key123
加密 Hello
两次,第一个结果可能显示 abcde
,第二个应该显示其他内容,例如 fghij
。但是用 key123
解密 abcde
和 fghij
应该 return 相同 Hello
.
我还可以知道他们用来生成最终结果的编码类型是什么吗?比如hex/base64,因为我都试过了,但是好像结果不一样。
这是我正在使用的:
加密货币 class:
public static String enc(String content, String key) {
String encCon = "";
try {
String IV = "12345678";
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
String secret = content;
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new javax.crypto.spec.IvParameterSpec(IV.getBytes("UTF-8")));
byte[] encoding = cipher.doFinal(secret.getBytes("UTF-8"));
System.out.println("-- Encrypted -----------");
encCon = DatatypeConverter.printBase64Binary(encoding);
System.out.println("-- encCon : " + encCon);
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
}
return encCon;
}
public static String dec(String content, String key) {
String decCon = "";
try {
String IV = "12345678";
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
// Decode Base64
byte[] ciphertext = DatatypeConverter.parseBase64Binary(content);
// Decrypt
cipher.init(Cipher.DECRYPT_MODE, keySpec, new javax.crypto.spec.IvParameterSpec(IV.getBytes("UTF-8")));
byte[] message = cipher.doFinal(ciphertext);
System.out.println("-- Decrypted -----------");
decCon = new String(message, "UTF-8");
System.out.println("-- decCon : " + decCon);
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
}
return decCon;
}
调用class(如Main.java)
// This is what I get from codebeautify site, encrypting Hello with key123
// However, I'm getting javax.crypto.BadPaddingException: Given final block not properly padded
Crypto.dec("08GCpwyZc+qGNuxSvXAD2A==", "key123");
// Below 2 lines works fine, the only problem is the result isn't randomized
String encContent = Crypto.enc("Hello", "key123");
Crypto.dec(encContent, "key123");
将不同的输出映射回同一输入的唯一方法是向输入添加额外数据,然后将其从解密输出中剥离。使用 PKCS5Padding 是不够的,因为这不是随机的,而且在最坏的情况下,只增加 1 个字节。使用 IV 没有用,因为它需要在解密时知道。
最简单的方法是加密时加入一定字节数(例如等于块大小)的随机数据,解密时忽略这些字节。这个随机数据的名称是 'nonce' 来自 Number Used Once。 (不要与密切相关的 'salt' 混淆,后者是您保留以备后用的数字)。
顺便说一句,我做这个并不是为了匹配网站。我不知道网站是如何加密的,因为它将所有输入值发送到服务器并显示响应。谈论安全...
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
public static String enc(String content, String key) {
String encCon = "";
try {
String IV = "12345678";
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
byte[] nonce = new byte[cipher.getBlockSize()];
SECURE_RANDOM.nextBytes(nonce);
// Construct plaintext = nonce + secret
byte[] secret = content.getBytes(StandardCharsets.UTF_8);
byte[] plaintext = new byte[nonce.length + secret.length];
System.arraycopy(nonce, 0, plaintext, 0, nonce.length);
System.arraycopy(secret, 0, plaintext, nonce.length, secret.length);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8)));
byte[] encoding = cipher.doFinal(plaintext);
encCon = DatatypeConverter.printBase64Binary(encoding);
} catch (Exception ex) {
ex.printStackTrace();
}
return encCon;
}
public static String dec(String content, String key) {
String decCon = "";
try {
String IV = "12345678";
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
// Decode Base64
byte[] ciphertext = DatatypeConverter.parseBase64Binary(content);
// Decrypt
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8)));
byte[] message = cipher.doFinal(ciphertext);
decCon = new String(message,
cipher.getBlockSize(),
message.length - cipher.getBlockSize(),
StandardCharsets.UTF_8);
} catch (Exception ex) {
ex.printStackTrace();
}
return decCon;
}
Ps。您知道将秘密存储在字符串中是个坏主意吗?字符串是最终的,所以内容不能被删除。可以擦除字节数组(为简洁起见,本例中未执行)。您是否也知道您可以制作任何 Windows 程序来查看任何其他 Windows 程序的完整内存占用?
更新 2019-04-21 09:49 P.M。 UTC
在@MaartenBodewes 和@MarkJeronimus 指出了一些需要考虑的事项后,我正在更新答案以使其更正确。但是因为这个问题是关于实现的,而不是关于让它更安全的,所以这个和旧版本应该足以至少提供一点洞察力。同样,可以通过修改以下代码来实现更安全的解决方案。
更新日志
- 密钥派生
- 处理异常及其详细信息
- 对每个数据使用单个 SecureRandom 实例(iv[8 字节] 和 salt[32 字节])
- 待加密明文和待解密密文检查空值和空值
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.xml.bind.DatatypeConverter;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
public class Crypto {
private static final char[] tempKey = new char[] {'T', 'E', 'M', 'P', '_', 'G', 'E', 'N', '_', 'K', 'E', 'Y'};
private static final SecureRandom secureRandomForSalt = new SecureRandom();
private static final SecureRandom secureRandomForIV = new SecureRandom();
private static byte[] generateSalt() throws RuntimeException {
try{
byte[] saltBytes = new byte[32];
secureRandomForSalt.nextBytes(saltBytes);
return saltBytes;
}
catch(Exception ex){
ex.printStackTrace();
throw new RuntimeException("An error occurred in salt generation part. Reason: " + ex.getMessage());
}
}
public static String enc(String content) throws RuntimeException {
String encClassMethodNameForLogging = Crypto.class.getName() + ".enc" + " || ";
byte[] salt;
byte[] encodedTmpSecretKey;
SecretKeySpec keySpec;
Cipher cipher;
byte[] iv;
IvParameterSpec ivParameterSpec;
String finalEncResult;
if(content == null || content.trim().length() == 0) {
throw new RuntimeException("To be encrypted text is null or empty");
}
System.out.println("-- Encrypting -----------");
try {
salt = generateSalt();
}
catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in salt generation part. Reason: " + ex.getMessage());
}
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(Crypto.tempKey, salt, 65536, 256);
SecretKey tmpSecretKey = factory.generateSecret(spec);
encodedTmpSecretKey = tmpSecretKey.getEncoded();
System.out.println("-- Secret Key Derivation in Encryption: " + Base64.getEncoder().encodeToString(encodedTmpSecretKey));
}
catch (NoSuchAlgorithmException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
}
catch (InvalidKeySpecException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: Key length may not be correct");
}
catch (Exception ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage());
}
try {
keySpec = new SecretKeySpec(encodedTmpSecretKey, "Blowfish");
cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
}
catch (NoSuchAlgorithmException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
}
catch (NoSuchPaddingException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation: The particular padding mechanism is requested but is not available in the environment");
}
catch (Exception ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage());
}
try {
iv = new byte[cipher.getBlockSize()];
secureRandomForIV.nextBytes(iv);
ivParameterSpec = new IvParameterSpec(iv);
}
catch (Exception ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in iv creation part. Reason: " + ex.getMessage());
}
try {
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
byte[] encoding = cipher.doFinal(content.getBytes("UTF-8"));
String encCon = DatatypeConverter.printBase64Binary(encoding);
String ivStr = DatatypeConverter.printBase64Binary(iv);
String saltStr = DatatypeConverter.printBase64Binary(salt);
System.out.println("-- encCon : " + encCon);
System.out.println("-- iv : " + ivStr);
System.out.println("-- salt : " + saltStr);
finalEncResult = encCon + ":" + ivStr + ":" + saltStr;
System.out.println("-- finalEncRes : " + finalEncResult + "\n");
}
catch (InvalidKeyException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: Most probably you didn't download and copy 'Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files'");
}
catch (InvalidAlgorithmParameterException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: IV length may not be correct");
}
catch (IllegalBlockSizeException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: The length of data provided to a block cipher is incorrect, i.e., does not match the block size of the cipher");
}
catch (BadPaddingException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: A particular padding mechanism is expected for the input data but the data is not padded properly (Most probably wrong/corrupt key caused this)");
}
catch (UnsupportedEncodingException ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: The Character Encoding is not supported");
}
catch (Exception ex){
ex.printStackTrace();
throw new RuntimeException(encClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage());
}
return finalEncResult;
}
public static String dec(String encContent) throws RuntimeException {
String decClassMethodNameForLogging = Crypto.class.getName() + ".dec" + " || ";
String decCon;
byte[] salt;
byte[] encodedTmpSecretKey;
SecretKeySpec keySpec;
Cipher cipher;
byte[] iv;
if(encContent == null || encContent.trim().length() == 0) {
throw new RuntimeException("To be decrypted text is null or empty");
}
System.out.println("-- Decrypting -----------");
try {
salt = DatatypeConverter.parseBase64Binary(encContent.substring(encContent.lastIndexOf(":") + 1));
}
catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in salt retrieving part. Reason: " + ex.getMessage());
}
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(Crypto.tempKey, salt, 65536, 256);
SecretKey tmpSecretKey = factory.generateSecret(spec);
encodedTmpSecretKey = tmpSecretKey.getEncoded();
System.out.println("-- Secret Key Gathering in Decryption: " + Base64.getEncoder().encodeToString(encodedTmpSecretKey));
}
catch (NoSuchAlgorithmException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
}
catch (InvalidKeySpecException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage() + " - Explanation: Key length may not be correct");
}
catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in key derivation part. Reason: " + ex.getMessage());
}
try {
keySpec = new SecretKeySpec(encodedTmpSecretKey, "Blowfish");
cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
}
catch (NoSuchAlgorithmException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation: The particular cryptographic algorithm requested is not available in the environment");
}
catch (NoSuchPaddingException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage() + " - Explanation : The particular padding mechanism requested is not available in the environment");
}
catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in cipher instantiation part. Reason: " + ex.getMessage());
}
try {
iv = DatatypeConverter.parseBase64Binary(encContent.substring(encContent.indexOf(":") + 1, encContent.lastIndexOf(":")));
}
catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in iv creation part. Reason: " + ex.getMessage());
}
try {
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
byte[] decoding = cipher.doFinal(Base64.getDecoder().decode(encContent.substring(0, encContent.indexOf(":"))));
decCon = new String(decoding, "UTF-8");
System.out.println("-- decCon : " + decCon + "\n");
}
catch (InvalidKeyException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: Most probably you didn't download and copy 'Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files'");
}
catch (InvalidAlgorithmParameterException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: IV length may not be correct");
}
catch (IllegalBlockSizeException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage() + " - Explanation: The length of data provided to a block cipher is incorrect, i.e., does not match the block size of the cipher");
}
catch (BadPaddingException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: A particular padding mechanism is expected for the input data but the data is not padded properly (Most probably wrong/corrupt key caused this)");
}
catch (UnsupportedEncodingException ex){
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in encryption part. Reason: " + ex.getMessage() + " - Explanation: The Character Encoding is not supported");
}
catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(decClassMethodNameForLogging + "An error occurred in decryption part. Reason: " + ex.getMessage());
}
return decCon;
}
public static void main(String args[]) {
System.out.println("-- Original -------------");
String plainText = "hello world";
System.out.println("-- origWord : " + plainText + "\n");
String e = Crypto.enc(plainText);
String d = Crypto.dec(e);
System.out.println("-- Results --------------");
System.out.println("-- PlainText: " + plainText);
System.out.println("-- EncryptedText: " + e);
System.out.println("-- DecryptedText: " + d);
}
}
此外,可执行版本在下面;
https://www.jdoodle.com/a/19HT
原始答案
我看到书面评论满足您的需求,但我想分享以下解决方案作为代码示例以满足您的需求,也供将来参考;
** 使用随机 IV(针对 IV 大小给出了密码块大小,但也可以定义静态字节大小,例如“16 字节”)
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import javax.xml.bind.DatatypeConverter;
import java.security.SecureRandom;
import javax.crypto.spec.IvParameterSpec;
public class Crypto {
public static String enc(String content, String key) {
String encCon = "";
String ivStr = "";
try {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
byte[] iv = new byte[cipher.getBlockSize()];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
String secret = content;
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
byte[] encoding = cipher.doFinal(secret.getBytes("UTF-8"));
System.out.println("-- Encrypted -----------");
encCon = DatatypeConverter.printBase64Binary(encoding);
ivStr = DatatypeConverter.printBase64Binary(iv);
System.out.println("-- encCon : " + encCon);
System.out.println("-- iv : " + ivStr);
} catch (Exception ex) {
ex.printStackTrace();
}
return encCon + ":" + ivStr;
}
public static String dec(String encContent, String key) {
String decCon = "";
try {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
byte[] iv = DatatypeConverter.parseBase64Binary(encContent.substring(encContent.indexOf(":") + 1));
String secret = encContent.substring(0, encContent.indexOf(":"));
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
byte[] decoding = cipher.doFinal(Base64.getDecoder().decode(secret));
System.out.println("-- Decrypted -----------");
decCon = new String(decoding, "UTF-8");
System.out.println("-- decCon : " + decCon);
} catch (Exception ex) {
ex.printStackTrace();
}
return decCon;
}
public static void main(String args[]) {
String e = Crypto.enc("hello world", "key123");
String d = Crypto.dec(e, "key123");
}
}
注意:当然可以实现更安全的方案。以上解决方案仅供参考。