12

I would like to use Typesafe's Config in my project but I don't want any passwords in clear text in any file on the file system of any integration or production server. Also, I do not want to use environment variables to store clear text passwords.

Ideally, I would like a solution similar to the Jasypt EncryptablePropertyPlaceholderConfigurer available for Spring that would allow me to designate some property values as being encrypted and have the config system automatically decrypt them before handing the value down to the application. I'd like to use the JCE keystore to store the key and pass it into my app, but I'm also open to other tools that use a database to store keys.

Has anyone managed to get the Typesafe Config project to work this way?

Update: sourcedelica was completely correct to criticize the solution that relied on passing the key as an environment variable. I changed my question to request a solution that uses a more secure way of handling keys.

4

4 回答 4

8

Config您可以尝试像这样对类型安全类进行拉皮条:

object ConfigPimping{
  implicit class RichConfig(conf:Config){
    def getPasswordString(path:String, encryptKey:String):String = {
      val encrypted = conf.getString(path)
      val decrypted = ... //do decripy logic of your choice here
      decrypted
    }
  }  
}

object ConfigTest{
  import ConfigPimping._
  def main(args: Array[String]) {
    val conf = ConfigFactory.load()
    val myPass = conf.getPasswordString("myPass", "myDecryptKey")
  }
}

然后,只要RichConfig始终导入并可用,您就可以通过该getPasswordString函数访问您的自定义 decrpyt 密码逻辑。

于 2013-04-28T19:17:22.847 回答
7

如果您愿意将加密密钥作为环境变量传递,那么您可以将所有敏感属性作为环境变量传递,而不必担心直接通过 Typesafe 配置库使用加密。

例如:

my.sensitive = ${FOO_ENV}

您说您不想使用环境变量来存储明文密码,但如果您将加密密钥存储在环境变量中,它是等效的。

或者,您可以使用系统属性而不是环境变量。例如,在启动您的应用程序时,使用-Dmy.sensitive=xxx.

如果您最终将加密值添加到您的配置中,那么您可以使用一个包装类来进行解密。 我使用一个包装类来添加像optStringConfig 这样的方法。您可以添加一个方法,如decryptString.

有关保护生产环境中使用的密钥的讨论,请参阅我的问题:在生产环境中保护密码

于 2013-04-26T22:13:17.063 回答
4

我选择了 cmbaxter 建议的路径。我将示例代码放在这里是因为注释似乎不支持代码。

我在配置文件中添加了一些特殊的语法,所以如果我想在我的配置文件中放置一个加密的密码,我会这样做:

my-app-config{
  db-username="foo"
  db-password="ENC(9yYqENpuCkkL6gpoVh7a11l1IFgZ0LovX2MBF9jn3+VD0divs8TLRA==)"
}

请注意加密密码周围的“ENC()”包装。

然后我创建了一个配置工厂,它返回一个 DycryptingConfig 对象而不是类型安全配置:

import rzrelyea.config.crypto.DecryptingConfig;
import rzrelyea.config.crypto.KeyProvider;

public class ConfigFactory{

public static final Config makeDecryptingConfig(com.typesafe.config.Config config, KeyProvider keyProvider){
    return new DecryptingConfig(config, keyProvider);
}

}

这是 DecryptingConfig 的代码:

import java.security.Key;    
import static rzrelyea.config.Validators.require;

public class DecryptingConfig extends rzrelyae.config.Config {

    private final com.typesafe.config.Config config;
    private final Decryptor decryptor;

    public DecryptingConfig(com.typesafe.config.Config config, KeyProvider keyProvider){
        super(config);
        require(keyProvider, "You must initialize DecryptingConfig with a non-null keyProvider");
        this.config = config;
        final Key key = keyProvider.getKey();
        require(key, "KeyProvider must provide a non-null key");
        decryptor = new Decryptor(config.getString("crypto-algorithm"), key, config.getString("encoding-charset"));
    }

    @Override
    public String getString(String s) {
        final String raw = config.getString(s);
        if (EncryptedPropertyUtil.isEncryptedValue(raw)){
            return decryptor.decrypt(EncryptedPropertyUtil.getInnerEncryptedValue(raw));
        }
        return raw;
    }

显然,您需要实现自己的 rzrelyea.config.Config 对象、自己的 EncryptedPropertyUtil、自己的 Decryptor 和自己的 KeyProvider。我的 rzrelya.config.Config 实现将类型安全的配置对象作为构造函数参数,并将所有调用转发给它。里面有很多样板代码!但我认为将调用转发到接口而不是扩展 com.typesafe.config.impl.SimpleConfig 更好。你知道,更喜欢组合而不是继承,更喜欢代码而不是接口,而不是实现。您可以选择不同的路线。

于 2014-05-10T23:23:50.777 回答
0

冒着告诉你一些你已经知道的事情的风险......

  • 永远不要存储密码——存储并与哈希值进行比较
  • 使用 Bcrypt 进行密码哈希——它很慢,有利于防止暴力攻击
  • 使用盐——防止彩虹表式攻击
  • 使用 SSL (https) -- 防止密码被明文看到


这是一个使用Mindrot jBCrypt 库的示例:

  def PasswordHash(名称:字符串,密码:字符串,版本:Int = 1):字符串 = {
    如果(版本 == 2 && 假)
    {
      // 任何更改都应作为新版本进行并在此处添加
      “”
    }
    别的
    {
      导入 org.mindrot.jbcrypt.BCrypt // jbcrypt-0.3m.jar

      // Salt 将被包含在密码哈希中
      val salt = BCrypt.gensalt(12) // 默认为 10 轮,或 2**10 轮。更多的轮次更慢。

      BCrypt.hashpw((名称+密码),盐)
    }
  }

  def 验证密码(名称:字符串,密码:字符串,哈希:字符串,版本:Int = 1):布尔 = {
    如果(版本 == 1)
    {
      导入 org.mindrot.jbcrypt.BCrypt // jbcrypt-0.3m.jar

      BCrypt.checkpw((name + pwd), hash )
    }
    别的
      错误的
  }


> PasswordHash("johnny", "mypassword")
res4: String = $2a$12$dHIlTL14.t37Egf7DqG4qePE446GzzhIUAVuewMfkhfK0xxw3NW6i

> 验证密码(“约翰尼”、“我的密码”、“$2a$12$dHIlTL14.t37Egf7DqG4qePE446GzzhIUAVuewMfkhfK0xxw3NW6i”)
res5:布尔 = true

> 验证密码(“约翰尼”、“妈妈密码”、“$2a$12$dHIlTL14.t37Egf7DqG4qePE446GzzhIUAVuewMfkhfK0xxw3NW6i”)
res6:布尔 = 假

对于您要执行的操作,我假设您会在配置中存储“名称”、“密码哈希”和“哈希版本”。

于 2013-04-26T20:43:41.923 回答