diff --git a/silk-core/src/main/scala/org/silkframework/runtime/plugin/ParameterType.scala b/silk-core/src/main/scala/org/silkframework/runtime/plugin/ParameterType.scala index 6290158d8f..d63a6bb2e1 100644 --- a/silk-core/src/main/scala/org/silkframework/runtime/plugin/ParameterType.scala +++ b/silk-core/src/main/scala/org/silkframework/runtime/plugin/ParameterType.scala @@ -3,8 +3,10 @@ package org.silkframework.runtime.plugin import java.lang.reflect.{ParameterizedType, Type} import java.net.{URLDecoder, URLEncoder} import java.security.InvalidKeyException -import java.util.logging.Logger +import java.security.spec.InvalidKeySpecException +import java.util.logging.{Level, Logger} +import javax.crypto.SecretKey import org.silkframework.config.{DefaultConfig, Prefixes, ProjectReference, TaskReference} import org.silkframework.dataset.rdf.SparqlEndpointDatasetParameter import org.silkframework.execution.AbortExecutionException @@ -14,7 +16,7 @@ import org.silkframework.util.{AesCrypto, Identifier, Uri} import scala.language.existentials import scala.reflect.ClassTag -import scala.util.Try +import scala.util.{Failure, Success, Try} /** * Represents a plugin parameter type and provides serialization. @@ -361,10 +363,26 @@ object ParameterType { override def description: String = "A password string." - lazy val key: String = { - Try(DefaultConfig.instance().getString(CONFIG_KEY)).getOrElse { - log.warning(s"No valid value set for $CONFIG_KEY, using insecure default key!") - "1234567890123456" + /** + * The configured key. Failure, if no key has been configured or the key is invalid. + */ + lazy val configuredKey: Try[SecretKey] = { + for { + password <- Try(DefaultConfig.instance().getString(CONFIG_KEY)) + checkedPassword <- checkPassword(password) + key <- Try(AesCrypto.generateKey(checkedPassword)) + } yield key + } + + /** + * The key used for encryption. A default key will be used, if no key has been configured. + */ + lazy val key: SecretKey = { + configuredKey match { + case Success(k) => k + case Failure(ex) => + log.log(Level.WARNING, s"No valid key set for $CONFIG_KEY, using insecure default key!", ex) + AesCrypto.generateKey("1234567890123456") } } @@ -374,16 +392,24 @@ object ParameterType { } else if (str.startsWith(PREAMBLE)) { str.stripPrefix(PREAMBLE) } else { - try { - AesCrypto.encrypt(key, str) - } catch { - case ex: InvalidKeyException => - throw new RuntimeException(s"The password parameter encryption key is invalid. Value for " + - s"${PasswordParameterType.CONFIG_KEY} needs to be a character string of length 16.", ex) - } + AesCrypto.encrypt(key, str) } PasswordParameter(encryptedPassword) } + + /** + * Makes sure that the password has been set and is longer than 16 characters. + */ + private def checkPassword(password: String): Try[String] = { + if(password == "changemechangeme") { + Failure(new InvalidKeySpecException("Default key is not overridden")) + } else if(password.length < 16) { + Failure(new InvalidKeySpecException("Key must be at least 16 characters long")) + } else { + Success(password) + } + } + } object SparqlEndpointDatasetParameterType extends ParameterType[SparqlEndpointDatasetParameter] { diff --git a/silk-core/src/main/scala/org/silkframework/util/AesCrypto.scala b/silk-core/src/main/scala/org/silkframework/util/AesCrypto.scala index c2c34b2d34..d876e3f9d6 100644 --- a/silk-core/src/main/scala/org/silkframework/util/AesCrypto.scala +++ b/silk-core/src/main/scala/org/silkframework/util/AesCrypto.scala @@ -1,27 +1,43 @@ package org.silkframework.util import java.util.Base64 + import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec +import javax.crypto.SecretKey +import javax.crypto.SecretKeyFactory +import javax.crypto.spec.PBEKeySpec +import java.security.spec.InvalidKeySpecException import javax.crypto.spec.SecretKeySpec object AesCrypto { - final val RANDOM_INIT_VECTOR = "FKJrJQWZ9DEW6KOv" - def encrypt(key: String, value: String): String = { - val iv = new IvParameterSpec(RANDOM_INIT_VECTOR.getBytes("UTF-8")) - val skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES") + private final val INIT_VECTOR = new IvParameterSpec("FKJrJQWZ9DEW6KOv".getBytes("UTF-8")) + + private final val SALT = Array[Byte](9, -119, -42, 5, -63, 102, -11, -104, 66, -17, 112, 55, 44, 73, 32, -12, -103, 88, 14, -44, 18, -46, 30, -6, -55, 28, -54, 12, 39, 110, 63, 125) + + /** + * Generates a secret key from a password. + * + * @throws InvalidKeySpecException If no key could be generated from the password. + */ + def generateKey(password: String): SecretKey = { + val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") + val spec = new PBEKeySpec(password.toCharArray, SALT, 65536, 256) + val secret = factory.generateSecret(spec) + new SecretKeySpec(secret.getEncoded, "AES") + } + + def encrypt(key: SecretKey, value: String): String = { val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING") - cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv) + cipher.init(Cipher.ENCRYPT_MODE, key, INIT_VECTOR) val encrypted = cipher.doFinal(value.getBytes) Base64.getEncoder.encodeToString(encrypted) } - def decrypt(key: String, encrypted: String): String = { - val iv = new IvParameterSpec(RANDOM_INIT_VECTOR.getBytes("UTF-8")) - val skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES") + def decrypt(key: SecretKey, encrypted: String): String = { val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING") - cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv) + cipher.init(Cipher.DECRYPT_MODE, key, INIT_VECTOR) val original = cipher.doFinal(Base64.getDecoder.decode(encrypted)) new String(original) }