· 6 years ago · Jul 09, 2019, 08:16 PM
1import cats.data.Kleisli
2import cats.effect.Sync
3import com.google.common.io.BaseEncoding
4import javax.crypto.spec.{IvParameterSpec, PBEKeySpec, SecretKeySpec}
5import javax.crypto.{Cipher, SecretKeyFactory}
6
7object Hasher {
8 type Key = (String, String)
9
10 final case class InvalidInputException(hash: String, ex: Throwable) extends IllegalArgumentException(s"Could not decrypt [$hash]", ex)
11
12 private val iv: Array[Byte] = Array.fill(16)(0)
13 private val serde = BaseEncoding.base64Url().omitPadding()
14
15 private def secretKey[F[_]](implicit F: Sync[F]): Kleisli[F, Key, SecretKeySpec] = Kleisli(config => F.delay {
16 val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
17 val spec = new PBEKeySpec(config._1.toCharArray, config._2.getBytes(), 65536, 256)
18 val tmp = factory.generateSecret(spec)
19 new SecretKeySpec(tmp.getEncoded, "AES")
20 })
21
22 def encrypt[F[_]](input: String)(implicit F: Sync[F]): Kleisli[F, Key, String] = for {
23 secret <- secretKey
24 iparam <- Kleisli.liftF(F.pure(new IvParameterSpec(iv)))
25 cipher <- Kleisli.liftF(F.delay {
26 val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
27 cipher.init(Cipher.ENCRYPT_MODE, secret, iparam)
28 cipher.doFinal(input.getBytes)
29 })
30 hash <- Kleisli.liftF(F.delay(serde.encode(cipher)))
31 } yield hash
32
33 def decrypt[F[_]](input: String)(implicit F: Sync[F]): Kleisli[F, Key, String] = for {
34 secret <- secretKey
35 hash <- Kleisli.liftF(F.delay(serde.decode(input)))
36 iparam <- Kleisli.liftF(F.pure(new IvParameterSpec(iv)))
37 cipher <- Kleisli.liftF(F.delay {
38 val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
39 cipher.init(Cipher.DECRYPT_MODE, secret, iparam)
40 cipher.doFinal(hash)
41 })
42 value <- Kleisli.liftF(F.delay {
43 new String(cipher)
44 })
45 } yield value
46}