我有一个从配置文件中读取服务器信息的程序,并希望加密该配置中的密码,以便我的程序能够读取并解密。
要求:
你有什么建议吗?我想写自己的算法,但我觉得它会非常不安全。
是的,绝对不要编写自己的算法。 Java 有很多加密 API。
如果您正在安装的操作系统上有一个密钥存储库,那么您可以使用它来存储您的加密密钥,您将需要加密和解密配置或其他文件中的敏感数据。
看看 Jasypt,它是一个提供基本加密功能的库,只需要最少的努力。
我认为最好的方法是确保您的配置文件(包含您的密码)是 只有特定的用户帐户才能访问。例如,您可能有一个特定于应用程序的用户 appuser,该用户只有受信任的人才有密码(而且他们的 su也有密码)。
appuser
su
这样,就不会有烦人的加密开销,而且您仍然有一个安全的密码。
编辑: 我假设您没有将应用程序配置导出到受信任的环境之外(考虑到这个问题,我不确定这样做是否有意义)
查看 Jetty 中有哪些可用于在配置文件中存储密码(或哈希)的内容,并考虑 OBF 编码是否对您有用。然后在源代码中查看它是如何完成的。
Http://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html
一个简单的方法是在 Java 中使用基于密码的加密。这允许您使用密码对文本进行加密和解密。
这基本上意味着使用算法 "AES/CBC/PKCS5Padding"初始化 javax.crypto.Cipher,并使用 "PBKDF2WithHmacSHA512"算法从 javax.crypto.SecretKeyFactory获得一个密钥。
"AES/CBC/PKCS5Padding"
javax.crypto.Cipher
"PBKDF2WithHmacSHA512"
javax.crypto.SecretKeyFactory
下面是一个代码示例(更新为替换安全性较差的基于 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=...)时将其作为系统属性提供。
-DpropertyProtectionPassword=...
如果使用 KeyStore,则仍然存在同样的问题,KeyStore 也受到密码的保护。基本上,您将需要有一个主密码的地方,这是非常难以保护。
为了很好地解决主密码的问题——最好的方法是不要将密码存储在任何地方,应用程序应该为自己加密密码——这样只有它才能解密密码。所以如果我用一个。配置文件我将执行以下操作,配置文件:
加密这些密钥 = 保密密钥,另一个秘密 Secret Key = unprotected tedPasswordThatIputHere 另一个秘密 = 另一个通行证 关于
加密这些密钥 = 保密密钥,另一个秘密
Secret Key = unprotected tedPasswordThatIputHere
另一个秘密 = 另一个通行证
关于
这样我就可以读出 加密 TheseKeys,将上面的 Brodwall 示例应用于它们,然后 用某种标记(比方说 地窖:)将它们写回文件,让应用程序知道不要再这样做,输出如下:
加密这些密钥 = 保密密钥,另一个秘密 Secret Key = 地下室: ii4jfj304fjhfj934fuh938 另一个秘密 = 地窖: jd48jofh48h 关于
Secret Key = 地下室: ii4jfj304fjhfj934fuh938
另一个秘密 = 地窖: jd48jofh48h
只要确保把原件放在你自己的安全地方。
尝试使用 ESAPI 加密方法。它很容易配置,你也可以很容易地改变你的密钥。
Http://owasp-esapi-java.googlecode.com/svn/trunk_doc/latest/org/owasp/esapi/encryptor.html
你
1)加密 2)解密 3)签署 4)取消签署 5)散列 6)基于时间的签名和更多的只有一个库。
最重要的一点,房间里的大象和所有这一切,是 如果你的应用程序可以得到的密码,那么黑客可以访问的盒子也可以得到它!
解决这个问题的唯一方法是,应用程序使用 Standard Input 在控制台上请求“主密码”,然后使用该方法对存储在文件中的密码进行解密。当然,这完全使得应用程序在启动时不可能与操作系统一起启动。
然而,即使有这种程度的烦恼,如果一个黑客设法获得 root 访问权限(甚至只是作为运行应用程序的用户进行访问) ,他可以转储内存并在那里找到密码。
要确保的是,不要让整个公司都访问生产服务器(从而访问密码) ,并确保不可能破解这个盒子!
取决于您需要配置文件的安全性或应用程序的可靠性,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);
注意,此解决方案不保护数据,因为解密方法存储在相同的位置。这只会让它更难被打破。主要是避免打印它,并显示给大家错误。