GithubHelp home page GithubHelp logo

dwolla / fs2-pgp Goto Github PK

View Code? Open in Web Editor NEW
4.0 14.0 3.0 288 KB

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

Home Page: https://dwolla.com

License: MIT License

Scala 100.00%
fs2 cats-effect pgp gpg bouncycastle hacktoberfest

fs2-pgp's Introduction

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
"pgp-testkit" ScalaCheck Generators and Arbitrary instances for PGP classes Cats Effect 3 fs2 3.x

Bouncy Castle Versions

Bouncy Castle often releases versions that report as binary-incompatible with previous versions when examined with a tool such as MiMa. The "current" version of the artifacts published by this project intend to track the latest Bouncy Castle version, with previously supported versions published as supplemental artifacts with the supported Bouncy Castle version appended to the artifact name.

For example, the latest Bouncy Castle version is 1.77, so the latest version of com.dwolla::fs2-pgp depends on org.bouncycastle:bcpg-jdk18on:1.77. In addition, we publish artifacts named like com.dwolla::fs2-pgp-bcpg1.76 for each of the previously supported Bouncy Castle artifacts.

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 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

"

Decryption

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

fs2-pgp's People

Contributors

aquilesg avatar bpholt avatar cjsmith-0141 avatar dwolla-oss-scala-steward[bot] avatar mergify[bot] avatar scala-steward avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fs2-pgp's Issues

Feature Request: Support decryption of recipient-less messages

Thank you for this library!

I'm adding this issue in the hopes that I can submit a PR to address it, but the workflow I would like to have is this:

  1. You encrypt a message using gpg -R on the command line i.e.
❯ cat test.txt
foobarfoobarfoobar
❯ gpg -e -a -R 'Connor James Smith <[email protected]>' -o test3.txt.pgp test.txt
File 'test3.txt.pgp' exists. Overwrite? (y/N) y
❯ cat test3.txt.pgp
-----BEGIN PGP MESSAGE-----

hQIMAwAAAAAAAAAAAQ/9HgFExBedlOndKW8bOQ9m4uiNqJnm7UNNCe1IQpJoytHN
FYO6IFXbHCnQVS5EcYJVQO4n9tglqWAT8hwOjAGKwb1XxltY3y62dMRSi+CyPeR+
6dMw/taAHQmw+2xv25KRWEH3xPjTsvI3MVT92PANRsHLRs2L2TOIFUoOBwe7Pgzj
NafR2/R1sIpbDvDnvpJHdwLZrUiyjvug8jxs7Oto8TRv1u8tzgNg8Z6WD9myqM+N
HQWxvMOPJ/iSn5pelD7722eHeL/3JSwtJF30Te+H7809wPw9i9Xaeo5p3SHfLXdu
egr5xGrr5aoRHfQwkhbhAYOrX63N/FUsCqCfr2e2XcUKSpxwXLz+J9L+94Es02KX
tL+FZXDSB/sBV5qmanfVs+XGB+rZQ7VjS+PcxLc0I3EuLQr2hgJEsRd5DYNjArH1
2072S9eF1Kmq/uQE231KUSqv1UUDm3oX8Lg53Pt1f/F07go2QOFdmHjONSf5VUcb
2uAOyHS05iLnj/Uciqu1QQyvJz8YF5FCWlpCXZSsDfXqx7esEZWvztkAkwQa3E/n
VRc/UkwT31axtrYXp1KAbrx0Hg61Cqdejn5K2hnNz4JUlcNCwYRSPPbwmbKcYB/k
v8xeUKgx4DS/RzJFZUH/SIclCi1MvJo6ILiFKoVz2GU570b6R4XNWE9bREm7GvDS
TAFzoBx/OP/AICMaKbyOT0rHZmgvLbZsqo496q7dSsC3Cn6HvLG5xqvxt4afon+I
PUrwTuf/XJN2/lFEk1yTpk9ebFDDSECWDts6jtc=
=IeYl
-----END PGP MESSAGE-----
  1. Inside a unit test, be able to decrypt with hard-coded private key
test("decrypt works") {
  val input = "foobarfoobarfoobar"
  val encryptedInput: String = ??? //omitted
  val privateKeyString: String = ??? //omitted
  implicit val logger: Logger[IO] = Slf4jLogger.getLogger

  for {
    resultString <- PGPKeyAlg[IO].readPrivateKey(privateKeyString).attempt.flatMap {
      case Right(k) if k.getKeyID > 0 =>
        (for {
          crypto <- Stream.resource(CryptoAlg[IO])
          output <- Stream
            .emits(encryptedInput.getBytes())
            .through(crypto.decrypt(k))
            .through(utf8.decode)
            .attempt
        } yield output).compile.foldMonoid
      case Right(_) => new Throwable("shouldn't happen!").asLeft[String].pure[IO]
      case Left(e) => e.asLeft[String].pure[IO]
    }
  } yield {
    resultString match {
      case Right(s) => assertEquals(s, input)
      case Left(e) => fail(s"should never happen: ${e.getMessage}")
    }
  }
}

What is happening when I try this right now is this:

should never happen: Cannot decrypt message with key 0 because it requires key <key omitted>

Thanks again.

KeyMistmatchExceptions during decryption

Hi, I'm having some issues with decryption and hoping you could help out. I'm seeing the following behavior:

  1. encrypting -> decrypting round trip with fs2-pgp works correctly
  2. Generating an encrypted message with fs2-pgp and using OpenPGP in terminal to decrypt it (gpg --decrypt) works correctly
  3. Generating an encrypted message with fs2-pgp and using a Node.js script with openpgpjs to decrypt it works correctly
  4. Generating an encrypted message with either OpenPGP in terminal or openpgpjs and using fs2-pgp to decrypt it results in a KeyMismatchException, e.g.:
com.dwolla.security.crypto.KeyMismatchException: Cannot decrypt message with key -5276281287153731066 because it requires key -3508842406183721463

My code basically looks like this:
Encryption:

def encrypt(...): Pipe[IO, Byte, Byte] =  { stream =>
    val key: PGPPublicKey = PGPKeyAlg[IO](blocker).readPublicKey(/* key string */)
    for {
      crypto <- Stream.resource(CryptoAlg[IO](blocker))
      output <- stream.through(crypto.encrypt(key)).through(crypto.armor())
    } yield output
}

Decryption:

def decrypt(...): Pipe[IO, Byte, Byte] = { stream =>
    val key: PGPPrivateKey = PGPKeyAlg[IO](blocker).readPrivateKey(/* key string */, /* passcode */)
    for {
      crypto <- Stream.resource(CryptoAlg[IO](blocker))
      output <- stream.through(crypto.decrypt(key))
    } yield output
  }

I've triple checked that I'm using the same keys in all of these scenarios, and so the only thing I can think of is that I'm doing something silly with armoring/encoding either the key or the message. Do you know what could be causing this?

Improve effect type inference in PGPKeyAlg

I think if we reverse the order of the partially applied HKT in PartiallyAppliedPGPKeyAlg, we can let the caller specify the eventual output effect (e.g. Resource[F, *]), which should facilitate better inference overall. In 0.1.0 callers have to specify both effects, e.g. PGPKeyAlg[IO][Resource[IO, *]](blocker), when I think it would work to just do PGPKeyAlg[Resource[IO, *]](blocker).

Release v0.5.0

Release v0.5.0

  • Prepare release in this repo

  • Make PR to Scala Steward to make it aware of our Scalafix

    I think it will look like adding this

    {
      groupId: "org.dwolla",
      artifactIds: ["fs2-pgp", "pgp-testkit"],
      newVersion: "0.5.0",
      doc: "https://github.com/Dwolla/fs2-pgp#scalafix-rule",
      rewriteRules: ["dependency:[email protected]::fs2-pgp-scalafix:0.5.0"]
    }
    

    to scalafix-migrations.conf, but I'm waiting for feedback in Discord on a couple things.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.