在配置文件中加密密码?[关闭]


130

我有一个程序可以从配置文件中读取服务器信息,并希望在该配置中加密密码,该密码可以由我的程序读取并解密。

要求:

  • 加密要存储在文件中的纯文本密码
  • 解密从我的程序从文件读取的加密密码

关于我将如何做到这一点的任何建议?我当时在考虑编写自己的算法,但我认为这绝对是不安全的。

Answers:


172

一种简单的方法是在Java中使用基于密码的加密。这使您可以使用密码来加密和解密文本。

这基本上意味着初始化一个javax.crypto.Cipherwith算法"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,则仍然存在相同的问题。基本上,您将需要在某个地方拥有一个主密码,而且很难保护。


3
感谢您的代码示例,这几乎是我最终完成该操作的方式。关于保护密码的密码,我遇到了同样的问题,我暂时不采用混淆方法,但是想出了一个可以接受的解决方案,谢谢您的建议。
Petey B

7
“或者,您可以在启动Java进程时将它作为系统属性提供(-DpropertyProtectionPassword = ...)”。注意,这将使在(GNU / Linux)/ UNIX上使用“ ps Fax”提取密码成为可能。
Ztyx

7
@Ben通常的做法是编码为Base64,以允许将结果值存储在基于文本文件或基于字符串的数据库列或类似的列中。
RB。

4
@ V.7不。MD5对于密码哈希绝对是不安全的,并且从未为此目的而设计。永远不要使用它。这些天,Argon2是最好的。见owasp.org/index.php/Password_Storage_Cheat_Sheetparagonie.com/blog/2016/02/how-safely-store-password-in-2016
金博尔罗宾逊

3
这样好多了。当然,安全的随机盐和40K(保守的低端)的迭代计数会更好,但是至少您已在注释中指出了这些内容,并且PBKDF2和AES / CBC无疑是改进。我认为通过更新答案来解决此问题非常好;我将删除警告。对您的评论进行投票,以使人们不会惊讶地找到更新的代码(他们可以查看编辑内容以查找我认为的旧代码)。清理旧评论也可能是一个好主意。
Maarten Bodewes '17

20

是的,绝对不要编写自己的算法。Java有很多加密API。

如果要安装的操作系统具有密钥库,则可以使用该密钥库来存储加密和解密配置或其他文件中的敏感数据所需的加密密钥。


4
+1使用KeyStore!如果您将密钥存储在Jar文件中,那就无非是混淆。
双面

2
如果所需要做的只是不要以明文形式存储密码,那么密钥库就显得过头了。
托尔比约恩Ravn的安徒生


16

我认为最好的方法是确保仅特定用户帐户可以访问您的配置文件(包含密码)。例如,您可能有一个特定appuser于应用程序的用户,只有受信任的人才能拥有密码(并且密码要被授予该密码su)。

这样,没有烦人的加密开销,并且您仍然拥有安全的密码。

编辑: 我假设您没有将应用程序配置导出到受信任的环境之外(鉴于该问题,我不确定这是否有任何意义)


4

很好地解决了主密码的问题-最好的方法是不要将密码存储在任何地方,应用程序应该为自己加密密码-以便只有它才能解密密码。因此,如果我使用的是.config文件,则将执行以下操作mySettings.config

cryptoTheseKeys = secretKey,另一个秘密

secretKey = unprotectedPasswordThatIputHere

anotherSecret = anotherPass

someKey = unprotectedSettingIdontCareAbout

因此,我会读入cryptoTheseKeys中提到的密钥,从上面应用Brodwalls示例,然后使用某种标记将它们写回到文件中(让我们说crypt:),以使应用程序知道不要这样做再次,输出将如下所示:

cryptoTheseKeys = secretKey,另一个秘密

secretKey = 地穴:ii4jfj304fjhfj934fouh938

anotherSecret = 地穴:jd48jofh48h

someKey = unprotectedSettingIdontCareAbout

只要确保将原件放在自己安全的地方即可...


2
是的,这是3年前的。为了避免使用主密钥,我最终使用了从内部CA发出的RSA密钥。通过使用机器硬件的指纹进行加密,可以保护对私钥的访问。
Petey B 2012年

我知道,听起来很扎实。很好
user1007231 2012年

@ user1007231-保存地点-“只要确保将原件保存在自己安全的地方...”?
nanosoft

@PeteyB-听不懂吗?您能指出一些启发我的链接吗?谢谢
nanosoft

@nanosoft-获取“宙斯盾安全密钥USB”并将其存储在文本文档中或钱包中的纸张上
user1007231 2015年

4

最重要的一点是,如果您的应用程序可以获取密码,那么可以访问该密码箱的黑客也可以获取密码!

解决此问题的唯一方法是,应用程序使用标准输入在控制台上请求“主密码”,然后使用该密码解密文件上存储的密码。当然,这完全不可能使应用程序在启动时随操作系统一起启动。

但是,即使有这种烦恼,如果黑客设法获得root用户访问权限(或什至只是在运行应用程序的用户时进行访问),他就可以转储内存并在那里找到密码。

要确保的事情是,不要让整个公司都可以访问生产服务器(从而不能访问密码),并确保不可能破解此服务器!


真正的解决方案是将您的私钥存储在其他地方,例如卡或HSM:en.wikipedia.org/wiki/Hardware_security_module
atom88



0

根据您需要配置文件的安全性或应用程序的可靠性,http://activemq.apache.org/encrypted-passwords.html可能是您的理想解决方案。

如果您不太担心密码被解密,那么使用bean来存储密码密钥进行配置可能非常简单。但是,如果需要更高的安全性,则可以使用密码设置环境变量,并在启动后将其删除。这样,您就不必担心应用程序/服务器崩溃,而不必担心应用程序不会自动重新启动。



-8

如果您使用的是Java 8,则可以通过替换内部Base64编码器和解码器来避免使用

return new BASE64Encoder().encode(bytes);

return Base64.getEncoder().encodeToString(bytes);

return new BASE64Decoder().decodeBuffer(property);

return Base64.getDecoder().decode(property);

请注意,由于解密方法存储在同一位置,因此该解决方案不能保护您的数据。这只会使破解变得更加困难。主要是避免打印错误并向所有人显示。


26
Base64不加密。
jwilleke 2015年

1
Base64不是加密,它是您可以提供的最糟糕的示例...很多人认为base64是一种加密算法,因此最好不要混淆它们...
robob

请注意,源文件顶部的encode()方法进行了实际的加密。但是,需要使用base64编码和解码来将字符串转换为字节,以便将该函数可以使用的东西传递给它(字节数组byte [])
atom88
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.