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@79273a4f

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@7845ee8a

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

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

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@2db82155

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@253c7189

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

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@707b0b9b

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@1780bc15

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