A Scala 3, functional, type-safe and memory-safe library to handle secret values
Secret
library does its best to avoid leaking information in memory and in the code, BUT an attack is always possible,
and I don't give any certainties or guarantees about security by using this library, you use it at your own risk. The code is open-sourced; you can check the implementation and take your
decision consciously. I'll do my best to improve the security and documentation of this project.
Please, drop a ⭐️ if you are interested in this project and you want to support it.
Note
Scala 3 only, Scala 2 is not supported.
libraryDependencies += "com.github.geirolz" %% "secret" % "0.0.11"
import com.geirolz.secret.*
import scala.util.Try
val reusableSecret: Secret[String] = Secret("password") // reusable secret
// reusableSecret: Secret[String] = ** SECRET **
val oneShotSecret: Secret.OneShot[String] = Secret.oneShot("password") // one shot secret
// oneShotSecret: OneShotSecret[String] = ** SECRET **
val deferredSecret: Secret.Deferred[Try, String] = Secret.deferred(Try("password")) // deferred secret
// deferredSecret: DeferredSecret[[T >: Nothing <: Any] => Try[T], String] = com.geirolz.secret.DeferredSecret$$anon$1@2ed6f149
By default the value is obfuscated when creating the Secret
instance using the implicit SecretStrategy
which, by default, transform the value into a xor-ed
ByteBuffer
which store bytes outside the JVM using direct memory access.
The obfuscated value is de-obfuscated using the implicit SecretStrategy
instance every time use
, and derived method, are invoked which returns the original
value converting the bytes back to T
re-applying the xor formula.
While obfuscating the value prevents or at least makes it harder to read the value from the memory, Secret
class API are designed to avoid leaking
information in other ways. Preventing developers to improperly use the secret value ( logging, etc...).
Example
import com.geirolz.secret.*
import scala.util.Try
case class Database(password: String)
val secretString: Secret[String] = Secret("password")
// secretString: Secret[String] = ** SECRET **
val database: Either[SecretDestroyed, Database] = secretString.euseAndDestroy(password => Database(password))
// database: Either[SecretDestroyed, Database] = Right(
// value = Database(password = "password")
// )
// if you try to access the secret value once used, you'll get an error
secretString.euse(println(_))
// res2: Either[SecretDestroyed, Unit] = Left(
// value = SecretDestroyed(destructionLocation = README.md:50:113)
// )
These integrations aim to enhance the functionality and capabilities of Secret
type making it easier to use in different contexts.
libraryDependencies += "com.github.geirolz" %% "secret-effect" % "0.0.11"
import com.geirolz.secret.*
import cats.effect.{IO, Resource}
val s: Secret[String] = Secret("password")
// !!!! this will not destroy the secret because it uses a duplicated one !!!
val res: Resource[IO, String] = s.resource[IO]
// this will destroy the secret because it uses the original one
val res2: Resource[IO, String] = s.resourceDestroy[IO]
// this will destroy the secret because it uses the original one
val res3: Resource[IO, String] = Secret.resource[IO, String]("password")
libraryDependencies += "com.github.geirolz" %% "secret-pureconfig" % "0.0.11"
Just provides the ConfigReader
instance for Secret[T]
type.
There must be an ConfigReader[T]
and a SecretStrategy[T]
instances implicitly in the scope.
import com.geirolz.secret.pureconfig.given
libraryDependencies += "com.github.geirolz" %% "secret-typesafe-config" % "0.0.11"
import com.geirolz.secret.typesafe.config.given
libraryDependencies += "com.github.geirolz" %% "secret-ciris" % "0.0.11"
import com.geirolz.secret.ciris.given
Provides the json Decoder
instance for Secret[T]
and OneShotSecret[T]
type.
libraryDependencies += "com.github.geirolz" %% "secret-circe" % "0.0.11"
import com.geirolz.secret.circe.given
Provides the xml Decoder
instance for Secret[T]
and OneShotSecret[T]
type.
libraryDependencies += "com.github.geirolz" %% "secret-cats-xml" % "0.0.11"
import com.geirolz.secret.catsxml.given
If you are using Secret in your company, please let me know and I'll add it to the list! It means a lot to me.
If you want to use a custom obfuscation strategy for a specific type you can implement a custom SecretStrategy
and provide an implicit instance of it during the secret creation.
If you think that your strategy can be useful for other people, please consider to contribute to the project and add it to the library.
import com.geirolz.secret.strategy.SecretStrategy
import com.geirolz.secret.strategy.SecretStrategy.{DeObfuscator, Obfuscator}
import com.geirolz.secret.util.KeyValueBuffer
import com.geirolz.secret.Secret
given SecretStrategy[String] = SecretStrategy[String](
Obfuscator.of[String](_ => KeyValueBuffer.directEmpty(0)),
DeObfuscator.of[String](_ => "CUSTOM"),
)
Secret("my_password").euse(secret => secret)
// res10: Either[SecretDestroyed, String] = Right(value = "CUSTOM")
If you want to use a custom obfuscation strategy algebra you can implement a custom SecretStrategyAlgebra
and provide an implicit SecretStrategyFactory
instance built on it during the secret creation.
If you think that your strategy can be useful for other people, please consider to contribute to the project and add it to the library.
import com.geirolz.secret.strategy.SecretStrategy.{DeObfuscator, Obfuscator}
import com.geirolz.secret.strategy.{SecretStrategy, SecretStrategyAlgebra}
import com.geirolz.secret.util.KeyValueBuffer
import com.geirolz.secret.{PlainValueBuffer, Secret}
import java.nio.ByteBuffer
// build the custom algebra
val myCustomAlgebra = new SecretStrategyAlgebra:
final def obfuscator[P](f: P => PlainValueBuffer): Obfuscator[P] =
Obfuscator.of { (plain: P) => KeyValueBuffer(ByteBuffer.allocateDirect(0), f(plain)) }
final def deObfuscator[P](f: PlainValueBuffer => P): DeObfuscator[P] =
DeObfuscator.of { bufferTuple => f(bufferTuple.roObfuscatedBuffer) }
// myCustomAlgebra: SecretStrategyAlgebra = repl.MdocSession$MdocApp11$$anon$12@6512f34b
// build factory based on the algebra
val myCustomStrategyFactory = myCustomAlgebra.newFactory
// myCustomStrategyFactory: SecretStrategyFactory = com.geirolz.secret.strategy.SecretStrategyFactory@5be15840
// ----------------------------- USAGE -----------------------------
// implicitly in the scope
import myCustomStrategyFactory.given
Secret("my_password").euse(secret => secret)
// res12: Either[SecretDestroyed, String] = Right(value = "my_password")
// or restricted to a specific scope
myCustomStrategyFactory {
Secret("my_password").euse(secret => secret)
}
// res13: Either[SecretDestroyed, String] = Right(value = "my_password")
We welcome contributions from the open-source community to make Secret even better. If you have any bug reports, feature requests, or suggestions, please submit them via GitHub issues. Pull requests are also welcome.
Before contributing, please read our Contribution Guidelines to understand the development process and coding conventions.
Please remember te following:
- Run
sbt scalafmtAll
before submitting a PR. - Run
sbt gen-doc
to update the documentation.
Secret is released under the Apache License 2.0. Feel free to use it in your open-source or commercial projects.