Question

I don't want to put unencrypted passwords in the application config file.

This question: Encrypting db password in application.conf has a great solution for the problem but it works only for Play 1.

Does anybody know a solution that works for Play 2.0? I am using anorm in Scala version of Play 2.0.2.

Was it helpful?

Solution 3

Thanks to discussion with Raffaele and following my own investigation of the code, it seems that Play 2.0 does not allow you to encrypt DB passwords.

If I missed something please let me know.

EDIT: One can work around the problem by using custom database driver in the following manner:

// Just redirect everything to the delegate
class DelegatingDriver(delegate: Driver) extends Driver
{
  def connect(url: String, info: Properties) = delegate.connect(url, info)
  def acceptsURL(url: String) = delegate.acceptsURL(url)
  def getPropertyInfo(url: String, info: Properties) = delegate.getPropertyInfo(url, info)
  def getMajorVersion = delegate.getMajorVersion
  def getMinorVersion = delegate.getMinorVersion
  def jdbcCompliant() = delegate.jdbcCompliant()
}

// Replace password in properties with the decrypted one
class MyDecryptingDriver extends DelegatingDriver(Class.forName("<my.original.Driver>").newInstance().asInstanceOf[Driver])
{
  override def connect(url: String, info: Properties)= {
    // copy Properties
    val overriddenProperties= clone(info)   
    // override password property with the decrypted value
    Option(info.getProperty("password")).foreach(value => overriddenProperties.setProperty("password", decryptPassword(value)))
    super.connect(url, overriddenProperties)
  }

  def clone(orig: Properties)= {
    val result= new Properties()
    orig.propertyNames().map(_.asInstanceOf[String]).foreach(pName => result.setProperty(pName, orig.getProperty(pName)))
    result
  }

  def decryptPassword(encrypted: String)= ...
}

then you replace application.conf/db..driver by my.com.MyDecrypting driver. Not perfect but works for me...

OTHER TIPS

All efforts are pointless. When we put hashed password in a database is because humans can retain passwords in their brains, and their brains are not readable. It's called asymmetric encryption.

The thing your are talking about is only possible with symmetric encryption: the program has the key at runtime, and uses this key to decrypt the db password. But what's the point in storing the db password encrypted with a key, and still having this key publicly available? (This is true for both java sources and compiled classes). A chain is only as strong as its weakest link.

When a machine has to connect to a db, it needs a password: we store this password in plain text because the program must use it as is, and no human input is required. All we can do to enforce security is to restrict the access to this plain text file, eventually protecting it with a password stored only in the admin's mind (BTW, more likely the admin will keep all of its passwords in a database, maybe with a master password). Note that things don't change if you use the mentioned Play plugin.

The only other thing that comes to my mind is a Play app which only connects to the db when the admin inputs the db password (but really this is only a thinking exercise)

I know it's a bit late but there's not newer discussions about this problema. I want to share the actual solution (Play v.2.5.X), as suggested in documentation, is now possible to override the GuiceApplicationLoader to configure the GuiceApplicationBuilder to process someway the initial configs.

In a new class modules/ApplicationLoaderConfig.scala:

import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import javax.xml.bind.DatatypeConverter

import play.api.inject.guice._
import play.api.{ApplicationLoader, Configuration}

class ApplicationLoaderConfig extends GuiceApplicationLoader() {

  override def builder(context: ApplicationLoader.Context): GuiceApplicationBuilder = {

    // Decrypt secrets
    val decryptedConfig = context.initialConfiguration ++
      Configuration("config.to.descrypt.1" -> decryptDES(context.initialConfiguration.getString("config.to.descrypt.1").get)) ++
      Configuration("config.to.descrypt.2" -> decryptDES(context.initialConfiguration.getString("config.to.descrypt.2").get))

    initialBuilder
      .in(context.environment)
      .loadConfig(decryptedConfig)
      .overrides(overrides(context): _*)
  }

  private def decryptDES(secret: String): String = {
    val key = "12345678"
    val skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "DES")

    val cipher = Cipher.getInstance("DES/ECB/PKCS5Padding")
    cipher.init(Cipher.DECRYPT_MODE, skeySpec)

    new String(cipher.doFinal(DatatypeConverter.parseBase64Binary(secret)))
  }
}

Also add to application.config:

play.application.loader = "modules.ApplicationLoaderConfig"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top