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

Posted

技术标签:

【中文标题】如何在 Scala 中使用 Typesafe 的 Config 和加密密码【英文标题】:How to use Typesafe's Config in Scala with encrypted passwords 【发布时间】:2013-04-20 21:45:49 【问题描述】:

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

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

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

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

【问题讨论】:

【参考方案1】:

您可以尝试像这样对 typesafe 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 函数访问您自定义的密码解密逻辑。

【讨论】:

我最终写了一个非常像这样的工具。我会接受您的回答,并在我自己的回答中详细说明我所做的事情。 那么,rzeleyea 你能提供更多细节吗? :)【参考方案2】:

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

例如:

my.sensitive = $FOO_ENV

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

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

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

有关保护生产中使用的密钥的讨论,请参阅我的问题:Securing passwords in production environment。

【讨论】:

我赞成您的回复,因为这是对我的问题的有效批评。但是,这并不完全是我的问题的答案,它询问如何通过 Typesafe 的配置工具使用加密密钥。 还不错 - 添加了更多评论【参考方案3】:

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

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

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

【讨论】:

我是 typesafe 的新手...我想查看带有详细信息的示例工作代码。这将有助于了解完整的加载过程【参考方案4】:

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

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

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

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: 字符串 = $2a$12$dHIlTL14.t37Egf7DqG4qePE446GzzhIUAVuewMfkhfK0xxw3NW6i

> VerifyPassword("johnny", "mypassword", "$2a$12$dHIlTL14.t37Egf7DqG4qePE446GzzhIUAVuewMfkhfK0xxw3NW6i") res5: Boolean = true

> 验证密码(“johnny”、“mommiespassword”、“$2a$12$dHIlTL14.t37Egf7DqG4qePE446GzzhIUAVuewMfkhfK0xxw3NW6i”) res6: Boolean = false

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

【讨论】:

我猜 OP 想为数据库连接设置或类似的东西存储密码,而不是为了访问控制而存储它。在这种情况下,我们必须确切地知道密码是什么。 Brian Hsu 是正确的。我需要能够解密存储在配置文件中的密码,以便我可以通过数据库进行身份验证。 javax.crypto 包将具有加密/解密例程。您可能需要检查您的数据库是否支持“可信连接”。这将是一种更安全的连接数据库的方式,并且不需要在配置中存储密码。

以上是关于如何在 Scala 中使用 Typesafe 的 Config 和加密密码的主要内容,如果未能解决你的问题,请参考以下文章

热点Scala 2.12将只支持Java 8

无法使用 scala 的 sbt 从存储库中获取插件

电子书 Scala程序设计 第2版.pdf

scala 参考 sbt 项目版本

使用 shadowJar 和 Scala 依赖项时如何修复丢失的 conf 文件?

假期学习进度十三