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 | ✅ | ✅ |
"pgp-testkit" |
ScalaCheck Generators and Arbitrary instances for PGP classes | Cats Effect 3 | fs2 3.x | ✅ | ✅ |
Bouncy Castle often releases versions that report as binary-incompatible with previous versions when examined with a tool such as MiMa.
Upon each release of this library, artifacts are published with the version of
Bouncy Castle supported by that artifact. For example, the latest Bouncy Castle
version is 1.80, so we'll publish com.dwolla::fs2-pgp-bcpg1.80. In addition,
we publish com.dwolla::fs2-pgp as an empty artifact that will depend on the
more specifically versioned artifact (i.e., com.dwolla::fs2-pgp depends on
com.dwolla::fs2-pgp-bcpg1.80.)
This allows applications to depend on com.dwolla::fs2-pgp and always stay up
to date with the latest supported version of Bouncy Castle, while libraries
can depend on a specifically versioned artifact like
com.dwolla::fs2-pgp-bcpg1.80 to address binary compatibility concerns.
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@1003b416Read a PGPPublicKey using PGPKeyAlg[F], then pipe your message bytes through CryptoAlg[F].encrypt.
import cats.effect._
import cats.syntax.all._
import org.typelevel.log4cats.Logger
import fs2._
import fs2.text._
import com.dwolla.security.crypto._
import org.bouncycastle.openpgp._
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
…
"Read a PGPPrivateKey using PGPKeyAlg[F], then pipe the encrypted message bytes through CryptoAlg[F].decrypt.