如何使用加密密码在Scala中使用Typesafe的Config

时间:2013-04-26 16:33:04

标签: scala configuration playframework config akka

我想在我的项目中使用Typesafe's Config,但我不希望任何集成或生产服务器的文件系统上的任何文件中都有明文密码。另外,我不想使用environment variables to store clear text passwords

理想情况下,我想要一个类似于Spring可用的Jasypt EncryptablePropertyPlaceholderConfigurer的解决方案,它允许我将一些属性值指定为加密,并让配置系统在处理值之前自动解密它们到应用程序。我想使用JCE密钥库来存储密钥并将其传递到我的应用程序中,但我也对使用数据库来存储密钥的其他工具持开放态度。

有没有人设法让Typesafe Config项目以这种方式工作?

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

4 个答案:

答案 0 :(得分: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逻辑。

答案 1 :(得分:7)

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

例如:

my.sensitive = ${FOO_ENV}

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

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

如果您最终将加密值添加到配置中,那么您可以使用包装类来进行解密。 I use a wrapper classoptString等方法添加到Config中。您可以添加decryptString等方法。

有关保护要在制作中使用的密钥的讨论,请参阅我的问题:Securing passwords in production environment

答案 2 :(得分:4)

我选择了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);
}

}

这里是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。您知道,更喜欢组合继承和代码到接口,而不是实现。您可以选择不同的路线。

答案 3 :(得分:2)

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

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


以下是使用Mindrot jBCrypt library

的示例
  def PasswordHash( name:String, pwd:String, version:Int = 1 ) : String = {
    if( version == 2 && false )
    {
      // ANY CHANGES SHOULD BE MADE AS A NEW VERSION AND ADDED HERE
      ""
    }
    else
    {
      import org.mindrot.jbcrypt.BCrypt      // jbcrypt-0.3m.jar

      // Salt will be incorporated in the password hash
      val salt = BCrypt.gensalt(12) // Default is 10, or 2**10 rounds.  More rounds is slower.

      BCrypt.hashpw( (name + pwd), salt )
    }
  }

  def VerifyPassword( name:String, pwd:String, hash:String, version:Int = 1 ) : Boolean = {
    if( version == 1 )
    {
      import org.mindrot.jbcrypt.BCrypt      // jbcrypt-0.3m.jar

      BCrypt.checkpw( (name + pwd), hash )
    }
    else
      false
  }


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

<强>&GT; VerifyPassword(“johnny”,“mypassword”,“$ 2a $ 12 $ dHIlTL14.t37Egf7DqG4qePE446GzzhIUAVuewMfkhfK0xxw3NW6i”)
res5:Boolean = true

<强>&GT; VerifyPassword(“johnny”,“mommiespassword”,“$ 2a $ 12 $ dHIlTL14.t37Egf7DqG4qePE446GzzhIUAVuewMfkhfK0xxw3NW6i”)
res6:Boolean = false

对于您要执行的操作,我假设您将在配置中存储“name”,“password hash”和“hash version”。