mirror of
https://github.com/fluencelabs/crypto
synced 2025-04-24 14:22:18 +00:00
Aes crypto js (#72)
* WIP * facade for encrypt-decrypt and TestApp for testing * test * understanding of return types in crypto-js * refactor jvm aes, keyJVM === keyJS, encryptedJVM === encryptedJS * great refactoring of cryptography * small improvements * refactoring and documentation * delete main classes * rename AesCryptJS to AesCrypt * fix tests * License headers
This commit is contained in:
parent
26b41e7b4d
commit
d091146dd2
142
js/src/main/scala/fluence/crypto/algorithm/AesCrypt.scala
Normal file
142
js/src/main/scala/fluence/crypto/algorithm/AesCrypt.scala
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.algorithm
|
||||
|
||||
import cats.data.EitherT
|
||||
import cats.{ Applicative, Monad, MonadError }
|
||||
import cats.syntax.applicative._
|
||||
import cats.syntax.flatMap._
|
||||
import fluence.codec.Codec
|
||||
import fluence.crypto.algorithm.CryptoErr.nonFatalHandling
|
||||
import fluence.crypto.cipher.Crypt
|
||||
import fluence.crypto.facade.cryptojs.{ CryptOptions, CryptoJS, Key, KeyOptions }
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scalajs.js.JSConverters._
|
||||
import scala.language.higherKinds
|
||||
import scala.scalajs.js.typedarray.Int8Array
|
||||
|
||||
class AesCrypt[F[_] : Monad, T](password: Array[Char], withIV: Boolean, config: AesConfig)(implicit ME: MonadError[F, Throwable], codec: Codec[F, T, Array[Byte]]) extends Crypt[F, T, Array[Byte]] {
|
||||
|
||||
private val salt = config.salt
|
||||
|
||||
private val rndStr = CryptoJS.lib.WordArray
|
||||
|
||||
//number of password hashing iterations
|
||||
private val iterationCount = config.iterationCount
|
||||
//initialisation vector must be the same length as block size
|
||||
private val IV_SIZE = 16
|
||||
private val BITS = 256
|
||||
//generate IV in hex
|
||||
private def generateIV = rndStr.random(IV_SIZE)
|
||||
|
||||
private val pad = CryptoJS.pad.Pkcs7
|
||||
private val mode = CryptoJS.mode.CBC
|
||||
private val aes = CryptoJS.AES
|
||||
|
||||
override def encrypt(plainText: T): F[Array[Byte]] = {
|
||||
val e = for {
|
||||
data ← EitherT.liftF(codec.encode(plainText))
|
||||
key ← initSecretKey()
|
||||
encrypted ← encryptData(data, key)
|
||||
} yield encrypted
|
||||
|
||||
e.value.flatMap(ME.fromEither)
|
||||
}
|
||||
|
||||
override def decrypt(cipherText: Array[Byte]): F[T] = {
|
||||
val e = for {
|
||||
detachedData ← detachData(cipherText)
|
||||
(iv, base64) = detachedData
|
||||
key ← initSecretKey()
|
||||
decData ← decryptData(key, base64, iv)
|
||||
_ ← EitherT.cond(decData.nonEmpty, decData, CryptoErr("Cannot decrypt message with this password."))
|
||||
plain ← EitherT.liftF[F, CryptoErr, T](codec.decode(decData.toArray))
|
||||
} yield plain
|
||||
|
||||
e.value.flatMap(ME.fromEither)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt data.
|
||||
* @param data Data to encrypt
|
||||
* @param key Salted and hashed password
|
||||
* @return Encrypted data with IV
|
||||
*/
|
||||
private def encryptData(data: Array[Byte], key: Key): EitherT[F, CryptoErr, Array[Byte]] = {
|
||||
nonFatalHandling {
|
||||
//transform data to JS type
|
||||
val wordArray = CryptoJS.lib.WordArray.create(new Int8Array(data.toJSArray))
|
||||
val iv = if (withIV) Some(generateIV) else None
|
||||
val cryptOptions = CryptOptions(iv = iv, padding = pad, mode = mode)
|
||||
//encryption return base64 string, transform it to byte array
|
||||
val crypted = ByteVector.fromValidBase64(aes.encrypt(wordArray, key, cryptOptions).toString)
|
||||
//IV also needs to be transformed in byte array
|
||||
val byteIv = iv.map(i ⇒ ByteVector.fromValidHex(i.toString))
|
||||
byteIv.map(_.toArray ++ crypted.toArray).getOrElse(crypted.toArray)
|
||||
}("Cannot encrypt data.")
|
||||
}
|
||||
|
||||
private def decryptData(key: Key, base64Data: String, iv: Option[String]) = {
|
||||
nonFatalHandling {
|
||||
//parse IV to WordArray JS format
|
||||
val cryptOptions = CryptOptions(iv = iv.map(i ⇒ CryptoJS.enc.Hex.parse(i)), padding = pad, mode = mode)
|
||||
val dec = aes.decrypt(base64Data, key, cryptOptions)
|
||||
ByteVector.fromValidHex(dec.toString)
|
||||
}("Cannot decrypt data.")
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cipherText Encrypted data with IV
|
||||
* @return IV in hex and data in base64
|
||||
*/
|
||||
private def detachData(cipherText: Array[Byte]): EitherT[F, CryptoErr, (Option[String], String)] = {
|
||||
nonFatalHandling {
|
||||
val dataWithParams = if (withIV) {
|
||||
val ivDec = ByteVector(cipherText.slice(0, IV_SIZE)).toHex
|
||||
val encMessage = cipherText.slice(IV_SIZE, cipherText.length)
|
||||
(Some(ivDec), encMessage)
|
||||
} else (None, cipherText)
|
||||
val (ivOp, data) = dataWithParams
|
||||
val base64 = ByteVector(data).toBase64
|
||||
(ivOp, base64)
|
||||
}("Cannot detach data and IV.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash password with salt `iterationCount` times
|
||||
*/
|
||||
private def initSecretKey(): EitherT[F, CryptoErr, Key] = {
|
||||
nonFatalHandling {
|
||||
// get raw key from password and salt
|
||||
val keyOption = KeyOptions(BITS, iterations = iterationCount, hasher = CryptoJS.algo.SHA256)
|
||||
CryptoJS.PBKDF2(new String(password), salt, keyOption)
|
||||
}("Cannot init secret key.")
|
||||
}
|
||||
}
|
||||
|
||||
object AesCrypt extends slogging.LazyLogging {
|
||||
|
||||
def forString[F[_] : Applicative](password: ByteVector, withIV: Boolean, config: AesConfig)(implicit ME: MonadError[F, Throwable]): AesCrypt[F, String] = {
|
||||
implicit val codec: Codec[F, String, Array[Byte]] = Codec[F, String, Array[Byte]](_.getBytes.pure[F], bytes ⇒ new String(bytes).pure[F])
|
||||
apply[F, String](password, withIV, config)
|
||||
}
|
||||
|
||||
def apply[F[_] : Applicative, T](password: ByteVector, withIV: Boolean, config: AesConfig)(implicit ME: MonadError[F, Throwable], codec: Codec[F, T, Array[Byte]]): AesCrypt[F, T] =
|
||||
new AesCrypt(password.toHex.toCharArray, withIV, config)
|
||||
}
|
@ -18,9 +18,9 @@
|
||||
package fluence.crypto.algorithm
|
||||
|
||||
import cats.data.EitherT
|
||||
import cats.{ Monad, MonadError }
|
||||
import cats.Monad
|
||||
import fluence.crypto.SignAlgo
|
||||
import fluence.crypto.facade.EC
|
||||
import fluence.crypto.facade.ecdsa.EC
|
||||
import fluence.crypto.hash.{ CryptoHasher, JsCryptoHasher }
|
||||
import fluence.crypto.keypair.KeyPair
|
||||
import fluence.crypto.signature.Signature
|
||||
@ -34,7 +34,7 @@ import scala.scalajs.js.JSConverters._
|
||||
* Return in all js methods hex, because in the other case we will receive javascript objects
|
||||
* @param ec implementation of ecdsa logic for different curves
|
||||
*/
|
||||
class EcdsaJS(ec: EC, hasher: Option[CryptoHasher[Array[Byte], Array[Byte]]]) extends Algorithm with SignatureFunctions with KeyGenerator {
|
||||
class Ecdsa(ec: EC, hasher: Option[CryptoHasher[Array[Byte], Array[Byte]]]) extends Algorithm with SignatureFunctions with KeyGenerator {
|
||||
import CryptoErr._
|
||||
|
||||
override def generateKeyPair[F[_] : Monad](seed: Option[Array[Byte]] = None): EitherT[F, CryptoErr, KeyPair] = {
|
||||
@ -82,8 +82,8 @@ class EcdsaJS(ec: EC, hasher: Option[CryptoHasher[Array[Byte], Array[Byte]]]) ex
|
||||
|
||||
}
|
||||
|
||||
object EcdsaJS {
|
||||
val ecdsa_secp256k1_sha256 = new EcdsaJS(new EC("secp256k1"), Some(JsCryptoHasher.Sha256))
|
||||
object Ecdsa {
|
||||
val ecdsa_secp256k1_sha256 = new Ecdsa(new EC("secp256k1"), Some(JsCryptoHasher.Sha256))
|
||||
|
||||
val signAlgo = new SignAlgo("ecdsa/secp256k1/sha256/js", ecdsa_secp256k1_sha256)
|
||||
}
|
36
js/src/main/scala/fluence/crypto/facade/cryptojs/AES.scala
Normal file
36
js/src/main/scala/fluence/crypto/facade/cryptojs/AES.scala
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade.cryptojs
|
||||
|
||||
import scala.scalajs.js
|
||||
import scala.scalajs.js.annotation.JSGlobal
|
||||
|
||||
@js.native
|
||||
@JSGlobal
|
||||
class AES extends js.Object {
|
||||
|
||||
/**
|
||||
* @param msg Message to encrypt in JS WordArray.
|
||||
* Could be created with CryptoJS.lib.WordArray.create(new Int8Array(arrayByte.toJSArray))
|
||||
* @param options { iv: iv, padding: CryptoJS.pad.Pkcs7, mode: CryptoJS.mode.CBC }
|
||||
* @return Encrypted message
|
||||
*/
|
||||
def encrypt(msg: WordArray, key: Key, options: CryptOptions): js.Any = js.native
|
||||
|
||||
def decrypt(encrypted: String, key: Key, options: CryptOptions): js.Any = js.native
|
||||
}
|
28
js/src/main/scala/fluence/crypto/facade/cryptojs/Algos.scala
Normal file
28
js/src/main/scala/fluence/crypto/facade/cryptojs/Algos.scala
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade.cryptojs
|
||||
|
||||
import scala.scalajs.js
|
||||
|
||||
@js.native
|
||||
trait Algos extends js.Object {
|
||||
def SHA256: Algo = js.native
|
||||
}
|
||||
|
||||
@js.native
|
||||
trait Algo extends js.Object
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade.cryptojs
|
||||
|
||||
import scala.scalajs.js
|
||||
|
||||
@js.native
|
||||
trait CryptOptions extends js.Object {
|
||||
val iv: Option[js.Any]
|
||||
val padding: Pad
|
||||
val mode: Mode
|
||||
}
|
||||
|
||||
object CryptOptions {
|
||||
def apply(iv: Option[WordArray], padding: Pad, mode: Mode): CryptOptions = {
|
||||
iv match {
|
||||
case Some(i) ⇒
|
||||
js.Dynamic.literal(iv = i, padding = padding, mode = mode).asInstanceOf[CryptOptions]
|
||||
case None ⇒
|
||||
//if IV is empty, there will be an error in JS lib
|
||||
js.Dynamic.literal(iv = CryptoJS.lib.WordArray.random(0), padding = padding, mode = mode).asInstanceOf[CryptOptions]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade.cryptojs
|
||||
|
||||
import scala.scalajs.js
|
||||
import scala.scalajs.js.annotation.JSImport
|
||||
|
||||
@js.native
|
||||
@JSImport("crypto-js", JSImport.Namespace)
|
||||
object CryptoJS extends js.Object {
|
||||
|
||||
def pad: Paddings = js.native
|
||||
def mode: Modes = js.native
|
||||
def AES: AES = js.native
|
||||
|
||||
/**
|
||||
* https://en.wikipedia.org/wiki/PBKDF2
|
||||
* @return Salted and hashed key
|
||||
*/
|
||||
def PBKDF2(pass: String, salt: String, options: KeyOptions): Key = js.native
|
||||
|
||||
def lib: Lib = js.native
|
||||
|
||||
def enc: Enc = js.native
|
||||
|
||||
def algo: Algos = js.native
|
||||
}
|
25
js/src/main/scala/fluence/crypto/facade/cryptojs/Enc.scala
Normal file
25
js/src/main/scala/fluence/crypto/facade/cryptojs/Enc.scala
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade.cryptojs
|
||||
|
||||
import scala.scalajs.js
|
||||
|
||||
@js.native
|
||||
trait Enc extends js.Object {
|
||||
def Hex: Hex = js.native
|
||||
}
|
29
js/src/main/scala/fluence/crypto/facade/cryptojs/Hex.scala
Normal file
29
js/src/main/scala/fluence/crypto/facade/cryptojs/Hex.scala
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade.cryptojs
|
||||
|
||||
import scala.scalajs.js
|
||||
|
||||
@js.native
|
||||
trait Hex extends js.Object {
|
||||
/**
|
||||
* Parse from HEX to JS byte representation
|
||||
* @param str Hex
|
||||
*/
|
||||
def parse(str: String): WordArray = js.native
|
||||
}
|
23
js/src/main/scala/fluence/crypto/facade/cryptojs/Key.scala
Normal file
23
js/src/main/scala/fluence/crypto/facade/cryptojs/Key.scala
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade.cryptojs
|
||||
|
||||
import scala.scalajs.js
|
||||
|
||||
@js.native
|
||||
trait Key extends js.Object
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade.cryptojs
|
||||
|
||||
import scala.scalajs.js
|
||||
|
||||
@js.native
|
||||
trait KeyOptions extends js.Object {
|
||||
val keySize: Int
|
||||
val iterations: Int
|
||||
val hasher: Algo
|
||||
}
|
||||
|
||||
object KeyOptions {
|
||||
def apply(keySizeBits: Int, iterations: Int, hasher: Algo): KeyOptions = {
|
||||
js.Dynamic.literal(keySize = keySizeBits / 32, iterations = iterations, hasher = hasher).asInstanceOf[KeyOptions]
|
||||
}
|
||||
}
|
25
js/src/main/scala/fluence/crypto/facade/cryptojs/Lib.scala
Normal file
25
js/src/main/scala/fluence/crypto/facade/cryptojs/Lib.scala
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade.cryptojs
|
||||
|
||||
import scala.scalajs.js
|
||||
|
||||
@js.native
|
||||
trait Lib extends js.Object {
|
||||
def WordArray: WordArrayFactory = js.native
|
||||
}
|
28
js/src/main/scala/fluence/crypto/facade/cryptojs/Mode.scala
Normal file
28
js/src/main/scala/fluence/crypto/facade/cryptojs/Mode.scala
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade.cryptojs
|
||||
|
||||
import scala.scalajs.js
|
||||
|
||||
@js.native
|
||||
trait Modes extends js.Object {
|
||||
val CBC: Mode = js.native
|
||||
}
|
||||
|
||||
@js.native
|
||||
trait Mode extends js.Object
|
32
js/src/main/scala/fluence/crypto/facade/cryptojs/Pad.scala
Normal file
32
js/src/main/scala/fluence/crypto/facade/cryptojs/Pad.scala
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade.cryptojs
|
||||
|
||||
import scala.scalajs.js
|
||||
import scala.scalajs.js.annotation.JSGlobal
|
||||
|
||||
@js.native
|
||||
@JSGlobal
|
||||
class Paddings extends js.Object {
|
||||
|
||||
val Pkcs7: Pad = js.native
|
||||
}
|
||||
|
||||
@js.native
|
||||
@JSGlobal
|
||||
class Pad extends js.Object
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade.cryptojs
|
||||
|
||||
import scala.scalajs.js
|
||||
|
||||
@js.native
|
||||
trait WordArrayFactory extends js.Object {
|
||||
|
||||
def random(size: Int): WordArray = js.native
|
||||
def create(array: js.Any): WordArray = js.native
|
||||
}
|
||||
|
||||
@js.native
|
||||
trait WordArray extends js.Object
|
@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade
|
||||
package fluence.crypto.facade.ecdsa
|
||||
|
||||
import scala.scalajs.js
|
||||
import scala.scalajs.js.annotation._
|
@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.crypto.facade
|
||||
package fluence.crypto.facade.ecdsa
|
||||
|
||||
import scala.scalajs.js
|
||||
import scala.scalajs.js.annotation.JSImport
|
@ -17,7 +17,7 @@
|
||||
|
||||
package fluence.crypto.hash
|
||||
|
||||
import fluence.crypto.facade.{ SHA1, SHA256 }
|
||||
import fluence.crypto.facade.ecdsa.{ SHA1, SHA256 }
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.scalajs.js.JSConverters._
|
||||
|
65
js/src/test/scala/fluence/crypto/AesJSSpec.scala
Normal file
65
js/src/test/scala/fluence/crypto/AesJSSpec.scala
Normal file
@ -0,0 +1,65 @@
|
||||
package fluence.crypto
|
||||
|
||||
import cats.instances.try_._
|
||||
import fluence.crypto.algorithm.{ AesConfig, AesCrypt, CryptoErr }
|
||||
import org.scalactic.source.Position
|
||||
import org.scalatest.{ Assertion, Matchers, WordSpec }
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.util.{ Random, Try }
|
||||
|
||||
class AesJSSpec extends WordSpec with Matchers with slogging.LazyLogging {
|
||||
|
||||
def rndString(size: Int): String = Random.nextString(10)
|
||||
|
||||
val conf = AesConfig()
|
||||
|
||||
"aes crypto" should {
|
||||
"work with IV" in {
|
||||
val pass = ByteVector("pass".getBytes())
|
||||
val crypt = AesCrypt.forString[Try](pass, withIV = true, config = conf)
|
||||
|
||||
val str = rndString(200)
|
||||
val crypted = crypt.encrypt(str).get
|
||||
crypt.decrypt(crypted).get shouldBe str
|
||||
|
||||
val fakeAes = AesCrypt.forString[Try](ByteVector("wrong".getBytes()), withIV = true, config = conf)
|
||||
checkCryptoError(fakeAes.decrypt(crypted), str)
|
||||
|
||||
//we cannot check if first bytes is iv or already data, but encryption goes wrong
|
||||
val aesWithoutIV = AesCrypt.forString[Try](pass, withIV = false, config = conf)
|
||||
aesWithoutIV.decrypt(crypted).get shouldNot be (str)
|
||||
|
||||
val aesWrongSalt = AesCrypt.forString[Try](pass, withIV = true, config = conf.copy(salt = rndString(10)))
|
||||
checkCryptoError(aesWrongSalt.decrypt(crypted), str)
|
||||
}
|
||||
|
||||
"work without IV" in {
|
||||
val pass = ByteVector("pass".getBytes())
|
||||
val crypt = AesCrypt.forString[Try](pass, withIV = false, config = conf)
|
||||
|
||||
val str = rndString(200)
|
||||
val crypted = crypt.encrypt(str).get
|
||||
crypt.decrypt(crypted).get shouldBe str
|
||||
|
||||
val fakeAes = AesCrypt.forString[Try](ByteVector("wrong".getBytes()), withIV = false, config = conf)
|
||||
checkCryptoError(fakeAes.decrypt(crypted), str)
|
||||
|
||||
//we cannot check if first bytes is iv or already data, but encryption goes wrong
|
||||
val aesWithIV = AesCrypt.forString[Try](pass, withIV = true, config = conf)
|
||||
aesWithIV.decrypt(crypted).get shouldNot be (str)
|
||||
|
||||
val aesWrongSalt = AesCrypt.forString[Try](pass, withIV = false, config = conf.copy(salt = rndString(10)))
|
||||
checkCryptoError(aesWrongSalt.decrypt(crypted), str)
|
||||
}
|
||||
|
||||
def checkCryptoError(tr: Try[String], msg: String)(implicit pos: Position): Assertion = {
|
||||
tr.map{ r ⇒ r != msg }.recover {
|
||||
case e: CryptoErr ⇒ true
|
||||
case e ⇒
|
||||
logger.error("Unexpected error", e)
|
||||
false
|
||||
}.get shouldBe true
|
||||
}
|
||||
}
|
||||
}
|
@ -19,13 +19,13 @@ package fluence.crypto
|
||||
|
||||
import cats.data.EitherT
|
||||
import cats.instances.try_._
|
||||
import fluence.crypto.algorithm.{ CryptoErr, EcdsaJS }
|
||||
import fluence.crypto.algorithm.{ CryptoErr, Ecdsa }
|
||||
import org.scalatest.{ Matchers, WordSpec }
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.util.{ Random, Try }
|
||||
|
||||
class EcdsaJSSpec extends WordSpec with Matchers {
|
||||
class EcdsaSpec extends WordSpec with Matchers {
|
||||
|
||||
def rndBytes(size: Int) = Random.nextString(10).getBytes
|
||||
|
||||
@ -42,7 +42,7 @@ class EcdsaJSSpec extends WordSpec with Matchers {
|
||||
|
||||
"ecdsa algorithm" should {
|
||||
"correct sign and verify data" in {
|
||||
val algorithm = EcdsaJS.ecdsa_secp256k1_sha256
|
||||
val algorithm = Ecdsa.ecdsa_secp256k1_sha256
|
||||
|
||||
val keys = algorithm.generateKeyPair[Try]().extract
|
||||
val data = rndByteVector(10)
|
||||
@ -59,7 +59,7 @@ class EcdsaJSSpec extends WordSpec with Matchers {
|
||||
}
|
||||
|
||||
"correctly work with signer and checker" in {
|
||||
val algo = EcdsaJS.signAlgo
|
||||
val algo = Ecdsa.signAlgo
|
||||
val keys = algo.generateKeyPair().extract
|
||||
val signer = algo.signer(keys)
|
||||
|
||||
@ -73,7 +73,7 @@ class EcdsaJSSpec extends WordSpec with Matchers {
|
||||
}
|
||||
|
||||
"throw an errors on invalid data" in {
|
||||
val algo = EcdsaJS.signAlgo
|
||||
val algo = Ecdsa.signAlgo
|
||||
val keys = algo.generateKeyPair().extract
|
||||
val signer = algo.signer(keys)
|
||||
val data = rndByteVector(10)
|
@ -1,6 +1,6 @@
|
||||
package fluence.crypto
|
||||
|
||||
import fluence.crypto.facade.{ SHA1, SHA256 }
|
||||
import fluence.crypto.facade.ecdsa.{ SHA1, SHA256 }
|
||||
import org.scalatest.{ Matchers, WordSpec }
|
||||
|
||||
import scala.scalajs.js.JSConverters._
|
||||
|
@ -17,22 +17,19 @@
|
||||
|
||||
package fluence.crypto.algorithm
|
||||
|
||||
import cats.{ Applicative, Monad, MonadError }
|
||||
import cats.data.EitherT
|
||||
import cats.syntax.flatMap._
|
||||
import cats.syntax.applicative._
|
||||
import cats.syntax.flatMap._
|
||||
import cats.{ Applicative, Monad, MonadError }
|
||||
import fluence.codec.Codec
|
||||
import fluence.crypto.cipher.Crypt
|
||||
import org.bouncycastle.crypto.CipherParameters
|
||||
import org.bouncycastle.crypto.{ CipherParameters, PBEParametersGenerator }
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest
|
||||
import org.bouncycastle.crypto.engines.AESEngine
|
||||
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator
|
||||
import org.bouncycastle.crypto.modes.CBCBlockCipher
|
||||
import org.bouncycastle.crypto.paddings.PKCS7Padding
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher
|
||||
import org.bouncycastle.crypto.params.KeyParameter
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.PBEKeySpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import org.bouncycastle.crypto.paddings.{ PKCS7Padding, PaddedBufferedBlockCipher }
|
||||
import org.bouncycastle.crypto.params.{ KeyParameter, ParametersWithIV }
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.language.higherKinds
|
||||
@ -61,7 +58,6 @@ class AesCrypt[F[_] : Monad, T](password: Array[Char], withIV: Boolean, config:
|
||||
private val salt = config.salt.getBytes()
|
||||
|
||||
//number of password hashing iterations
|
||||
//todo should be configurable
|
||||
private val iterationCount = config.iterationCount
|
||||
//initialisation vector must be the same length as block size
|
||||
private val IV_SIZE = 16
|
||||
@ -76,18 +72,8 @@ class AesCrypt[F[_] : Monad, T](password: Array[Char], withIV: Boolean, config:
|
||||
val e = for {
|
||||
data ← EitherT.liftF(codec.encode(plainText))
|
||||
key ← initSecretKey(password, salt)
|
||||
(extData, params) = {
|
||||
if (withIV) {
|
||||
val ivData = generateIV
|
||||
|
||||
// setup cipher parameters with key and IV
|
||||
val keyParam = new KeyParameter(key)
|
||||
(Some(ivData), new ParametersWithIV(keyParam, ivData))
|
||||
} else {
|
||||
(None, new KeyParameter(key))
|
||||
}
|
||||
}
|
||||
encData ← processData(DataWithParams(data, params), extData, encrypt = true)
|
||||
extDataWithParams ← extDataWithParams(key)
|
||||
encData ← processData(DataWithParams(data, extDataWithParams._2), extDataWithParams._1, encrypt = true)
|
||||
} yield encData
|
||||
|
||||
e.value.flatMap(ME.fromEither)
|
||||
@ -103,18 +89,28 @@ class AesCrypt[F[_] : Monad, T](password: Array[Char], withIV: Boolean, config:
|
||||
e.value.flatMap(ME.fromEither)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate key parameters with IV if it is necessary
|
||||
* @param key Password
|
||||
* @return Optional IV and cipher parameters
|
||||
*/
|
||||
def extDataWithParams(key: Array[Byte]): EitherT[F, CryptoErr, (Option[Array[Byte]], CipherParameters)] = {
|
||||
if (withIV) {
|
||||
val ivData = generateIV
|
||||
|
||||
// setup cipher parameters with key and IV
|
||||
paramsWithIV(key, ivData).map(k ⇒ (Some(ivData), k))
|
||||
} else {
|
||||
params(key).map(k ⇒ (None, k))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key spec initialization
|
||||
*/
|
||||
private def initSecretKey(password: Array[Char], salt: Array[Byte]): EitherT[F, CryptoErr, Array[Byte]] = {
|
||||
nonFatalHandling {
|
||||
// get raw key from password and salt
|
||||
val pbeKeySpec = new PBEKeySpec(password, salt, iterationCount, BITS)
|
||||
val keyFactory: SecretKeyFactory = SecretKeyFactory.getInstance("PBEWithSHA256And256BitAES-CBC-BC")
|
||||
val secretKey = new SecretKeySpec(keyFactory.generateSecret(pbeKeySpec).getEncoded, "AES")
|
||||
secretKey.getEncoded
|
||||
}("Cannot init secret key.")
|
||||
}
|
||||
private def initSecretKey(password: Array[Char], salt: Array[Byte]): EitherT[F, CryptoErr, Array[Byte]] = nonFatalHandling {
|
||||
PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password)
|
||||
}("Cannot init secret key.")
|
||||
|
||||
/**
|
||||
* Setup AES CBC cipher
|
||||
@ -170,20 +166,34 @@ class AesCrypt[F[_] : Monad, T](password: Array[Char], withIV: Boolean, config:
|
||||
}("Cannot detach data and IV.")
|
||||
}
|
||||
|
||||
private def paramsWithIV(key: Array[Byte], iv: Array[Byte]): EitherT[F, CryptoErr, ParametersWithIV] = {
|
||||
params(key).flatMap { keyParam ⇒
|
||||
nonFatalHandling(new ParametersWithIV(keyParam, iv))("Cannot generate key parameters with IV")
|
||||
}
|
||||
}
|
||||
|
||||
private def params(key: Array[Byte]): EitherT[F, CryptoErr, KeyParameter] = {
|
||||
nonFatalHandling {
|
||||
val pGen = new PKCS5S2ParametersGenerator(new SHA256Digest)
|
||||
pGen.init(key, salt, iterationCount)
|
||||
|
||||
pGen.generateDerivedParameters(BITS).asInstanceOf[KeyParameter]
|
||||
}("Cannot generate key parameters")
|
||||
}
|
||||
|
||||
private def detachDataAndGetParams(data: Array[Byte], password: Array[Char], salt: Array[Byte], withIV: Boolean): EitherT[F, CryptoErr, DataWithParams] = {
|
||||
if (withIV) {
|
||||
for {
|
||||
ivDataWithEncData ← detachIV(data, IV_SIZE)
|
||||
key ← initSecretKey(password, salt)
|
||||
// setup cipher parameters with key and IV
|
||||
keyParam = new KeyParameter(key)
|
||||
params = new ParametersWithIV(keyParam, ivDataWithEncData.ivData)
|
||||
} yield DataWithParams(ivDataWithEncData.encData, params)
|
||||
paramsWithIV ← paramsWithIV(key, ivDataWithEncData.ivData)
|
||||
} yield DataWithParams(ivDataWithEncData.encData, paramsWithIV)
|
||||
} else {
|
||||
for {
|
||||
key ← initSecretKey(password, salt)
|
||||
// setup cipher parameters with key
|
||||
params = new KeyParameter(key)
|
||||
params ← params(key)
|
||||
} yield DataWithParams(data, params)
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ class AesSpec extends WordSpec with Matchers with slogging.LazyLogging {
|
||||
val str = rndString(200)
|
||||
val crypted = crypt.encrypt(str).get
|
||||
crypt.decrypt(crypted).get shouldBe str
|
||||
|
||||
val fakeAes = AesCrypt.forString[Try](ByteVector("wrong".getBytes()), withIV = true, config = conf)
|
||||
checkCryptoError(fakeAes.decrypt(crypted))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user