如何使用Typesafe';使用加密密码在Scala中配置

如何使用Typesafe';使用加密密码在Scala中配置,scala,configuration,playframework,config,akka,Scala,Configuration,Playframework,Config,Akka,我想在我的项目中使用,但我不想在任何集成或生产服务器的文件系统的任何文件中使用任何明文密码。还有,我不想用 理想情况下,我希望有一个类似于Spring的解决方案,它允许我将一些属性值指定为加密的,并让配置系统在将值传递给应用程序之前自动解密它们。我希望使用JCE密钥库来存储密钥并将其传递到我的应用程序中,但我也欢迎使用数据库来存储密钥的其他工具 有人设法让Typesafe配置项目以这种方式工作吗 更新:sourcedelica完全正确地批评了依赖于将密钥作为环境变量传递的解决方案。我改变了我的问

我想在我的项目中使用,但我不想在任何集成或生产服务器的文件系统的任何文件中使用任何明文密码。还有,我不想用

理想情况下,我希望有一个类似于Spring的解决方案,它允许我将一些属性值指定为加密的,并让配置系统在将值传递给应用程序之前自动解密它们。我希望使用JCE密钥库来存储密钥并将其传递到我的应用程序中,但我也欢迎使用数据库来存储密钥的其他工具

有人设法让Typesafe配置项目以这种方式工作吗


更新:sourcedelica完全正确地批评了依赖于将密钥作为环境变量传递的解决方案。我改变了我的问题,要求一个使用更安全的方式处理密钥的解决方案。

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

  • 永远不要存储密码——而是存储并与哈希进行比较
  • 使用Bcrypt进行密码散列——它的速度很慢,有利于防止暴力攻击
  • 使用盐——防止彩虹桌式攻击
  • 使用SSL(https)--防止在清除中看到密码

下面是一个使用以下命令的示例:

def PasswordHash(名称:String,pwd:String,版本:Int=1):String={ 如果(版本==2&&false) { //任何更改都应作为新版本进行,并添加到此处 "" } 其他的 { 导入org.mindrot.jbcrypt.BCrypt//jbcrypt-0.3m.jar //Salt将包含在密码散列中 val salt=BCrypt.gensalt(12)//默认值为10,或2**10轮。轮数越多,速度越慢。 BCrypt.hashpw((名称+pwd),盐) } } def VerifyPassword(名称:String,pwd:String,哈希:String,版本:Int=1):布尔={ 如果(版本==1) { 导入org.mindrot.jbcrypt.BCrypt//jbcrypt-0.3m.jar BCrypt.checkpw((名称+pwd),散列) } 其他的 假的 }
>密码哈希(“约翰尼”,“我的密码”)
res4:String=$2a$12$dHIlTL14.t37Egf7DqG4qePE446GzzhIUAVuewMfkhfK0xxw3NW6i

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

>验证密码(“约翰尼”,“妈妈密码”,“2a$12$dHIlTL14.T37EGF7DQG4QEPE446GZHIUAVUEWMFKHFK0xXW3NW6i”)
res6:Boolean=false


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

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

例如:

my.sensitive = ${FOO_ENV}
您说过您不想使用环境变量来存储明文密码,但如果您将加密密钥存储在环境变量中,则它是等效的

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

如果您最终将加密值获取到配置中,那么您可以使用一个包装类来完成解密。将
optString
等方法添加到配置中。您可以添加类似于
decryptString
的方法


有关保护生产中使用的密钥的讨论,请参见我的问题:。

您可以尝试按如下方式对typesafe
Config
类进行PIMP:

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
函数访问自定义密码解密逻辑。

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

我在配置文件中添加了一些特殊语法,因此如果我想在配置文件中输入加密密码,我会这样做:

my-app-config{
  db-username="foo"
  db-password="ENC(9yYqENpuCkkL6gpoVh7a11l1IFgZ0LovX2MBF9jn3+VD0divs8TLRA==)"
}
请注意加密密码周围的“ENC()”包装

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

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);
}
}

下面是解密配置的代码:

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、自己的解密程序和自己的密钥提供程序。我的rzrelya.config.config实现将typesafe配置对象作为构造函数参数,并将所有调用转发给它。里面有很多锅炉铭牌代码!但我认为最好将调用转发到接口,而不是扩展com.typesafe.config.impl.SimpleConfig。你知道,比起继承,你更喜欢组合,比起接口,你更喜欢代码,而不是实现。您可以选择不同的路由。

我想OP希望存储DB连接设置或类似的密码,而不是存储访问控制。在这种情况下,我们必须确切知道密码是什么。Brian Hsu是正确的。我需要能够解密存储在配置文件中的密码,以便我能够通过数据库进行身份验证。javax.crypto包将具有加密/解密例程。您可能需要检查数据库是否支持“可信连接”。这将是一种更安全的连接DB的方式,并且不需要在配置中存储密码。我对您的回答投了赞成票,因为这是对我问题的有效批评。然而,这并不能完全回答我的问题,我的问题是如何在Typesafe的配置工具中使用加密密钥。我会接受你的回答,并在我自己的回答中详细说明我所做的事情
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;
    }