Symmetric Ciphers

Introduction

For symmetric ciphers, we provide a low level as well as a high-level api construction.

Symmetric ciphers, as an introduction, have the following basic properties, for all non-empty m (as, if a message is empty, you’re not encrypting anything and c = m):

 E(K, m) = c
 D(K, c) = m
 D(K, E(K, m)) = m
 
 Where:
    E: Encryption function
    D: Decryption function
    K: Some symmetric key
    m: Some message, or plainText
    c: Some ciphertext

However, in practice, block ciphers (and most secure stream ciphers like Salsa20) require a nonce as well (called an initialization vector for block ciphers). As such, we need to either provide it explicitly, or implicitly in our cipher construction. TSec provides such an implicit construction using IvStrategy.

Usage

These are the imports you will need for basic usage:

  import tsec.common._
  import tsec.cipher.symmetric._
  import tsec.cipher.symmetric.jca._
  import cats.effect.IO

In tsec, we provide a few default constructions for simple AES encryption: AES/CTR and AES/CBC. For Authenticated encryption, we provide an AES/GCM construction.

To be able to generate initialization vectors for a particular cipher, you must either have an implicit IvStrategy[A](where A is the algorithm type, i.e AES128GCM) in scope, or pass it explicitly, or use it to generate a nonce of the right size:

scala>   val toEncrypt = "hi hello welcome to tsec".utf8Bytes
toEncrypt: Array[Byte] = Array(104, 105, 32, 104, 101, 108, 108, 111, 32, 119, 101, 108, 99, 111, 109, 101, 32, 116, 111, 32, 116, 115, 101, 99)

scala>   implicit val ctrStrategy: IvGen[IO, AES128CTR] = AES128CTR.defaultIvStrategy[IO]
ctrStrategy: tsec.cipher.symmetric.IvGen[cats.effect.IO,tsec.cipher.symmetric.jca.AES128CTR] = tsec.cipher.symmetric.jca.JCAIvGen$$anon$1@5bdb9c69

scala>   implicit val cachedInstance                    = AES128CTR.genEncryptor[IO] //Cache the implicit
cachedInstance: tsec.cipher.symmetric.jca.primitive.JCAPrimitiveCipher[cats.effect.IO,tsec.cipher.symmetric.jca.AES128CTR,tsec.cipher.symmetric.jca.CTR,tsec.cipher.common.padding.NoPadding] = tsec.cipher.symmetric.jca.primitive.JCAPrimitiveCipher$$anon$1@a1d926b

scala>   val onlyEncrypt: IO[String] =
     |     for {
     |       key       <- AES128CTR.generateKey[IO] //Generate our key
     |       encrypted <- AES128CTR.encrypt[IO](PlainText(toEncrypt), key) //Encrypt our message
     |       decrypted <- AES128CTR.decrypt[IO](encrypted, key)
     |     } yield decrypted.toUtf8String // "hi hello welcome to tsec!"
onlyEncrypt: cats.effect.IO[String] = IO$1938841087

scala>   /** You can also turn it into a singular array with the IV concatenated at the end */
     |   val onlyEncrypt2: IO[String] =
     |     for {
     |       key       <- AES128CTR.generateKey[IO]                        //Generate our key
     |       encrypted <- AES128CTR.encrypt[IO](PlainText(toEncrypt), key) //Encrypt our message
     |       array = encrypted.toConcatenated
     |       from      <- IO.fromEither(AES128CTR.ciphertextFromConcat(array))
     |       decrypted <- AES128CTR.decrypt[IO](from, key)
     |     } yield decrypted.toUtf8String // "hi hello welcome to tsec!"
onlyEncrypt2: cats.effect.IO[String] = IO$1241184277

For authenticated encryption and decryption

scala>   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@52e34db

scala>   implicit val cachedAADEncryptor = AES128GCM.genEncryptor[IO]
cachedAADEncryptor: tsec.cipher.symmetric.AADEncryptor[cats.effect.IO,tsec.cipher.symmetric.jca.AES128GCM,tsec.cipher.symmetric.jca.SecretKey] = tsec.cipher.symmetric.jca.primitive.JCAAEADPrimitive$$anon$1@4f5ad6b7

scala>   val aad = AAD("myAdditionalAuthenticationData".utf8Bytes)
aad: tsec.cipher.symmetric.AAD.Type = Array(109, 121, 65, 100, 100, 105, 116, 105, 111, 110, 97, 108, 65, 117, 116, 104, 101, 110, 116, 105, 99, 97, 116, 105, 111, 110, 68, 97, 116, 97)

scala>   val encryptAAD: IO[String] =
     |     for {
     |       key       <- AES128GCM.generateKey[IO]                                    //Generate our key
     |       encrypted <- AES128GCM.encryptWithAAD[IO](PlainText(toEncrypt), key, aad) //Encrypt
     |       decrypted <- AES128GCM.decryptWithAAD[IO](encrypted, key, aad)            //Decrypt
     |     } yield decrypted.toUtf8String // "hi hello welcome to tsec!"
encryptAAD: cats.effect.IO[String] = IO$1664297171

Finally, For more advanced usage, i.e you know which cipher you want specifically, you must import both padding as well as the primitive package.

Note: This is not recommended. Use at your own risk.

scala>   /** For more advanced usage, i.e you know which cipher you want specifically, you must import padding
     |     * as well as the low level package
     |     *
     |     * this is not recommended, but useful for.. science!
     |     *
     |     */
     |   import tsec.cipher.common.padding._
import tsec.cipher.common.padding._

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

scala>   val desStrategy = JCAIvGen.random[IO, DES]
desStrategy: tsec.cipher.symmetric.IvGen[cats.effect.IO,tsec.cipher.symmetric.jca.DES] = tsec.cipher.symmetric.jca.JCAIvGen$$anon$1@685efb22

scala>   implicit val instance = JCAPrimitiveCipher.sync[IO, DES, CBC, PKCS7Padding]
instance: tsec.cipher.symmetric.jca.primitive.JCAPrimitiveCipher[cats.effect.IO,tsec.cipher.symmetric.jca.DES,tsec.cipher.symmetric.jca.CBC,tsec.cipher.common.padding.PKCS7Padding] = tsec.cipher.symmetric.jca.primitive.JCAPrimitiveCipher$$anon$1@7b6bcbee

scala>   val advancedUsage: IO[String] = for {
     |     key       <- DES.generateKey[IO]
     |     iv        <- desStrategy.genIv
     |     encrypted <- instance.encrypt(PlainText(toEncrypt), key, iv) //Encrypt our message, with our auth data
     |     decrypted <- instance.decrypt(encrypted, key) //Decrypt our message: We need to pass it the same AAD
     |   } yield decrypted.toUtf8String
advancedUsage: cats.effect.IO[String] = IO$1387460501