Skip to content
/ fs2-pgp Public

fs2 pipes for encrypting and decrypting data using BouncyCastle's PGP implementation

License

Notifications You must be signed in to change notification settings

Dwolla/fs2-pgp

Repository files navigation

fs2-pgp

A library for encrypting and decrypting fs2 Stream[F, Byte] using PGP.

Artifact Description Cats Effect Version fs2 Version Scala 2.12 Scala 2.13
"fs2-pgp" Load PGP keys and use them to encrypt, decrypt, and armor byte streams. Cats Effect 3 fs2 3.x
"fs2-pgp-ce2" Cats Effect 2 fs2 2.x
"pgp-testkit" ScalaCheck Generators and Arbitrary instances for PGP classes Cats Effect 3 fs2 3.x
"pgp-testkit-ce2" Cats Effect 2 fs2 2.x

Keys

import com.dwolla.security.crypto._
import cats.effect._

val key =
  """-----BEGIN PGP PUBLIC KEY BLOCK-----
    |Version: GnuPG v1
    |
    |mQENBFVoyeYBCACy0S/9y/c/CpoYLL6aD3TMCV1Pe/0jcWN0ykULf9l4znYODZLr
    |f10BGAJETj9ghrJCNXMib2ogz0wo43KVAp9o3mkg01vVyqs1rzM5jw+yCZmyGPFf
    |GsE2lxZFMX+rS0dyq2w0FQN2IjYsELwIFeQ02GXTLyTlhY+u5wwCXo4e7AEXaEo7
    |jl8129NA46gf6l+6lUMyFpKnunO7L4W5rCCrIizP4Fmll1adYfClSX6cztIfz4vg
    |Fs2HuViPin5y8THodkg9cIkCfyNHivEbfBbx0xfx67BCwxFcYgF/84H8TASRhjRl
    |4s1fZDA7rETWDJIcC+neNV/qtF0kY1ECSd3nABEBAAG0IFRlc3QgVXNlciA8ZnJl
    |ZCt0ZXN0QGR3b2xsYS5jb20+iQE4BBMBAgAiBQJVaMnmAhsDBgsJCAcDAgYVCAIJ
    |CgsEFgIDAQIeAQIXgAAKCRA2OYfNakCqV1bYB/9QNR5DN5J27Z4DIGoOto/PuVvs
    |bQHZj8NLcvIZL1cUyKOg+oRICq2z4BXHAMqyouhs/GLiR5P74I9cJTSIudAvBhwi
    |du9AcMQy+Qg3K1rUQGlNU+iamD8DFNUhLoK+Oicij0Mw4TSWBsoR3+Pg/jZ5SDUc
    |dUsGGaBJthYoiJR8vZ6Uf9oCn+mpVhrso0zemBDud4AHKaVa+8o7VUWGa6jeyRHX
    |RKVbHn7GGYiHZkl+qfpthcxyPHOIkZo+t8GVTItLpvVuU+X36N70+rIzXj5t8NDZ
    |KfD3M4p6BSq6Cu6DtJOZ1F28hwaWiRoCdbPrJfW33fo1RxLB6+nLf/ttYGmhuQEN
    |BFVoyeYBCADiZfKA98YQip/kvj5rBS2ilQDycBX7Ls2IftuwzO6Q9QSF2lDiz708
    |zyvg0czQPZYaZkFgziZEmjbvOc7hDG+icVWRLCjCcZk4i2TXy7bGcTZmBQ31iVMJ
    |ia7GxsJhu4ngrP15pZakAYcCwEk3QH17TdhOwvV8ixHmv9USCMJyiNnuhVAP2tY/
    |Ef0EoCV6qAMoP3dNPT30sFI8+55Ce9yAtWQItT5q4vYOmC9Q34XtSxvpLsLzVByd
    |rdvgXe0acjvMiTGcYBdjitawFYeLuz2s5mQAi4X1vcJqxBSBjG7X+1PiDqFFIid3
    |+6rIQtR3ho+Xqz/ucGglKxtn6m49wMHJABEBAAGJAR8EGAECAAkFAlVoyeYCGwwA
    |CgkQNjmHzWpAqldxFgf/SZIT1AiBAOLkqdWEObg0cU7n1YOXbj56sUeUCFxdbnl9
    |V2paf2SaMB6EEGLTk9PN0GG3hPyDkl4O6w3mn2J46uP8ecVaNvTSxoq2OmkMmD1H
    |/OSnF8a/jB6R1ODiAwekVuUMtAS7JiaAAcKcenG1f0XRKwQs52uavGXPgUuJbVtK
    |bB0SyLBhvGG8YIWTXRMHoJRt/Ls4JEuYaoBYqfV2eDn4WhW1LVuXP13gXixy0RiV
    |8rHs9aH8BAU7Dy0BBnaS3R9m8vtfdFxMI3/+1iGt0+xh/B4w++9oFE2DgyoZXUF8
    |mbjKYhiRPKNoj6Rn/mHUGcnuPlKvKP+1X5bObpDbQQ==
    |=TJUS
    |-----END PGP PUBLIC KEY BLOCK-----""".stripMargin

PGPKeyAlg[IO].readPublicKey(key).unsafeRunSync()
val res0: org.bouncycastle.openpgp.PGPPublicKey = org.bouncycastle.openpgp.PGPPublicKey@1003b416

Encryption

Read a PGPPublicKey using PGPKeyAlg[F], then pipe your message bytes through CryptoAlg[F].encrypt.

import cats.effect._
import cats.syntax.all._
import io.chrisdavenport.log4cats.Logger
import fs2._
import fs2.text._
import com.dwolla.security.crypto._
import org.bouncycastle.openpgp._

implicit val cs: ContextShift[IO] = IO.contextShift(scala.concurrent.ExecutionContext.global)
implicit val t: Timer[IO] = IO.timer(scala.concurrent.ExecutionContext.global)
implicit val L: Logger[IO] = new Logger[IO] {
    override def error(t: Throwable)(message: => String): IO[Unit] = ().pure[IO]
    override def warn(t: Throwable)(message: => String): IO[Unit] = ().pure[IO]
    override def info(t: Throwable)(message: => String): IO[Unit] = ().pure[IO]
    override def debug(t: Throwable)(message: => String): IO[Unit] = ().pure[IO]
    override def trace(t: Throwable)(message: => String): IO[Unit] = ().pure[IO]
    override def error(message: => String): IO[Unit] = ().pure[IO]
    override def warn(message: => String): IO[Unit] = ().pure[IO]
    override def info(message: => String): IO[Unit] = ().pure[IO]
    override def debug(message: => String): IO[Unit] = ().pure[IO]
    override def trace(message: => String): IO[Unit] = ().pure[IO]
}
val key: PGPPublicKey = ??? // from above

(for {
  crypto <- Stream.resource(CryptoAlg[IO])
  output <- Stream.emit("hello world")
                  .through(utf8.encode)
                  .through(crypto.encrypt(key))
                  .through(crypto.armor())
                  .through(utf8.decode)
} yield output).compile.string.unsafeRunSync()
val res1: String =
"-----BEGIN PGP MESSAGE-----
Version: BCPG v1.66

hQEMAzY5h81qQKpXAQf/YTq6GtTkWlbg2DRu7r133FZaAudA149WB2BV/vsgyHkN

"

Decryption

Read a PGPPrivateKey using PGPKeyAlg[F], then pipe the encrypted message bytes through CryptoAlg[F].decrypt.