diff --git a/js/src/main/scala/fluence/crypto/algorithm/AesCrypt.scala b/js/src/main/scala/fluence/crypto/algorithm/AesCrypt.scala
new file mode 100644
index 0000000..300782d
--- /dev/null
+++ b/js/src/main/scala/fluence/crypto/algorithm/AesCrypt.scala
@@ -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 .
+ */
+
+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)
+}
diff --git a/js/src/main/scala/fluence/crypto/algorithm/EcdsaJS.scala b/js/src/main/scala/fluence/crypto/algorithm/Ecdsa.scala
similarity index 90%
rename from js/src/main/scala/fluence/crypto/algorithm/EcdsaJS.scala
rename to js/src/main/scala/fluence/crypto/algorithm/Ecdsa.scala
index 86ebc39..e6b637c 100644
--- a/js/src/main/scala/fluence/crypto/algorithm/EcdsaJS.scala
+++ b/js/src/main/scala/fluence/crypto/algorithm/Ecdsa.scala
@@ -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)
}
diff --git a/js/src/main/scala/fluence/crypto/facade/cryptojs/AES.scala b/js/src/main/scala/fluence/crypto/facade/cryptojs/AES.scala
new file mode 100644
index 0000000..2ce8368
--- /dev/null
+++ b/js/src/main/scala/fluence/crypto/facade/cryptojs/AES.scala
@@ -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 .
+ */
+
+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
+}
diff --git a/js/src/main/scala/fluence/crypto/facade/cryptojs/Algos.scala b/js/src/main/scala/fluence/crypto/facade/cryptojs/Algos.scala
new file mode 100644
index 0000000..3bd19ba
--- /dev/null
+++ b/js/src/main/scala/fluence/crypto/facade/cryptojs/Algos.scala
@@ -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 .
+ */
+
+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
diff --git a/js/src/main/scala/fluence/crypto/facade/cryptojs/CryptOptions.scala b/js/src/main/scala/fluence/crypto/facade/cryptojs/CryptOptions.scala
new file mode 100644
index 0000000..6cb9f42
--- /dev/null
+++ b/js/src/main/scala/fluence/crypto/facade/cryptojs/CryptOptions.scala
@@ -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 .
+ */
+
+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]
+ }
+
+ }
+}
diff --git a/js/src/main/scala/fluence/crypto/facade/cryptojs/CryptoJS.scala b/js/src/main/scala/fluence/crypto/facade/cryptojs/CryptoJS.scala
new file mode 100644
index 0000000..7c86134
--- /dev/null
+++ b/js/src/main/scala/fluence/crypto/facade/cryptojs/CryptoJS.scala
@@ -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 .
+ */
+
+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
+}
diff --git a/js/src/main/scala/fluence/crypto/facade/cryptojs/Enc.scala b/js/src/main/scala/fluence/crypto/facade/cryptojs/Enc.scala
new file mode 100644
index 0000000..734f3ef
--- /dev/null
+++ b/js/src/main/scala/fluence/crypto/facade/cryptojs/Enc.scala
@@ -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 .
+ */
+
+package fluence.crypto.facade.cryptojs
+
+import scala.scalajs.js
+
+@js.native
+trait Enc extends js.Object {
+ def Hex: Hex = js.native
+}
diff --git a/js/src/main/scala/fluence/crypto/facade/cryptojs/Hex.scala b/js/src/main/scala/fluence/crypto/facade/cryptojs/Hex.scala
new file mode 100644
index 0000000..a810efc
--- /dev/null
+++ b/js/src/main/scala/fluence/crypto/facade/cryptojs/Hex.scala
@@ -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 .
+ */
+
+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
+}
diff --git a/js/src/main/scala/fluence/crypto/facade/cryptojs/Key.scala b/js/src/main/scala/fluence/crypto/facade/cryptojs/Key.scala
new file mode 100644
index 0000000..c51eb90
--- /dev/null
+++ b/js/src/main/scala/fluence/crypto/facade/cryptojs/Key.scala
@@ -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 .
+ */
+
+package fluence.crypto.facade.cryptojs
+
+import scala.scalajs.js
+
+@js.native
+trait Key extends js.Object
diff --git a/js/src/main/scala/fluence/crypto/facade/cryptojs/KeyOptions.scala b/js/src/main/scala/fluence/crypto/facade/cryptojs/KeyOptions.scala
new file mode 100644
index 0000000..f4281b0
--- /dev/null
+++ b/js/src/main/scala/fluence/crypto/facade/cryptojs/KeyOptions.scala
@@ -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 .
+ */
+
+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]
+ }
+}
diff --git a/js/src/main/scala/fluence/crypto/facade/cryptojs/Lib.scala b/js/src/main/scala/fluence/crypto/facade/cryptojs/Lib.scala
new file mode 100644
index 0000000..b70d3f1
--- /dev/null
+++ b/js/src/main/scala/fluence/crypto/facade/cryptojs/Lib.scala
@@ -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 .
+ */
+
+package fluence.crypto.facade.cryptojs
+
+import scala.scalajs.js
+
+@js.native
+trait Lib extends js.Object {
+ def WordArray: WordArrayFactory = js.native
+}
diff --git a/js/src/main/scala/fluence/crypto/facade/cryptojs/Mode.scala b/js/src/main/scala/fluence/crypto/facade/cryptojs/Mode.scala
new file mode 100644
index 0000000..0f0bbbc
--- /dev/null
+++ b/js/src/main/scala/fluence/crypto/facade/cryptojs/Mode.scala
@@ -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 .
+ */
+
+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
diff --git a/js/src/main/scala/fluence/crypto/facade/cryptojs/Pad.scala b/js/src/main/scala/fluence/crypto/facade/cryptojs/Pad.scala
new file mode 100644
index 0000000..1c8fef3
--- /dev/null
+++ b/js/src/main/scala/fluence/crypto/facade/cryptojs/Pad.scala
@@ -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 .
+ */
+
+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
diff --git a/js/src/main/scala/fluence/crypto/facade/cryptojs/WordArrayFactory.scala b/js/src/main/scala/fluence/crypto/facade/cryptojs/WordArrayFactory.scala
new file mode 100644
index 0000000..259f98c
--- /dev/null
+++ b/js/src/main/scala/fluence/crypto/facade/cryptojs/WordArrayFactory.scala
@@ -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 .
+ */
+
+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
diff --git a/js/src/main/scala/fluence/crypto/facade/Elliptic.scala b/js/src/main/scala/fluence/crypto/facade/ecdsa/Elliptic.scala
similarity index 98%
rename from js/src/main/scala/fluence/crypto/facade/Elliptic.scala
rename to js/src/main/scala/fluence/crypto/facade/ecdsa/Elliptic.scala
index 93e9557..96bf4b3 100644
--- a/js/src/main/scala/fluence/crypto/facade/Elliptic.scala
+++ b/js/src/main/scala/fluence/crypto/facade/ecdsa/Elliptic.scala
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package fluence.crypto.facade
+package fluence.crypto.facade.ecdsa
import scala.scalajs.js
import scala.scalajs.js.annotation._
diff --git a/js/src/main/scala/fluence/crypto/facade/Hash.scala b/js/src/main/scala/fluence/crypto/facade/ecdsa/Hash.scala
similarity index 97%
rename from js/src/main/scala/fluence/crypto/facade/Hash.scala
rename to js/src/main/scala/fluence/crypto/facade/ecdsa/Hash.scala
index 6a569a5..8ad9503 100644
--- a/js/src/main/scala/fluence/crypto/facade/Hash.scala
+++ b/js/src/main/scala/fluence/crypto/facade/ecdsa/Hash.scala
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package fluence.crypto.facade
+package fluence.crypto.facade.ecdsa
import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport
diff --git a/js/src/main/scala/fluence/crypto/hash/JsCryptoHasher.scala b/js/src/main/scala/fluence/crypto/hash/JsCryptoHasher.scala
index 33ea851..a1c9b19 100644
--- a/js/src/main/scala/fluence/crypto/hash/JsCryptoHasher.scala
+++ b/js/src/main/scala/fluence/crypto/hash/JsCryptoHasher.scala
@@ -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._
diff --git a/js/src/test/scala/fluence/crypto/AesJSSpec.scala b/js/src/test/scala/fluence/crypto/AesJSSpec.scala
new file mode 100644
index 0000000..967b97d
--- /dev/null
+++ b/js/src/test/scala/fluence/crypto/AesJSSpec.scala
@@ -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
+ }
+ }
+}
diff --git a/js/src/test/scala/fluence/crypto/EcdsaJSSpec.scala b/js/src/test/scala/fluence/crypto/EcdsaSpec.scala
similarity index 92%
rename from js/src/test/scala/fluence/crypto/EcdsaJSSpec.scala
rename to js/src/test/scala/fluence/crypto/EcdsaSpec.scala
index 70921c4..d0b2fcb 100644
--- a/js/src/test/scala/fluence/crypto/EcdsaJSSpec.scala
+++ b/js/src/test/scala/fluence/crypto/EcdsaSpec.scala
@@ -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)
diff --git a/js/src/test/scala/fluence/crypto/JSHashSpec.scala b/js/src/test/scala/fluence/crypto/JSHashSpec.scala
index 6ef1d13..e1de2c0 100644
--- a/js/src/test/scala/fluence/crypto/JSHashSpec.scala
+++ b/js/src/test/scala/fluence/crypto/JSHashSpec.scala
@@ -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._
diff --git a/jvm/src/main/scala/fluence/crypto/algorithm/AesCrypt.scala b/jvm/src/main/scala/fluence/crypto/algorithm/AesCrypt.scala
index ef0d2c9..b274e08 100644
--- a/jvm/src/main/scala/fluence/crypto/algorithm/AesCrypt.scala
+++ b/jvm/src/main/scala/fluence/crypto/algorithm/AesCrypt.scala
@@ -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)
}
}
diff --git a/jvm/src/test/scala/fluence/crypto/AesSpec.scala b/jvm/src/test/scala/fluence/crypto/AesSpec.scala
index 91200e1..36ea20c 100644
--- a/jvm/src/test/scala/fluence/crypto/AesSpec.scala
+++ b/jvm/src/test/scala/fluence/crypto/AesSpec.scala
@@ -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))
diff --git a/jvm/src/main/scala/fluence/crypto/algorithm/AesConfig.scala b/src/main/scala/fluence/crypto/algorithm/AesConfig.scala
similarity index 100%
rename from jvm/src/main/scala/fluence/crypto/algorithm/AesConfig.scala
rename to src/main/scala/fluence/crypto/algorithm/AesConfig.scala