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@224374ac

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@5d996e63

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

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

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@1beb4fad

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@4fdd1cfb

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

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@24e021d8

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

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