Motivation

TSec began as a desire to improve on what the JCA provides. While the JCA provides well tested, secure algorithms, it suffers from being incredibly cumbersome to use. As an example, let’s encrypt something using AES-GCM:

scala> import javax.crypto.{Cipher, KeyGenerator, SecretKey}
import javax.crypto.{Cipher, KeyGenerator, SecretKey}

scala> val keyGenerator: KeyGenerator = KeyGenerator.getInstance("AES") //Stringly typed
keyGenerator: javax.crypto.KeyGenerator = javax.crypto.KeyGenerator@1d611a7a

scala> keyGenerator.init(192) //Any way to get this standard constant without setting it yourself? Also mutates!

scala> val secretKey: SecretKey = keyGenerator.generateKey() //We have no information about what type of key this was
secretKey: javax.crypto.SecretKey = javax.crypto.spec.SecretKeySpec@fffe85fd

scala> val cipher: Cipher = Cipher.getInstance("AES/GCM/NoPadding") //Stringly Typed. also unsafe. May throw an exception.
cipher: javax.crypto.Cipher = javax.crypto.Cipher@7bb49698

scala> cipher.init(Cipher.ENCRYPT_MODE, secretKey) //Encrypt mode is an int. May throw an exception. Also mutates!

scala> val toEncrypt: Array[Byte] = "Hello".getBytes("UTF-8") 
toEncrypt: Array[Byte] = Array(72, 101, 108, 108, 111)

scala> val encrypted: Array[Byte] = cipher.doFinal(toEncrypt) //We have no information about 
encrypted: Array[Byte] = Array(-116, -117, 16, 32, 31, 61, 17, 127, -9, 30, 58, 1, -60, -98, -110, -69, 117, -37, 87, 109, -60)

Can you catch the mistake here? It’s easy to overlook, but we do not have a reference to the IV (Initialization Vector) used, thus we have no way to ever decrypt this if we forgot to retrieve it!

On top of this, outside of these local strings, we have no information carried by the objects about the encryption used, key type used, and whatnot, which could all be useful at compile time. Feed it a wrong length key, this throws an exception!

TSec was made to improve on this. We provide types where the JCA does not, as well as we provide helpers to propagate all the encryption type information the JCA does not, so you can rip out a few hairs at compile time, not when you have to hotfix in production.

The same thing, in TSec, plus decryption (as a pure expression):

scala> import cats.effect.IO
import cats.effect.IO

scala> import tsec.common._
import tsec.common._

scala> import tsec.cipher.symmetric._
import tsec.cipher.symmetric._

scala> import tsec.cipher.symmetric.jca._
import tsec.cipher.symmetric.jca._

scala> val toEncrypt = "Hello".utf8Bytes
toEncrypt: Array[Byte] = Array(72, 101, 108, 108, 111)

scala> /** An authenticated encryption and decryption */
     | implicit val gcmstrategy = AES128GCM.defaultIvStrategy[IO]
gcmstrategy: tsec.cipher.symmetric.IvGen[cats.effect.IO,tsec.cipher.symmetric.jca.AES128GCM] = tsec.cipher.symmetric.jca.package$GCM$$anon$4@e951fac

scala> val encryptAAD: IO[String] =
     |   for {
     |     key       <- AES128GCM.generateKey[IO]  //Generate our key
     |     encrypted <- AES128GCM.encrypt[IO](PlainText(toEncrypt), key) //Encrypt
     |     decrypted <- AES128GCM.decrypt[IO](encrypted, key)            //Decrypt
     |   } yield decrypted.toUtf8String // "Hello!" 
encryptAAD: cats.effect.IO[String] = IO$1754375615