Answers:
一种简单的方法是在Java中使用基于密码的加密。这使您可以使用密码来加密和解密文本。
这基本上意味着初始化一个javax.crypto.Cipher
with算法"AES/CBC/PKCS5Padding"
并从javax.crypto.SecretKeyFactory
该"PBKDF2WithHmacSHA512"
算法获取密钥。
这是一个代码示例(已更新以替换不太安全的基于MD5的变体):
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class ProtectedConfigFile {
public static void main(String[] args) throws Exception {
String password = System.getProperty("password");
if (password == null) {
throw new IllegalArgumentException("Run with -Dpassword=<password>");
}
// The salt (probably) can be stored along with the encrypted data
byte[] salt = new String("12345678").getBytes();
// Decreasing this speeds down startup time and can be useful during testing, but it also makes it easier for brute force attackers
int iterationCount = 40000;
// Other values give me java.security.InvalidKeyException: Illegal key size or default parameters
int keyLength = 128;
SecretKeySpec key = createSecretKey(password.toCharArray(),
salt, iterationCount, keyLength);
String originalPassword = "secret";
System.out.println("Original password: " + originalPassword);
String encryptedPassword = encrypt(originalPassword, key);
System.out.println("Encrypted password: " + encryptedPassword);
String decryptedPassword = decrypt(encryptedPassword, key);
System.out.println("Decrypted password: " + decryptedPassword);
}
private static SecretKeySpec createSecretKey(char[] password, byte[] salt, int iterationCount, int keyLength) throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterationCount, keyLength);
SecretKey keyTmp = keyFactory.generateSecret(keySpec);
return new SecretKeySpec(keyTmp.getEncoded(), "AES");
}
private static String encrypt(String property, SecretKeySpec key) throws GeneralSecurityException, UnsupportedEncodingException {
Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
pbeCipher.init(Cipher.ENCRYPT_MODE, key);
AlgorithmParameters parameters = pbeCipher.getParameters();
IvParameterSpec ivParameterSpec = parameters.getParameterSpec(IvParameterSpec.class);
byte[] cryptoText = pbeCipher.doFinal(property.getBytes("UTF-8"));
byte[] iv = ivParameterSpec.getIV();
return base64Encode(iv) + ":" + base64Encode(cryptoText);
}
private static String base64Encode(byte[] bytes) {
return Base64.getEncoder().encodeToString(bytes);
}
private static String decrypt(String string, SecretKeySpec key) throws GeneralSecurityException, IOException {
String iv = string.split(":")[0];
String property = string.split(":")[1];
Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
pbeCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(base64Decode(iv)));
return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
}
private static byte[] base64Decode(String property) throws IOException {
return Base64.getDecoder().decode(property);
}
}
仍然存在一个问题:您应该在哪里存储用于加密密码的密码?您可以将其存储在源文件中并对其进行模糊处理,但是再次找到它并不难。另外,您可以在启动Java进程(-DpropertyProtectionPassword=...
)时将其作为系统属性提供。
如果您使用同样受密码保护的KeyStore,则仍然存在相同的问题。基本上,您将需要在某个地方拥有一个主密码,而且很难保护。
是的,绝对不要编写自己的算法。Java有很多加密API。
如果要安装的操作系统具有密钥库,则可以使用该密钥库来存储加密和解密配置或其他文件中的敏感数据所需的加密密钥。
我认为最好的方法是确保仅特定用户帐户可以访问您的配置文件(包含密码)。例如,您可能有一个特定appuser
于应用程序的用户,只有受信任的人才能拥有密码(并且密码要被授予该密码su
)。
这样,没有烦人的加密开销,并且您仍然拥有安全的密码。
编辑: 我假设您没有将应用程序配置导出到受信任的环境之外(鉴于该问题,我不确定这是否有任何意义)
很好地解决了主密码的问题-最好的方法是不要将密码存储在任何地方,应用程序应该为自己加密密码-以便只有它才能解密密码。因此,如果我使用的是.config文件,则将执行以下操作mySettings.config:
cryptoTheseKeys = secretKey,另一个秘密
secretKey = unprotectedPasswordThatIputHere
anotherSecret = anotherPass
someKey = unprotectedSettingIdontCareAbout
因此,我会读入cryptoTheseKeys中提到的密钥,从上面应用Brodwalls示例,然后使用某种标记将它们写回到文件中(让我们说crypt:),以使应用程序知道不要这样做再次,输出将如下所示:
cryptoTheseKeys = secretKey,另一个秘密
secretKey = 地穴:ii4jfj304fjhfj934fouh938
anotherSecret = 地穴:jd48jofh48h
someKey = unprotectedSettingIdontCareAbout
只要确保将原件放在自己安全的地方即可...
最重要的一点是,如果您的应用程序可以获取密码,那么可以访问该密码箱的黑客也可以获取密码!
解决此问题的唯一方法是,应用程序使用标准输入在控制台上请求“主密码”,然后使用该密码解密文件上存储的密码。当然,这完全不可能使应用程序在启动时随操作系统一起启动。
但是,即使有这种烦恼,如果黑客设法获得root用户访问权限(或什至只是在运行应用程序的用户时进行访问),他就可以转储内存并在那里找到密码。
要确保的事情是,不要让整个公司都可以访问生产服务器(从而不能访问密码),并确保不可能破解此服务器!
尝试使用ESAPI加密方法。它易于配置,您也可以轻松更改密钥。
http://owasp-esapi-java.googlecode.com/svn/trunk_doc/latest/org/owasp/esapi/Encryptor.html
您
1)加密2)解密3)签名4)取消签名5)散列6)基于时间的签名以及仅使用一个库的更多信息。
查看Jetty中可用于在配置文件中存储密码(或哈希)的内容,并考虑OBF编码是否对您有用。然后在源代码中看到它是如何完成的。
http://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html
根据您需要配置文件的安全性或应用程序的可靠性,http://activemq.apache.org/encrypted-passwords.html可能是您的理想解决方案。
如果您不太担心密码被解密,那么使用bean来存储密码密钥进行配置可能非常简单。但是,如果需要更高的安全性,则可以使用密码设置环境变量,并在启动后将其删除。这样,您就不必担心应用程序/服务器崩溃,而不必担心应用程序不会自动重新启动。
如果您使用的是Java 8,则可以通过替换内部Base64编码器和解码器来避免使用
return new BASE64Encoder().encode(bytes);
与
return Base64.getEncoder().encodeToString(bytes);
和
return new BASE64Decoder().decodeBuffer(property);
与
return Base64.getDecoder().decode(property);
请注意,由于解密方法存储在同一位置,因此该解决方案不能保护您的数据。这只会使破解变得更加困难。主要是避免打印错误并向所有人显示。