crypto-refactoring (#111)

* Create crypto-core module

* Build.sbt deps fixed

* crypto-core WIP

* CryptoHasher removed

* Cipher removed

* NetworkSimulationSpec fixed

* SecureRandom returned to DumbCrypto

* keystore and algorithm modules

* hashsign and cipher modules

* compile bugs fixed

* CipherSearch.binarySearch doc comment reverted
This commit is contained in:
Dmitry Kurinskiy 2018-04-29 16:28:48 +03:00 committed by GitHub
parent dc8806de44
commit 88034b371f
56 changed files with 548 additions and 901 deletions

View File

@ -15,26 +15,22 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto.algorithm package fluence.crypto.aes
import cats.Monad
import cats.data.EitherT import cats.data.EitherT
import cats.{Applicative, Monad, MonadError} import cats.syntax.compose._
import cats.syntax.applicative._ import fluence.codec.PureCodec
import cats.syntax.flatMap._ import fluence.crypto.CryptoError.nonFatalHandling
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 fluence.crypto.facade.cryptojs.{CryptOptions, CryptoJS, Key, KeyOptions}
import fluence.crypto.{Crypto, CryptoError}
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scalajs.js.JSConverters._
import scala.language.higherKinds import scala.language.higherKinds
import scala.scalajs.js.JSConverters._
import scala.scalajs.js.typedarray.Int8Array import scala.scalajs.js.typedarray.Int8Array
class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: AesConfig)( class AesCrypt(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 salt = config.salt
@ -52,27 +48,25 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
private val mode = CryptoJS.mode.CBC private val mode = CryptoJS.mode.CBC
private val aes = CryptoJS.AES private val aes = CryptoJS.AES
override def encrypt(plainText: T): F[Array[Byte]] = { val encrypt: Crypto.Func[Array[Byte], Array[Byte]] =
val e = for { new Crypto.Func[Array[Byte], Array[Byte]] {
data EitherT.liftF(codec.encode(plainText)) override def apply[F[_]: Monad](input: Array[Byte]): EitherT[F, CryptoError, Array[Byte]] =
for {
key initSecretKey() key initSecretKey()
encrypted encryptData(data, key) encrypted encryptData(input, key)
} yield encrypted } yield encrypted
e.value.flatMap(ME.fromEither)
} }
override def decrypt(cipherText: Array[Byte]): F[T] = { val decrypt: Crypto.Func[Array[Byte], Array[Byte]] =
val e = for { new Crypto.Func[Array[Byte], Array[Byte]] {
detachedData detachData(cipherText) override def apply[F[_]: Monad](input: Array[Byte]): EitherT[F, CryptoError, Array[Byte]] =
for {
detachedData detachData(input)
(iv, base64) = detachedData (iv, base64) = detachedData
key initSecretKey() key initSecretKey()
decData decryptData(key, base64, iv) decData decryptData(key, base64, iv)
_ EitherT.cond(decData.nonEmpty, decData, CryptoErr("Cannot decrypt message with this password.")) _ EitherT.cond(decData.nonEmpty, decData, CryptoError("Cannot decrypt message with this password."))
plain EitherT.liftF[F, CryptoErr, T](codec.decode(decData.toArray)) } yield decData.toArray
} yield plain
e.value.flatMap(ME.fromEither)
} }
/** /**
@ -81,7 +75,7 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
* @param key Salted and hashed password * @param key Salted and hashed password
* @return Encrypted data with IV * @return Encrypted data with IV
*/ */
private def encryptData(data: Array[Byte], key: Key): EitherT[F, CryptoErr, Array[Byte]] = { private def encryptData[F[_]: Monad](data: Array[Byte], key: Key): EitherT[F, CryptoError, Array[Byte]] = {
nonFatalHandling { nonFatalHandling {
//transform data to JS type //transform data to JS type
val wordArray = CryptoJS.lib.WordArray.create(new Int8Array(data.toJSArray)) val wordArray = CryptoJS.lib.WordArray.create(new Int8Array(data.toJSArray))
@ -95,7 +89,7 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
}("Cannot encrypt data.") }("Cannot encrypt data.")
} }
private def decryptData(key: Key, base64Data: String, iv: Option[String]) = { private def decryptData[F[_]: Monad](key: Key, base64Data: String, iv: Option[String]) = {
nonFatalHandling { nonFatalHandling {
//parse IV to WordArray JS format //parse IV to WordArray JS format
val cryptOptions = CryptOptions(iv = iv.map(i CryptoJS.enc.Hex.parse(i)), padding = pad, mode = mode) val cryptOptions = CryptOptions(iv = iv.map(i CryptoJS.enc.Hex.parse(i)), padding = pad, mode = mode)
@ -108,7 +102,7 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
* @param cipherText Encrypted data with IV * @param cipherText Encrypted data with IV
* @return IV in hex and data in base64 * @return IV in hex and data in base64
*/ */
private def detachData(cipherText: Array[Byte]): EitherT[F, CryptoErr, (Option[String], String)] = { private def detachData[F[_]: Monad](cipherText: Array[Byte]): EitherT[F, CryptoError, (Option[String], String)] = {
nonFatalHandling { nonFatalHandling {
val dataWithParams = if (withIV) { val dataWithParams = if (withIV) {
val ivDec = ByteVector(cipherText.slice(0, IV_SIZE)).toHex val ivDec = ByteVector(cipherText.slice(0, IV_SIZE)).toHex
@ -124,7 +118,7 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
/** /**
* Hash password with salt `iterationCount` times * Hash password with salt `iterationCount` times
*/ */
private def initSecretKey(): EitherT[F, CryptoErr, Key] = { private def initSecretKey[F[_]: Monad](): EitherT[F, CryptoError, Key] = {
nonFatalHandling { nonFatalHandling {
// get raw key from password and salt // get raw key from password and salt
val keyOption = KeyOptions(BITS, iterations = iterationCount, hasher = CryptoJS.algo.SHA256) val keyOption = KeyOptions(BITS, iterations = iterationCount, hasher = CryptoJS.algo.SHA256)
@ -135,17 +129,19 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
object AesCrypt extends slogging.LazyLogging { object AesCrypt extends slogging.LazyLogging {
def forString[F[_]: Applicative](password: ByteVector, withIV: Boolean, config: AesConfig)( def build(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[Array[Byte]] = {
implicit ME: MonadError[F, Throwable] val aes = new AesCrypt(password.toHex.toCharArray, withIV, config)
): AesCrypt[F, String] = { Crypto.Bijection(aes.encrypt, aes.decrypt)
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)( def forString(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[String] = {
implicit ME: MonadError[F, Throwable], implicit val codec: PureCodec[String, Array[Byte]] =
codec: Codec[F, T, Array[Byte]] PureCodec.build(_.getBytes, bytes new String(bytes))
): AesCrypt[F, T] = apply[String](password, withIV, config)
new AesCrypt(password.toHex.toCharArray, withIV, config) }
def apply[T](password: ByteVector, withIV: Boolean, config: AesConfig)(
implicit codec: PureCodec[T, Array[Byte]]
): Crypto.Cipher[T] =
Crypto.codec[T, Array[Byte]] andThen build(password, withIV, config)
} }

View File

@ -18,7 +18,7 @@
package fluence.crypto package fluence.crypto
import cats.instances.try_._ import cats.instances.try_._
import fluence.crypto.algorithm.{AesConfig, AesCrypt, CryptoErr} import fluence.crypto.aes.{AesConfig, AesCrypt}
import org.scalactic.source.Position import org.scalactic.source.Position
import org.scalatest.{Assertion, Matchers, WordSpec} import org.scalatest.{Assertion, Matchers, WordSpec}
import scodec.bits.ByteVector import scodec.bits.ByteVector
@ -31,50 +31,51 @@ class AesJSSpec extends WordSpec with Matchers with slogging.LazyLogging {
val conf = AesConfig() val conf = AesConfig()
// TODO: use properties testing
"aes crypto" should { "aes crypto" should {
"work with IV" in { "work with IV" in {
val pass = ByteVector("pass".getBytes()) val pass = ByteVector("pass".getBytes())
val crypt = AesCrypt.forString[Try](pass, withIV = true, config = conf) val crypt = AesCrypt.forString(pass, withIV = true, config = conf)
val str = rndString(200) val str = rndString(200)
val crypted = crypt.encrypt(str).get val crypted = crypt.direct.unsafe(str)
crypt.decrypt(crypted).get shouldBe str crypt.inverse.unsafe(crypted) shouldBe str
val fakeAes = AesCrypt.forString[Try](ByteVector("wrong".getBytes()), withIV = true, config = conf) val fakeAes = AesCrypt.forString(ByteVector("wrong".getBytes()), withIV = true, config = conf)
checkCryptoError(fakeAes.decrypt(crypted), str) checkCryptoError(fakeAes.inverse.runF[Try](crypted), str)
//we cannot check if first bytes is iv or already data, but encryption goes wrong //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) val aesWithoutIV = AesCrypt.forString(pass, withIV = false, config = conf)
aesWithoutIV.decrypt(crypted).get shouldNot be(str) aesWithoutIV.inverse.unsafe(crypted) shouldNot be(str)
val aesWrongSalt = AesCrypt.forString[Try](pass, withIV = true, config = conf.copy(salt = rndString(10))) val aesWrongSalt = AesCrypt.forString(pass, withIV = true, config = conf.copy(salt = rndString(10)))
checkCryptoError(aesWrongSalt.decrypt(crypted), str) checkCryptoError(aesWrongSalt.inverse.runF[Try](crypted), str)
} }
"work without IV" in { "work without IV" in {
val pass = ByteVector("pass".getBytes()) val pass = ByteVector("pass".getBytes())
val crypt = AesCrypt.forString[Try](pass, withIV = false, config = conf) val crypt = AesCrypt.forString(pass, withIV = false, config = conf)
val str = rndString(200) val str = rndString(200)
val crypted = crypt.encrypt(str).get val crypted = crypt.direct.unsafe(str)
crypt.decrypt(crypted).get shouldBe str crypt.inverse.unsafe(crypted) shouldBe str
val fakeAes = AesCrypt.forString[Try](ByteVector("wrong".getBytes()), withIV = false, config = conf) val fakeAes = AesCrypt.forString(ByteVector("wrong".getBytes()), withIV = false, config = conf)
checkCryptoError(fakeAes.decrypt(crypted), str) checkCryptoError(fakeAes.inverse.runF[Try](crypted), str)
//we cannot check if first bytes is iv or already data, but encryption goes wrong //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) val aesWithIV = AesCrypt.forString(pass, withIV = true, config = conf)
aesWithIV.decrypt(crypted).get shouldNot be(str) aesWithIV.inverse.unsafe(crypted) shouldNot be(str)
val aesWrongSalt = AesCrypt.forString[Try](pass, withIV = false, config = conf.copy(salt = rndString(10))) val aesWrongSalt = AesCrypt.forString(pass, withIV = false, config = conf.copy(salt = rndString(10)))
checkCryptoError(aesWrongSalt.decrypt(crypted), str) checkCryptoError(aesWrongSalt.inverse.runF[Try](crypted), str)
} }
def checkCryptoError(tr: Try[String], msg: String)(implicit pos: Position): Assertion = { def checkCryptoError(tr: Try[String], msg: String)(implicit pos: Position): Assertion = {
tr.map { r tr.map { r
r != msg r != msg
}.recover { }.recover {
case e: CryptoErr true case e: CryptoError true
case e case e
logger.error("Unexpected error", e) logger.error("Unexpected error", e)
false false

View File

@ -15,20 +15,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto.algorithm package fluence.crypto.aes
import cats.Monad
import cats.data.EitherT import cats.data.EitherT
import cats.syntax.flatMap._ import cats.syntax.compose._
import cats.{Applicative, Monad, MonadError}
import fluence.codec.PureCodec import fluence.codec.PureCodec
import fluence.crypto.cipher.Crypt import fluence.crypto.{Crypto, CryptoError, JavaAlgorithm}
import org.bouncycastle.crypto.{CipherParameters, PBEParametersGenerator}
import org.bouncycastle.crypto.digests.SHA256Digest import org.bouncycastle.crypto.digests.SHA256Digest
import org.bouncycastle.crypto.engines.AESEngine import org.bouncycastle.crypto.engines.AESEngine
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator
import org.bouncycastle.crypto.modes.CBCBlockCipher import org.bouncycastle.crypto.modes.CBCBlockCipher
import org.bouncycastle.crypto.paddings.{PKCS7Padding, PaddedBufferedBlockCipher} import org.bouncycastle.crypto.paddings.{PKCS7Padding, PaddedBufferedBlockCipher}
import org.bouncycastle.crypto.params.{KeyParameter, ParametersWithIV} import org.bouncycastle.crypto.params.{KeyParameter, ParametersWithIV}
import org.bouncycastle.crypto.{CipherParameters, PBEParametersGenerator}
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.language.higherKinds import scala.language.higherKinds
@ -49,11 +49,8 @@ case class DataWithParams(data: Array[Byte], params: CipherParameters)
* under the same key does not allow an attacker to infer relationships between segments of the encrypted * under the same key does not allow an attacker to infer relationships between segments of the encrypted
* message * message
*/ */
class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: AesConfig)( class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) extends JavaAlgorithm {
implicit ME: MonadError[F, Throwable], import CryptoError.nonFatalHandling
codec: PureCodec[T, Array[Byte]]
) extends Crypt[F, T, Array[Byte]] with JavaAlgorithm {
import CryptoErr._
private val rnd = Random private val rnd = Random
private val salt = config.salt.getBytes() private val salt = config.salt.getBytes()
@ -69,27 +66,24 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
iv iv
} }
override def encrypt(plainText: T): F[Array[Byte]] = { val encrypt: Crypto.Func[Array[Byte], Array[Byte]] =
val e = for { new Crypto.Func[Array[Byte], Array[Byte]] {
data codec override def apply[F[_]: Monad](input: Array[Byte]): EitherT[F, CryptoError, Array[Byte]] =
.direct[F](plainText) for {
.leftMap(err CryptoErr("Cannot encode plain text to bytes to encrypt it", Some(err)))
key initSecretKey(password, salt) key initSecretKey(password, salt)
extDataWithParams extDataWithParams(key) extDataWithParams extDataWithParams(key)
encData processData(DataWithParams(data, extDataWithParams._2), extDataWithParams._1, encrypt = true) encData processData(DataWithParams(input, extDataWithParams._2), extDataWithParams._1, encrypt = true)
} yield encData } yield encData
e.value.flatMap(ME.fromEither)
} }
override def decrypt(cipherText: Array[Byte]): F[T] = { val decrypt: Crypto.Func[Array[Byte], Array[Byte]] =
val e = for { new Crypto.Func[Array[Byte], Array[Byte]] {
dataWithParams detachDataAndGetParams(cipherText, password, salt, withIV) override def apply[F[_]: Monad](input: Array[Byte]): EitherT[F, CryptoError, Array[Byte]] =
for {
dataWithParams detachDataAndGetParams(input, password, salt, withIV)
decData processData(dataWithParams, None, encrypt = false) decData processData(dataWithParams, None, encrypt = false)
plain codec.inverse[F](decData).leftMap(err CryptoErr("Cannot decode decrypted text", Some(err))) } yield decData
} yield plain
e.value.flatMap(ME.fromEither)
} }
/** /**
@ -97,7 +91,9 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
* @param key Password * @param key Password
* @return Optional IV and cipher parameters * @return Optional IV and cipher parameters
*/ */
def extDataWithParams(key: Array[Byte]): EitherT[F, CryptoErr, (Option[Array[Byte]], CipherParameters)] = { def extDataWithParams[F[_]: Monad](
key: Array[Byte]
): EitherT[F, CryptoError, (Option[Array[Byte]], CipherParameters)] = {
if (withIV) { if (withIV) {
val ivData = generateIV val ivData = generateIV
@ -111,7 +107,10 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
/** /**
* Key spec initialization * Key spec initialization
*/ */
private def initSecretKey(password: Array[Char], salt: Array[Byte]): EitherT[F, CryptoErr, Array[Byte]] = private def initSecretKey[F[_]: Monad](
password: Array[Char],
salt: Array[Byte]
): EitherT[F, CryptoError, Array[Byte]] =
nonFatalHandling { nonFatalHandling {
PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password) PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password)
}("Cannot init secret key.") }("Cannot init secret key.")
@ -121,10 +120,10 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
* @param encrypt True for encryption and false for decryption * @param encrypt True for encryption and false for decryption
* @return cipher * @return cipher
*/ */
private def setupAesCipher( private def setupAesCipher[F[_]: Monad](
params: CipherParameters, params: CipherParameters,
encrypt: Boolean encrypt: Boolean
): EitherT[F, CryptoErr, PaddedBufferedBlockCipher] = { ): EitherT[F, CryptoError, PaddedBufferedBlockCipher] = {
nonFatalHandling { nonFatalHandling {
// setup AES cipher in CBC mode with PKCS7 padding // setup AES cipher in CBC mode with PKCS7 padding
val padding = new PKCS7Padding val padding = new PKCS7Padding
@ -136,7 +135,10 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
}("Cannot setup aes cipher.") }("Cannot setup aes cipher.")
} }
private def cipherBytes(data: Array[Byte], cipher: PaddedBufferedBlockCipher): EitherT[F, CryptoErr, Array[Byte]] = { private def cipherBytes[F[_]: Monad](
data: Array[Byte],
cipher: PaddedBufferedBlockCipher
): EitherT[F, CryptoError, Array[Byte]] = {
nonFatalHandling { nonFatalHandling {
// create a temporary buffer to decode into (it'll include padding) // create a temporary buffer to decode into (it'll include padding)
val buf = new Array[Byte](cipher.getOutputSize(data.length)) val buf = new Array[Byte](cipher.getOutputSize(data.length))
@ -154,11 +156,11 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
* @param encrypt True for encryption and false for decryption * @param encrypt True for encryption and false for decryption
* @return Crypted bytes * @return Crypted bytes
*/ */
private def processData( private def processData[F[_]: Monad](
dataWithParams: DataWithParams, dataWithParams: DataWithParams,
addData: Option[Array[Byte]], addData: Option[Array[Byte]],
encrypt: Boolean encrypt: Boolean
): EitherT[F, CryptoErr, Array[Byte]] = { ): EitherT[F, CryptoError, Array[Byte]] = {
for { for {
cipher setupAesCipher(dataWithParams.params, encrypt = encrypt) cipher setupAesCipher(dataWithParams.params, encrypt = encrypt)
buf cipherBytes(dataWithParams.data, cipher) buf cipherBytes(dataWithParams.data, cipher)
@ -169,7 +171,7 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
/** /**
* encrypted data = initialization vector + data * encrypted data = initialization vector + data
*/ */
private def detachIV(data: Array[Byte], ivSize: Int): EitherT[F, CryptoErr, DetachedData] = { private def detachIV[F[_]: Monad](data: Array[Byte], ivSize: Int): EitherT[F, CryptoError, DetachedData] = {
nonFatalHandling { nonFatalHandling {
val ivData = data.slice(0, ivSize) val ivData = data.slice(0, ivSize)
val encData = data.slice(ivSize, data.length) val encData = data.slice(ivSize, data.length)
@ -177,13 +179,16 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
}("Cannot detach data and IV.") }("Cannot detach data and IV.")
} }
private def paramsWithIV(key: Array[Byte], iv: Array[Byte]): EitherT[F, CryptoErr, ParametersWithIV] = { private def paramsWithIV[F[_]: Monad](
key: Array[Byte],
iv: Array[Byte]
): EitherT[F, CryptoError, ParametersWithIV] = {
params(key).flatMap { keyParam params(key).flatMap { keyParam
nonFatalHandling(new ParametersWithIV(keyParam, iv))("Cannot generate key parameters with IV") nonFatalHandling(new ParametersWithIV(keyParam, iv))("Cannot generate key parameters with IV")
} }
} }
private def params(key: Array[Byte]): EitherT[F, CryptoErr, KeyParameter] = { private def params[F[_]: Monad](key: Array[Byte]): EitherT[F, CryptoError, KeyParameter] = {
nonFatalHandling { nonFatalHandling {
val pGen = new PKCS5S2ParametersGenerator(new SHA256Digest) val pGen = new PKCS5S2ParametersGenerator(new SHA256Digest)
pGen.init(key, salt, iterationCount) pGen.init(key, salt, iterationCount)
@ -192,12 +197,12 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
}("Cannot generate key parameters") }("Cannot generate key parameters")
} }
private def detachDataAndGetParams( private def detachDataAndGetParams[F[_]: Monad](
data: Array[Byte], data: Array[Byte],
password: Array[Char], password: Array[Char],
salt: Array[Byte], salt: Array[Byte],
withIV: Boolean withIV: Boolean
): EitherT[F, CryptoErr, DataWithParams] = { ): EitherT[F, CryptoError, DataWithParams] = {
if (withIV) { if (withIV) {
for { for {
ivDataWithEncData detachIV(data, IV_SIZE) ivDataWithEncData detachIV(data, IV_SIZE)
@ -217,17 +222,19 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
object AesCrypt extends slogging.LazyLogging { object AesCrypt extends slogging.LazyLogging {
def forString[F[_]: Applicative](password: ByteVector, withIV: Boolean, config: AesConfig)( def build(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[Array[Byte]] = {
implicit ME: MonadError[F, Throwable] val aes = new AesCrypt(password.toHex.toCharArray, withIV, config)
): AesCrypt[F, String] = { Crypto.Bijection(aes.encrypt, aes.decrypt)
implicit val codec: PureCodec[String, Array[Byte]] =
PureCodec.liftB[String, Array[Byte]](_.getBytes, new String(_))
apply[F, String](password, withIV, config)
} }
def apply[F[_]: Applicative, T](password: ByteVector, withIV: Boolean, config: AesConfig)( def forString(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[String] = {
implicit ME: MonadError[F, Throwable], implicit val codec: PureCodec[String, Array[Byte]] =
codec: PureCodec[T, Array[Byte]] PureCodec.build(_.getBytes, bytes new String(bytes))
): AesCrypt[F, T] = apply[String](password, withIV, config)
new AesCrypt(password.toHex.toCharArray, withIV, config) }
def apply[T](password: ByteVector, withIV: Boolean, config: AesConfig)(
implicit codec: PureCodec[T, Array[Byte]]
): Crypto.Cipher[T] =
Crypto.codec[T, Array[Byte]] andThen build(password, withIV, config)
} }

View File

@ -17,8 +17,8 @@
package fluence.crypto package fluence.crypto
import fluence.crypto.algorithm.{AesConfig, AesCrypt, CryptoErr}
import cats.instances.try_._ import cats.instances.try_._
import fluence.crypto.aes.{AesConfig, AesCrypt}
import org.scalactic.source.Position import org.scalactic.source.Position
import org.scalatest.{Assertion, Matchers, WordSpec} import org.scalatest.{Assertion, Matchers, WordSpec}
import scodec.bits.ByteVector import scodec.bits.ByteVector
@ -34,46 +34,46 @@ class AesSpec extends WordSpec with Matchers with slogging.LazyLogging {
"work with IV" in { "work with IV" in {
val pass = ByteVector("pass".getBytes()) val pass = ByteVector("pass".getBytes())
val crypt = AesCrypt.forString[Try](pass, withIV = true, config = conf) val crypt = AesCrypt.forString(pass, withIV = true, config = conf)
val str = rndString(200) val str = rndString(200)
val crypted = crypt.encrypt(str).get val crypted = crypt.direct.unsafe(str)
crypt.decrypt(crypted).get shouldBe str crypt.inverse.unsafe(crypted) shouldBe str
val fakeAes = AesCrypt.forString[Try](ByteVector("wrong".getBytes()), withIV = true, config = conf) val fakeAes = AesCrypt.forString(ByteVector("wrong".getBytes()), withIV = true, config = conf)
checkCryptoError(fakeAes.decrypt(crypted)) checkCryptoError(fakeAes.inverse.runF[Try](crypted))
//we cannot check if first bytes is iv or already data, but encryption goes wrong //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) val aesWithoutIV = AesCrypt.forString(pass, withIV = false, config = conf)
aesWithoutIV.decrypt(crypted).get shouldNot be(str) aesWithoutIV.inverse.unsafe(crypted) shouldNot be(str)
val aesWrongSalt = AesCrypt.forString[Try](pass, withIV = true, config = conf.copy(salt = rndString(10))) val aesWrongSalt = AesCrypt.forString(pass, withIV = true, config = conf.copy(salt = rndString(10)))
checkCryptoError(aesWrongSalt.decrypt(crypted)) checkCryptoError(aesWrongSalt.inverse.runF[Try](crypted))
} }
"work without IV" in { "work without IV" in {
val pass = ByteVector("pass".getBytes()) val pass = ByteVector("pass".getBytes())
val crypt = AesCrypt.forString[Try](pass, withIV = false, config = conf) val crypt = AesCrypt.forString(pass, withIV = false, config = conf)
val str = rndString(200) val str = rndString(200)
val crypted = crypt.encrypt(str).get val crypted = crypt.direct.unsafe(str)
crypt.decrypt(crypted).get shouldBe str crypt.inverse.unsafe(crypted) shouldBe str
val fakeAes = AesCrypt.forString[Try](ByteVector("wrong".getBytes()), withIV = false, config = conf) val fakeAes = AesCrypt.forString(ByteVector("wrong".getBytes()), withIV = false, config = conf)
checkCryptoError(fakeAes.decrypt(crypted)) checkCryptoError(fakeAes.inverse.runF[Try](crypted))
//we cannot check if first bytes is iv or already data, but encryption goes wrong //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) val aesWithIV = AesCrypt.forString(pass, withIV = true, config = conf)
aesWithIV.decrypt(crypted).get shouldNot be(str) aesWithIV.inverse.unsafe(crypted) shouldNot be(str)
val aesWrongSalt = AesCrypt.forString[Try](pass, withIV = true, config = conf.copy(salt = rndString(10))) val aesWrongSalt = AesCrypt.forString(pass, withIV = true, config = conf.copy(salt = rndString(10)))
checkCryptoError(aesWrongSalt.decrypt(crypted)) checkCryptoError(aesWrongSalt.inverse.runF[Try](crypted))
} }
} }
def checkCryptoError(tr: Try[String])(implicit pos: Position): Assertion = { def checkCryptoError(tr: Try[String])(implicit pos: Position): Assertion = {
tr.map(_ false) tr.map(_ false)
.recover { .recover {
case e: CryptoErr true case e: CryptoError true
case e case e
logger.error("Unexpected error", e) logger.error("Unexpected error", e)
false false

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto.algorithm package fluence.crypto.aes
/** /**
* Config for AES-256 password based encryption * Config for AES-256 password based encryption

View File

@ -15,18 +15,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto.algorithm package fluence.crypto
import cats.Monad import fluence.codec.{CodecError, MonadicalEitherArrow, PureCodec}
import cats.data.EitherT
import fluence.crypto.keypair.KeyPair
import fluence.crypto.signature.Signature
import scodec.bits.ByteVector
import scala.language.higherKinds object Crypto extends MonadicalEitherArrow[CryptoError] {
type Hasher[A, B] = Func[A, B]
trait SignatureFunctions { type Cipher[A] = Bijection[A, Array[Byte]]
def sign[F[_]: Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoErr, Signature]
def verify[F[_]: Monad](pubKey: KeyPair.Public, signature: Signature, message: ByteVector): EitherT[F, CryptoErr, Unit] type KeyPairGenerator = Func[Option[Array[Byte]], KeyPair]
// TODO: move it to MonadicalEitherArrow, make liftTry with try-catch, and easy conversions for other funcs and bijections
implicit val liftCodecErrorToCrypto: CodecError CryptoError = err CryptoError("Codec error", Some(err))
implicit def codec[A, B](implicit codec: PureCodec[A, B]): Bijection[A, B] =
Bijection(fromOtherFunc(codec.direct), fromOtherFunc(codec.inverse))
} }

View File

@ -15,27 +15,25 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto.algorithm package fluence.crypto
import cats.Applicative import cats.Applicative
import cats.data.EitherT import cats.data.EitherT
import scala.language.higherKinds
import scala.util.control.{NoStackTrace, NonFatal} import scala.util.control.{NoStackTrace, NonFatal}
case class CryptoErr(errorMessage: String, causedBy: Option[Throwable] = None) case class CryptoError(message: String, causedBy: Option[Throwable] = None) extends NoStackTrace {
extends Throwable(errorMessage) with NoStackTrace { override def getMessage: String = message
override def getCause: Throwable = causedBy getOrElse super.getCause override def getCause: Throwable = causedBy getOrElse super.getCause
} }
object CryptoErr { object CryptoError {
// TODO: there's a common `catchNonFatal` pattern, we should refactor this metod onto it // TODO: there's a common `catchNonFatal` pattern, we should refactor this metod onto it
def nonFatalHandling[F[_]: Applicative, A](a: A)(errorText: String): EitherT[F, CryptoErr, A] = { def nonFatalHandling[F[_]: Applicative, A](a: A)(errorText: String): EitherT[F, CryptoError, A] =
try EitherT.pure(a) try EitherT.pure(a)
catch { catch {
case NonFatal(e) EitherT.leftT(CryptoErr(errorText + ": " + e.getLocalizedMessage, Some(e))) case NonFatal(e) EitherT.leftT(CryptoError(errorText + ": " + e.getLocalizedMessage, Some(e)))
}
} }
} }

View File

@ -0,0 +1,56 @@
/*
* 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
import java.security.SecureRandom
import cats.Monad
import cats.data.EitherT
import fluence.crypto.signature.{SignAlgo, Signature, SignatureChecker, Signer}
import scodec.bits.ByteVector
import scala.language.higherKinds
object DumbCrypto {
lazy val signAlgo: SignAlgo =
SignAlgo(
"dumb",
Crypto.liftFunc { seedOpt
val seed = seedOpt.getOrElse {
new SecureRandom().generateSeed(32)
}
KeyPair.fromBytes(seed, seed)
},
keyPair Signer(keyPair.publicKey, Crypto.liftFunc(plain Signature(plain.reverse))),
publicKey
new SignatureChecker {
override def check[F[_]: Monad](signature: Signature, plain: ByteVector): EitherT[F, CryptoError, Unit] =
EitherT.cond[F](signature.sign == plain.reverse, (), CryptoError("Signatures mismatch"))
}
)
lazy val cipherString: Crypto.Cipher[String] =
Crypto.liftB(_.getBytes, bytes new String(bytes))
lazy val noOpHasher: Crypto.Hasher[Array[Byte], Array[Byte]] =
Crypto.identityFunc[Array[Byte]]
lazy val testHasher: Crypto.Hasher[Array[Byte], Array[Byte]] =
Crypto.liftFunc(bytes ("H<" + new String(bytes) + ">").getBytes())
}

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto.keypair package fluence.crypto
import scodec.bits.ByteVector import scodec.bits.ByteVector

View File

@ -0,0 +1,63 @@
/*
* 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.cipher
import cats.Monad
import cats.data.EitherT
import fluence.crypto.{Crypto, CryptoError}
import scala.collection.Searching.{Found, InsertionPoint, SearchResult}
import scala.language.higherKinds
object CipherSearch {
/**
* Searches the specified indexedSeq for the search element using the binary search algorithm.
* The sequence should be sorted with the same `Ordering` before calling, otherwise, the results are undefined.
*
* @param coll Ordered collection of encrypted elements to search in.
* @param decrypt Decryption function for sequence elements.
* @param ordering The ordering to be used to compare elements.
*
* @return A `Found` value containing the index corresponding to the search element in the
* sequence. A `InsertionPoint` value containing the index where the element would be inserted if
* the search element is not found in the sequence.
*/
def binarySearch[A, B](coll: IndexedSeq[A], decrypt: Crypto.Func[A, B])(
implicit ordering: Ordering[B]
): Crypto.Func[B, SearchResult] =
new Crypto.Func[B, SearchResult] {
override def apply[F[_]](input: B)(
implicit F: Monad[F]
): EitherT[F, CryptoError, SearchResult] = {
type M[X] = EitherT[F, CryptoError, X]
implicitly[Monad[M]].tailRecM((0, coll.length)) {
case (from, to) if from == to EitherT.rightT(Right(InsertionPoint(from)))
case (from, to)
val idx = from + (to - from - 1) / 2
decrypt(coll(idx)).map { d
math.signum(ordering.compare(input, d)) match {
case -1 Left((from, idx))
case 1 Left((idx + 1, to))
case _ Right(Found(idx))
}
}
}
}
}
}

View File

@ -17,7 +17,7 @@
package fluence.crypto.signature package fluence.crypto.signature
import fluence.crypto.keypair.KeyPair import fluence.crypto.KeyPair
/** /**
* Container for public key of signer and a signature. * Container for public key of signer and a signature.

View File

@ -17,30 +17,25 @@
package fluence.crypto.signature package fluence.crypto.signature
import cats.Monad import fluence.crypto.{Crypto, KeyPair}
import cats.data.EitherT
import fluence.crypto.algorithm.CryptoErr
import fluence.crypto.keypair.KeyPair
import scodec.bits.ByteVector
import scala.language.higherKinds
/** /**
* Wraps public and private key, along with signing algorithm, to produce signatures * Signature algorithm -- cryptographically coupled keypair, signer and signature checker.
*
* @param name Algorithm name
* @param generateKeyPair Keypair generator; note that you must ensure the seed entropy is secure
* @param signer Signer for a given keypair
* @param checker Checker for a given public key
*/ */
trait Signer { case class SignAlgo(
def publicKey: KeyPair.Public name: String,
generateKeyPair: Crypto.KeyPairGenerator,
def sign[F[_]: Monad](plain: ByteVector): EitherT[F, CryptoErr, Signature] signer: SignAlgo.SignerFn,
} implicit val checker: SignAlgo.CheckerFn,
)
object Signer {
class DumbSigner(keyPair: KeyPair) extends Signer {
override def publicKey: KeyPair.Public = keyPair.publicKey
override def sign[F[_]: Monad](plain: ByteVector): EitherT[F, CryptoErr, Signature] =
EitherT.pure(Signature(plain.reverse))
}
object SignAlgo {
type SignerFn = KeyPair Signer
type CheckerFn = KeyPair.Public SignatureChecker
} }

View File

@ -19,11 +19,11 @@ package fluence.crypto.signature
import cats.Monad import cats.Monad
import cats.data.EitherT import cats.data.EitherT
import fluence.crypto.algorithm.CryptoErr import fluence.crypto.CryptoError
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.language.higherKinds import scala.language.higherKinds
trait SignatureChecker { trait SignatureChecker {
def check[F[_]: Monad](signature: Signature, plain: ByteVector): EitherT[F, CryptoErr, Unit] def check[F[_]: Monad](signature: Signature, plain: ByteVector): EitherT[F, CryptoError, Unit]
} }

View File

@ -15,14 +15,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto.algorithm package fluence.crypto.signature
import cats.Monad import cats.syntax.profunctor._
import cats.data.EitherT import fluence.crypto.{Crypto, KeyPair}
import fluence.crypto.keypair.KeyPair import scodec.bits.ByteVector
import scala.language.higherKinds case class Signer(publicKey: KeyPair.Public, sign: Crypto.Func[ByteVector, Signature]) {
lazy val signWithPK: Crypto.Func[ByteVector, PubKeyAndSignature] = sign.rmap(PubKeyAndSignature(publicKey, _))
trait KeyGenerator {
def generateKeyPair[F[_]: Monad](seed: Option[Array[Byte]] = None): EitherT[F, CryptoErr, KeyPair]
} }

View File

@ -17,33 +17,30 @@
package fluence.crypto package fluence.crypto
import cats.instances.try_._ import fluence.crypto.cipher.CipherSearch
import fluence.crypto.cipher.NoOpCrypt
import org.scalatest.{Matchers, WordSpec} import org.scalatest.{Matchers, WordSpec}
import scala.collection.Searching.{Found, InsertionPoint} import scala.collection.Searching.{Found, InsertionPoint}
import scala.util.Try
class CryptoSearchingSpec extends WordSpec with Matchers { class CryptoSearchingSpec extends WordSpec with Matchers {
"search" should { "search" should {
"correct search plainText key in encrypted data" in { "correct search plainText key in encrypted data" in {
val crypt: NoOpCrypt[Try, String] = NoOpCrypt.forString val crypt: Crypto.Cipher[String] = DumbCrypto.cipherString
val plainTextElements = Array("A", "B", "C", "D", "E") val plainTextElements = Array("A", "B", "C", "D", "E")
val encryptedElements = plainTextElements.map(t crypt.encrypt(t).get) val encryptedElements = plainTextElements.map(t crypt.direct.unsafe(t))
import fluence.crypto.cipher.CryptoSearching._ val search = CipherSearch.binarySearch(encryptedElements, crypt.inverse)
implicit val decryptFn: Array[Byte] Try[String] = crypt.decrypt
encryptedElements.binarySearch("B").get shouldBe Found(1) search.unsafe("B") shouldBe Found(1)
encryptedElements.binarySearch("D").get shouldBe Found(3) search.unsafe("D") shouldBe Found(3)
encryptedElements.binarySearch("E").get shouldBe Found(4) search.unsafe("E") shouldBe Found(4)
encryptedElements.binarySearch("0").get shouldBe InsertionPoint(0) search.unsafe("0") shouldBe InsertionPoint(0)
encryptedElements.binarySearch("BB").get shouldBe InsertionPoint(2) search.unsafe("BB") shouldBe InsertionPoint(2)
encryptedElements.binarySearch("ZZ").get shouldBe InsertionPoint(5) search.unsafe("ZZ") shouldBe InsertionPoint(5)
} }
} }

View File

@ -17,7 +17,6 @@
package fluence.crypto package fluence.crypto
import fluence.crypto.cipher.NoOpCrypt
import org.scalatest.{Matchers, WordSpec} import org.scalatest.{Matchers, WordSpec}
class NoOpCryptSpec extends WordSpec with Matchers { class NoOpCryptSpec extends WordSpec with Matchers {
@ -25,22 +24,14 @@ class NoOpCryptSpec extends WordSpec with Matchers {
"NoOpCrypt" should { "NoOpCrypt" should {
"convert a string to bytes back and forth without any cryptography" in { "convert a string to bytes back and forth without any cryptography" in {
val noOpCrypt = NoOpCrypt.forString val noOpCrypt = DumbCrypto.cipherString
val emptyString = "" val emptyString = ""
noOpCrypt.decrypt(noOpCrypt.encrypt(emptyString)) shouldBe emptyString noOpCrypt.inverse.unsafe(noOpCrypt.direct.unsafe(emptyString)) shouldBe emptyString
val nonEmptyString = "some text here" val nonEmptyString = "some text here"
noOpCrypt.decrypt(noOpCrypt.encrypt(nonEmptyString)) shouldBe nonEmptyString noOpCrypt.inverse.unsafe(noOpCrypt.direct.unsafe(nonEmptyString)) shouldBe nonEmptyString
val byteArray = Array(1.toByte, 23.toByte, 45.toByte) val byteArray = Array(1.toByte, 23.toByte, 45.toByte)
noOpCrypt.encrypt(noOpCrypt.decrypt(byteArray)) shouldBe byteArray noOpCrypt.direct.unsafe(noOpCrypt.inverse.unsafe(byteArray)) shouldBe byteArray
}
"convert a long to bytes back and forth without any cryptography" in {
val noOpCrypt = NoOpCrypt.forLong
noOpCrypt.decrypt(noOpCrypt.encrypt(0L)) shouldBe 0L
noOpCrypt.decrypt(noOpCrypt.encrypt(1234567890123456789L)) shouldBe 1234567890123456789L
} }
} }
} }

View File

@ -15,15 +15,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto.algorithm package fluence.crypto.ecdsa
import cats.Monad import cats.Monad
import cats.data.EitherT import cats.data.EitherT
import fluence.crypto.SignAlgo import fluence.crypto._
import fluence.crypto.facade.ecdsa.EC import fluence.crypto.facade.ecdsa.EC
import fluence.crypto.hash.{CryptoHasher, JsCryptoHasher} import fluence.crypto.hash.JsCryptoHasher
import fluence.crypto.keypair.KeyPair import fluence.crypto.signature.{SignAlgo, Signature, SignatureChecker, Signer}
import fluence.crypto.signature.Signature
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.language.higherKinds import scala.language.higherKinds
@ -35,13 +34,14 @@ import scala.scalajs.js.typedarray.Uint8Array
* Return in all js methods hex, because in the other case we will receive javascript objects * 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 * @param ec implementation of ecdsa logic for different curves
*/ */
class Ecdsa(ec: EC, hasher: Option[CryptoHasher[Array[Byte], Array[Byte]]]) class Ecdsa(ec: EC, hasher: Option[Crypto.Hasher[Array[Byte], Array[Byte]]]) {
extends Algorithm with SignatureFunctions with KeyGenerator { import CryptoError.nonFatalHandling
import CryptoErr._
override def generateKeyPair[F[_]: Monad](seed: Option[Array[Byte]] = None): EitherT[F, CryptoErr, KeyPair] = { val generateKeyPair: Crypto.KeyPairGenerator =
new Crypto.Func[Option[Array[Byte]], KeyPair] {
override def apply[F[_]](input: Option[Array[Byte]])(implicit F: Monad[F]): EitherT[F, CryptoError, KeyPair] =
nonFatalHandling { nonFatalHandling {
val seedJs = seed.map(bs js.Dynamic.literal(entropy = bs.toJSArray)) val seedJs = input.map(bs js.Dynamic.literal(entropy = bs.toJSArray))
val key = ec.genKeyPair(seedJs) val key = ec.genKeyPair(seedJs)
val publicHex = key.getPublic(true, "hex") val publicHex = key.getPublic(true, "hex")
val secretHex = key.getPrivate("hex") val secretHex = key.getPrivate("hex")
@ -51,7 +51,7 @@ class Ecdsa(ec: EC, hasher: Option[CryptoHasher[Array[Byte], Array[Byte]]])
}("Failed to generate key pair.") }("Failed to generate key pair.")
} }
override def sign[F[_]: Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoErr, Signature] = { def sign[F[_]: Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoError, Signature] =
for { for {
secret nonFatalHandling { secret nonFatalHandling {
ec.keyFromPrivate(keyPair.secretKey.value.toHex, "hex") ec.keyFromPrivate(keyPair.secretKey.value.toHex, "hex")
@ -59,24 +59,21 @@ class Ecdsa(ec: EC, hasher: Option[CryptoHasher[Array[Byte], Array[Byte]]])
hash hash(message) hash hash(message)
signHex nonFatalHandling(secret.sign(new Uint8Array(hash)).toDER("hex"))("Cannot sign message") signHex nonFatalHandling(secret.sign(new Uint8Array(hash)).toDER("hex"))("Cannot sign message")
} yield Signature(ByteVector.fromValidHex(signHex)) } yield Signature(ByteVector.fromValidHex(signHex))
}
def hash[F[_]: Monad](message: ByteVector): EitherT[F, CryptoErr, js.Array[Byte]] = { def hash[F[_]: Monad](message: ByteVector): EitherT[F, CryptoError, js.Array[Byte]] = {
val arr = message.toArray val arr = message.toArray
hasher hasher
.fold(EitherT.pure[F, CryptoErr](arr)) { h .fold(EitherT.pure[F, CryptoError](arr)) { h
nonFatalHandling { h[F](arr)
h.hash(message.toArray)
}("Cannot hash message.")
} }
.map(_.toJSArray) .map(_.toJSArray)
} }
override def verify[F[_]: Monad]( def verify[F[_]: Monad](
pubKey: KeyPair.Public, pubKey: KeyPair.Public,
signature: Signature, signature: Signature,
message: ByteVector message: ByteVector
): EitherT[F, CryptoErr, Unit] = { ): EitherT[F, CryptoError, Unit] =
for { for {
public nonFatalHandling { public nonFatalHandling {
val hex = pubKey.value.toHex val hex = pubKey.value.toHex
@ -84,14 +81,33 @@ class Ecdsa(ec: EC, hasher: Option[CryptoHasher[Array[Byte], Array[Byte]]])
}("Incorrect public key format.") }("Incorrect public key format.")
hash hash(message) hash hash(message)
verify nonFatalHandling(public.verify(new Uint8Array(hash), signature.sign.toHex))("Cannot verify message.") verify nonFatalHandling(public.verify(new Uint8Array(hash), signature.sign.toHex))("Cannot verify message.")
_ EitherT.cond[F](verify, (), CryptoErr("Signature is not verified")) _ EitherT.cond[F](verify, (), CryptoError("Signature is not verified"))
} yield () } yield ()
}
} }
object Ecdsa { object Ecdsa {
val ecdsa_secp256k1_sha256 = new Ecdsa(new EC("secp256k1"), Some(JsCryptoHasher.Sha256)) val ecdsa_secp256k1_sha256 = new Ecdsa(new EC("secp256k1"), Some(JsCryptoHasher.Sha256))
val signAlgo = new SignAlgo("ecdsa/secp256k1/sha256/js", ecdsa_secp256k1_sha256) val signAlgo: SignAlgo = SignAlgo(
"ecdsa/secp256k1/sha256/js",
generateKeyPair = ecdsa_secp256k1_sha256.generateKeyPair,
signer = kp
Signer(
kp.publicKey,
new Crypto.Func[ByteVector, signature.Signature] {
override def apply[F[_]](
input: ByteVector
)(implicit F: Monad[F]): EitherT[F, CryptoError, signature.Signature] =
ecdsa_secp256k1_sha256.sign(kp, input)
}
),
checker = pk
new SignatureChecker {
override def check[F[_]: Monad](
signature: fluence.crypto.signature.Signature,
plain: ByteVector
): EitherT[F, CryptoError, Unit] =
ecdsa_secp256k1_sha256.verify(pk, signature, plain)
}
)
} }

View File

@ -17,8 +17,10 @@
package fluence.crypto.hash package fluence.crypto.hash
object CryptoHashers { import fluence.crypto.Crypto
lazy val Sha1: CryptoHasher.Bytes = JdkCryptoHasher.Sha1
lazy val Sha256: CryptoHasher.Bytes = JdkCryptoHasher.Sha256 object CryptoHashers {
lazy val Sha1: Crypto.Hasher[Array[Byte], Array[Byte]] = JsCryptoHasher.Sha1
lazy val Sha256: Crypto.Hasher[Array[Byte], Array[Byte]] = JsCryptoHasher.Sha256
} }

View File

@ -17,25 +17,31 @@
package fluence.crypto.hash package fluence.crypto.hash
import fluence.crypto.{Crypto, CryptoError}
import fluence.crypto.facade.ecdsa.{SHA1, SHA256} import fluence.crypto.facade.ecdsa.{SHA1, SHA256}
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.scalajs.js.JSConverters._ import scala.scalajs.js.JSConverters._
import scala.scalajs.js.typedarray.Uint8Array import scala.scalajs.js.typedarray.Uint8Array
import scala.util.Try
object JsCryptoHasher { object JsCryptoHasher {
lazy val Sha256: CryptoHasher.Bytes = lazy val Sha256: Crypto.Hasher[Array[Byte], Array[Byte]] =
CryptoHasher.buildM[Array[Byte], Array[Byte]] { msg Crypto.liftFuncEither[Array[Byte], Array[Byte]] { msg
Try {
val sha256 = new SHA256() val sha256 = new SHA256()
sha256.update(new Uint8Array(msg.toJSArray)) sha256.update(new Uint8Array(msg.toJSArray))
ByteVector.fromValidHex(sha256.digest("hex")).toArray ByteVector.fromValidHex(sha256.digest("hex")).toArray
}(_ ++ _) }.toEither.left.map(err CryptoError("Cannot calculate Sha256 hash", Some(err)))
}
lazy val Sha1: CryptoHasher.Bytes = lazy val Sha1: Crypto.Hasher[Array[Byte], Array[Byte]] =
CryptoHasher.buildM[Array[Byte], Array[Byte]] { msg Crypto.liftFuncEither[Array[Byte], Array[Byte]] { msg
Try {
val sha1 = new SHA1() val sha1 = new SHA1()
sha1.update(new Uint8Array(msg.toJSArray)) sha1.update(new Uint8Array(msg.toJSArray))
ByteVector.fromValidHex(sha1.digest("hex")).toArray ByteVector.fromValidHex(sha1.digest("hex")).toArray
}(_ ++ _) }.toEither.left.map(err CryptoError("Cannot calculate Sha256 hash", Some(err)))
}
} }

View File

@ -15,13 +15,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto package flyence.crypto
import cats.data.EitherT import cats.data.EitherT
import cats.instances.try_._ import cats.instances.try_._
import fluence.crypto.algorithm.{CryptoErr, Ecdsa} import fluence.crypto.ecdsa.Ecdsa
import fluence.crypto.keypair.KeyPair
import fluence.crypto.signature.Signature import fluence.crypto.signature.Signature
import fluence.crypto.{CryptoError, KeyPair}
import org.scalatest.{Matchers, WordSpec} import org.scalatest.{Matchers, WordSpec}
import scodec.bits.ByteVector import scodec.bits.ByteVector
@ -48,7 +48,7 @@ class EcdsaSpec extends WordSpec with Matchers {
"correct sign and verify data" in { "correct sign and verify data" in {
val algorithm = Ecdsa.ecdsa_secp256k1_sha256 val algorithm = Ecdsa.ecdsa_secp256k1_sha256
val keys = algorithm.generateKeyPair[Try]().extract val keys = algorithm.generateKeyPair.unsafe(None)
val pubKey = keys.publicKey val pubKey = keys.publicKey
val data = rndByteVector(10) val data = rndByteVector(10)
val sign = algorithm.sign[Try](keys, data).extract val sign = algorithm.sign[Try](keys, data).extract
@ -65,7 +65,7 @@ class EcdsaSpec extends WordSpec with Matchers {
"correctly work with signer and checker" in { "correctly work with signer and checker" in {
val algo = Ecdsa.signAlgo val algo = Ecdsa.signAlgo
val keys = algo.generateKeyPair().extract val keys = algo.generateKeyPair.unsafe(None)
val signer = algo.signer(keys) val signer = algo.signer(keys)
val checker = algo.checker(keys.publicKey) val checker = algo.checker(keys.publicKey)
@ -80,16 +80,16 @@ class EcdsaSpec extends WordSpec with Matchers {
"throw an errors on invalid data" in { "throw an errors on invalid data" in {
val algo = Ecdsa.signAlgo val algo = Ecdsa.signAlgo
val keys = algo.generateKeyPair().extract val keys = algo.generateKeyPair.unsafe(None)
val signer = algo.signer(keys) val signer = algo.signer(keys)
val checker = algo.checker(keys.publicKey) val checker = algo.checker(keys.publicKey)
val data = rndByteVector(10) val data = rndByteVector(10)
val sign = signer.sign(data).extract val sign = signer.sign(data).extract
the[CryptoErr] thrownBy checker.check(Signature(rndByteVector(10)), data).value.flatMap(_.toTry).get the[CryptoError] thrownBy checker.check(Signature(rndByteVector(10)), data).value.flatMap(_.toTry).get
val invalidChecker = algo.checker(KeyPair.fromByteVectors(rndByteVector(10), rndByteVector(10)).publicKey) val invalidChecker = algo.checker(KeyPair.fromByteVectors(rndByteVector(10), rndByteVector(10)).publicKey)
the[CryptoErr] thrownBy invalidChecker the[CryptoError] thrownBy invalidChecker
.check(sign, data) .check(sign, data)
.value .value
.flatMap(_.toTry) .flatMap(_.toTry)

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto package flyence.crypto
import fluence.crypto.facade.ecdsa.{SHA1, SHA256} import fluence.crypto.facade.ecdsa.{SHA1, SHA256}
import org.scalatest.{Matchers, WordSpec} import org.scalatest.{Matchers, WordSpec}

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto.algorithm package fluence.crypto
import java.security.Security import java.security.Security
@ -26,7 +26,7 @@ import scala.language.{higherKinds, implicitConversions}
/** /**
* trait that initializes a JVM-specific provider to work with cryptography * trait that initializes a JVM-specific provider to work with cryptography
*/ */
private[crypto] trait JavaAlgorithm extends Algorithm { private[crypto] trait JavaAlgorithm {
JavaAlgorithm.addProvider JavaAlgorithm.addProvider
} }

View File

@ -15,17 +15,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto.algorithm package fluence.crypto.ecdsa
import java.math.BigInteger import java.math.BigInteger
import java.security._ import java.security._
import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPrivateKey
import cats.data.EitherT
import cats.Monad import cats.Monad
import fluence.crypto.SignAlgo import cats.data.EitherT
import fluence.crypto.hash.{CryptoHasher, JdkCryptoHasher} import fluence.crypto.{KeyPair, _}
import fluence.crypto.keypair.KeyPair import fluence.crypto.hash.JdkCryptoHasher
import fluence.crypto.signature.{SignAlgo, SignatureChecker, Signer}
import org.bouncycastle.jce.ECNamedCurveTable import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.interfaces.ECPublicKey import org.bouncycastle.jce.interfaces.ECPublicKey
import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.provider.BouncyCastleProvider
@ -39,24 +39,29 @@ import scala.language.higherKinds
* @param curveType http://www.bouncycastle.org/wiki/display/JA1/Supported+Curves+%28ECDSA+and+ECGOST%29 * @param curveType http://www.bouncycastle.org/wiki/display/JA1/Supported+Curves+%28ECDSA+and+ECGOST%29
* @param scheme https://bouncycastle.org/specifications.html * @param scheme https://bouncycastle.org/specifications.html
*/ */
class Ecdsa(curveType: String, scheme: String, hasher: Option[CryptoHasher[Array[Byte], Array[Byte]]]) class Ecdsa(curveType: String, scheme: String, hasher: Option[Crypto.Hasher[Array[Byte], Array[Byte]]])
extends JavaAlgorithm with SignatureFunctions with KeyGenerator { extends JavaAlgorithm {
import CryptoErr._
import CryptoError.nonFatalHandling
import Ecdsa._ import Ecdsa._
val HEXradix = 16 val HEXradix = 16
override def generateKeyPair[F[_]: Monad](seed: Option[Array[Byte]]): EitherT[F, CryptoErr, KeyPair] = { val generateKeyPair: Crypto.KeyPairGenerator =
new Crypto.Func[Option[Array[Byte]], KeyPair] {
override def apply[F[_]](
input: Option[Array[Byte]]
)(implicit F: Monad[F]): EitherT[F, CryptoError, fluence.crypto.KeyPair] =
for { for {
ecSpec EitherT.fromOption( ecSpec EitherT.fromOption(
Option(ECNamedCurveTable.getParameterSpec(curveType)), Option(ECNamedCurveTable.getParameterSpec(curveType)),
CryptoErr("Parameter spec for the curve is not available.") CryptoError("Parameter spec for the curve is not available.")
) )
g getKeyPairGenerator g getKeyPairGenerator
_ nonFatalHandling { _ nonFatalHandling {
g.initialize(ecSpec, seed.map(new SecureRandom(_)).getOrElse(new SecureRandom())) g.initialize(ecSpec, input.map(new SecureRandom(_)).getOrElse(new SecureRandom()))
}(s"Could not initialize KeyPairGenerator") }(s"Could not initialize KeyPairGenerator")
p EitherT.fromOption(Option(g.generateKeyPair()), CryptoErr("Could not generate KeyPair. Unexpected.")) p EitherT.fromOption(Option(g.generateKeyPair()), CryptoError("Could not generate KeyPair. Unexpected."))
keyPair nonFatalHandling { keyPair nonFatalHandling {
//store S number for private key and compressed Q point on curve for public key //store S number for private key and compressed Q point on curve for public key
val pk = ByteVector(p.getPublic.asInstanceOf[ECPublicKey].getQ.getEncoded(true)) val pk = ByteVector(p.getPublic.asInstanceOf[ECPublicKey].getQ.getEncoded(true))
@ -67,61 +72,55 @@ class Ecdsa(curveType: String, scheme: String, hasher: Option[CryptoHasher[Array
} yield keyPair } yield keyPair
} }
override def sign[F[_]: Monad]( def sign[F[_]: Monad](
keyPair: KeyPair, keyPair: KeyPair,
message: ByteVector message: ByteVector
): EitherT[F, CryptoErr, fluence.crypto.signature.Signature] = { ): EitherT[F, CryptoError, signature.Signature] =
signMessage(new BigInteger(keyPair.secretKey.value.toHex, HEXradix), message.toArray) signMessage(new BigInteger(keyPair.secretKey.value.toHex, HEXradix), message.toArray)
.map(bb fluence.crypto.signature.Signature(ByteVector(bb))) .map(bb fluence.crypto.signature.Signature(ByteVector(bb)))
}
override def verify[F[_]: Monad]( def verify[F[_]: Monad](
publicKey: KeyPair.Public, publicKey: KeyPair.Public,
signature: fluence.crypto.signature.Signature, signature: fluence.crypto.signature.Signature,
message: ByteVector message: ByteVector
): EitherT[F, CryptoErr, Unit] = { ): EitherT[F, CryptoError, Unit] =
verifySign(publicKey.bytes, signature.bytes, message.toArray) verifySign(publicKey.bytes, signature.bytes, message.toArray)
}
private def signMessage[F[_]: Monad]( private def signMessage[F[_]: Monad](
privateKey: BigInteger, privateKey: BigInteger,
message: Array[Byte] message: Array[Byte]
): EitherT[F, CryptoErr, Array[Byte]] = { ): EitherT[F, CryptoError, Array[Byte]] =
for { for {
ec curveSpec ec curveSpec
keySpec nonFatalHandling(new ECPrivateKeySpec(privateKey, ec))("Cannot read private key.") keySpec nonFatalHandling(new ECPrivateKeySpec(privateKey, ec))("Cannot read private key.")
keyFactory getKeyFactory keyFactory getKeyFactory
signProvider getSignatureProvider signProvider getSignatureProvider
sign { sign nonFatalHandling {
nonFatalHandling {
signProvider.initSign(keyFactory.generatePrivate(keySpec)) signProvider.initSign(keyFactory.generatePrivate(keySpec))
signProvider.update(hasher.map(h h.hash(message)).getOrElse(message)) signProvider.update(hasher.map(_.unsafe(message)).getOrElse(message))
signProvider.sign() signProvider.sign()
}("Cannot sign message.") }("Cannot sign message.")
}
} yield sign } yield sign
}
private def verifySign[F[_]: Monad]( private def verifySign[F[_]: Monad](
publicKey: Array[Byte], publicKey: Array[Byte],
signature: Array[Byte], signature: Array[Byte],
message: Array[Byte], message: Array[Byte],
): EitherT[F, CryptoErr, Unit] = { ): EitherT[F, CryptoError, Unit] =
for { for {
ec curveSpec ec curveSpec
keySpec nonFatalHandling(new ECPublicKeySpec(ec.getCurve.decodePoint(publicKey), ec))("Cannot read public key.") keySpec nonFatalHandling(new ECPublicKeySpec(ec.getCurve.decodePoint(publicKey), ec))("Cannot read public key.")
keyFactory getKeyFactory keyFactory getKeyFactory
signProvider getSignatureProvider signProvider getSignatureProvider
verify { verify nonFatalHandling {
nonFatalHandling {
signProvider.initVerify(keyFactory.generatePublic(keySpec)) signProvider.initVerify(keyFactory.generatePublic(keySpec))
signProvider.update(hasher.map(h h.hash(message)).getOrElse(message)) signProvider.update(hasher.map(_.unsafe(message)).getOrElse(message))
signProvider.verify(signature) signProvider.verify(signature)
}("Cannot verify message.") }("Cannot verify message.")
}
_ EitherT.cond[F](verify, (), CryptoErr("Signature is not verified")) _ EitherT.cond[F](verify, (), CryptoError("Signature is not verified"))
} yield () } yield ()
}
private def curveSpec[F[_]: Monad] = private def curveSpec[F[_]: Monad] =
nonFatalHandling(ECNamedCurveTable.getParameterSpec(curveType).asInstanceOf[ECParameterSpec])( nonFatalHandling(ECNamedCurveTable.getParameterSpec(curveType).asInstanceOf[ECParameterSpec])(
@ -156,5 +155,26 @@ object Ecdsa {
*/ */
val ecdsa_secp256k1_sha256 = new Ecdsa("secp256k1", "NONEwithECDSA", Some(JdkCryptoHasher.Sha256)) val ecdsa_secp256k1_sha256 = new Ecdsa("secp256k1", "NONEwithECDSA", Some(JdkCryptoHasher.Sha256))
val signAlgo = new SignAlgo("ecdsa_secp256k1_sha256", ecdsa_secp256k1_sha256) val signAlgo: SignAlgo = SignAlgo(
name = "ecdsa_secp256k1_sha256",
generateKeyPair = ecdsa_secp256k1_sha256.generateKeyPair,
signer = kp
Signer(
kp.publicKey,
new Crypto.Func[ByteVector, signature.Signature] {
override def apply[F[_]](
input: ByteVector
)(implicit F: Monad[F]): EitherT[F, CryptoError, signature.Signature] =
ecdsa_secp256k1_sha256.sign(kp, input)
}
),
checker = pk
new SignatureChecker {
override def check[F[_]: Monad](
signature: fluence.crypto.signature.Signature,
plain: ByteVector
): EitherT[F, CryptoError, Unit] =
ecdsa_secp256k1_sha256.verify(pk, signature, plain)
}
)
} }

View File

@ -17,8 +17,10 @@
package fluence.crypto.hash package fluence.crypto.hash
object CryptoHashers { import fluence.crypto.Crypto
lazy val Sha1: CryptoHasher.Bytes = JsCryptoHasher.Sha1
lazy val Sha256: CryptoHasher.Bytes = JsCryptoHasher.Sha256 object CryptoHashers {
lazy val Sha1: Crypto.Hasher[Array[Byte], Array[Byte]] = JdkCryptoHasher.Sha1
lazy val Sha256: Crypto.Hasher[Array[Byte], Array[Byte]] = JdkCryptoHasher.Sha256
} }

View File

@ -0,0 +1,44 @@
/*
* 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.hash
import java.security.MessageDigest
import fluence.crypto.{Crypto, CryptoError}
import scala.util.Try
object JdkCryptoHasher {
lazy val Sha256: Crypto.Hasher[Array[Byte], Array[Byte]] = apply("SHA-256")
lazy val Sha1: Crypto.Hasher[Array[Byte], Array[Byte]] = apply("SHA-1")
/**
* Thread-safe implementation of [[Crypto.Hasher]] with standard jdk [[java.security.MessageDigest]]
*
* @param algorithm one of allowed hashing algorithms
* [[https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#MessageDigest]]
*/
def apply(algorithm: String): Crypto.Hasher[Array[Byte], Array[Byte]] =
Crypto.liftFuncEither(
bytes
Try(MessageDigest.getInstance(algorithm).digest(bytes)).toEither.left
.map(err CryptoError(s"Cannot get $algorithm hash", Some(err)))
)
}

View File

@ -29,7 +29,7 @@ class JvmHashSpec extends WordSpec with Matchers {
val sha256TesterHex = "513c17f8cf6ba96ce412cc2ae82f68821e9a2c6ae7a2fb1f5e46d08c387c8e65" val sha256TesterHex = "513c17f8cf6ba96ce412cc2ae82f68821e9a2c6ae7a2fb1f5e46d08c387c8e65"
val hasher = JdkCryptoHasher.Sha256 val hasher = JdkCryptoHasher.Sha256
ByteVector(hasher.hash(str.getBytes())).toHex shouldBe sha256TesterHex ByteVector(hasher.unsafe(str.getBytes())).toHex shouldBe sha256TesterHex
} }
"work with sha1" in { "work with sha1" in {
@ -37,7 +37,7 @@ class JvmHashSpec extends WordSpec with Matchers {
val sha1TesterHex = "879db20eabcecea7d4736a8bae5bc64564b76b2f" val sha1TesterHex = "879db20eabcecea7d4736a8bae5bc64564b76b2f"
val hasher = JdkCryptoHasher.Sha1 val hasher = JdkCryptoHasher.Sha1
ByteVector(hasher.hash(str.getBytes())).toHex shouldBe sha1TesterHex ByteVector(hasher.unsafe(str.getBytes())).toHex shouldBe sha1TesterHex
} }
"check unsigned array with sha1" in { "check unsigned array with sha1" in {
@ -49,7 +49,7 @@ class JvmHashSpec extends WordSpec with Matchers {
val hasher = JdkCryptoHasher.Sha1 val hasher = JdkCryptoHasher.Sha1
ByteVector(hasher.hash(arr)).toBase64 shouldBe base64Check ByteVector(hasher.unsafe(arr)).toBase64 shouldBe base64Check
} }
} }
} }

View File

@ -21,8 +21,8 @@ import java.io.File
import cats.data.EitherT import cats.data.EitherT
import cats.instances.try_._ import cats.instances.try_._
import fluence.crypto.algorithm.{CryptoErr, Ecdsa} import fluence.crypto.ecdsa.Ecdsa
import fluence.crypto.keypair.KeyPair import fluence.crypto.keystore.FileKeyStorage
import fluence.crypto.signature.Signature import fluence.crypto.signature.Signature
import org.scalatest.{Matchers, WordSpec} import org.scalatest.{Matchers, WordSpec}
import scodec.bits.ByteVector import scodec.bits.ByteVector
@ -50,7 +50,7 @@ class SignatureSpec extends WordSpec with Matchers {
"correct sign and verify data" in { "correct sign and verify data" in {
val algorithm = Ecdsa.ecdsa_secp256k1_sha256 val algorithm = Ecdsa.ecdsa_secp256k1_sha256
val keys = algorithm.generateKeyPair[Try]().extract val keys = algorithm.generateKeyPair.unsafe(None)
val pubKey = keys.publicKey val pubKey = keys.publicKey
val data = rndByteVector(10) val data = rndByteVector(10)
val sign = algorithm.sign[Try](keys, data).extract val sign = algorithm.sign[Try](keys, data).extract
@ -67,7 +67,7 @@ class SignatureSpec extends WordSpec with Matchers {
"correctly work with signer and checker" in { "correctly work with signer and checker" in {
val algo = Ecdsa.signAlgo val algo = Ecdsa.signAlgo
val keys = algo.generateKeyPair().extract val keys = algo.generateKeyPair.unsafe(None)
val signer = algo.signer(keys) val signer = algo.signer(keys)
val checker = algo.checker(keys.publicKey) val checker = algo.checker(keys.publicKey)
@ -82,18 +82,18 @@ class SignatureSpec extends WordSpec with Matchers {
"throw an errors on invalid data" in { "throw an errors on invalid data" in {
val algo = Ecdsa.signAlgo val algo = Ecdsa.signAlgo
val keys = algo.generateKeyPair().extract val keys = algo.generateKeyPair.unsafe(None)
val signer = algo.signer(keys) val signer = algo.signer(keys)
val checker = algo.checker(keys.publicKey) val checker = algo.checker(keys.publicKey)
val data = rndByteVector(10) val data = rndByteVector(10)
val sign = signer.sign(data).extract val sign = signer.sign(data).extract
the[CryptoErr] thrownBy { the[CryptoError] thrownBy {
checker.check(Signature(rndByteVector(10)), data).value.flatMap(_.toTry).get checker.check(Signature(rndByteVector(10)), data).value.flatMap(_.toTry).get
} }
val invalidChecker = algo.checker(KeyPair.fromByteVectors(rndByteVector(10), rndByteVector(10)).publicKey) val invalidChecker = algo.checker(KeyPair.fromByteVectors(rndByteVector(10), rndByteVector(10)).publicKey)
the[CryptoErr] thrownBy { the[CryptoError] thrownBy {
invalidChecker invalidChecker
.check(sign, data) .check(sign, data)
.value .value
@ -104,7 +104,7 @@ class SignatureSpec extends WordSpec with Matchers {
"store and read key from file" in { "store and read key from file" in {
val algo = Ecdsa.signAlgo val algo = Ecdsa.signAlgo
val keys = algo.generateKeyPair().extract val keys = algo.generateKeyPair.unsafe(None)
val keyFile = File.createTempFile("test", "") val keyFile = File.createTempFile("test", "")
if (keyFile.exists()) keyFile.delete() if (keyFile.exists()) keyFile.delete()

View File

@ -1,47 +0,0 @@
/*
* 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.hash
import java.security.MessageDigest
/**
* Thread-safe implementation of [[CryptoHasher]] with standard jdk [[java.security.MessageDigest]]
*
* @param algorithm one of allowed hashing algorithms
* [[https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#MessageDigest]]
*/
class JdkCryptoHasher(algorithm: String) extends CryptoHasher[Array[Byte], Array[Byte]] {
override def hash(msg1: Array[Byte]): Array[Byte] = {
MessageDigest.getInstance(algorithm).digest(msg1)
}
override def hash(msg1: Array[Byte], msg2: Array[Byte]*): Array[Byte] = {
hash(msg1 ++ msg2.flatten)
}
}
object JdkCryptoHasher {
lazy val Sha256: CryptoHasher[Array[Byte], Array[Byte]] = apply("SHA-256")
lazy val Sha1: CryptoHasher[Array[Byte], Array[Byte]] = apply("SHA-1")
def apply(algorithm: String): CryptoHasher[Array[Byte], Array[Byte]] = new JdkCryptoHasher(algorithm)
}

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto package fluence.crypto.keystore
import java.io.File import java.io.File
import java.nio.file.Files import java.nio.file.Files
@ -23,7 +23,7 @@ import java.nio.file.Files
import cats.MonadError import cats.MonadError
import cats.syntax.flatMap._ import cats.syntax.flatMap._
import cats.syntax.functor._ import cats.syntax.functor._
import fluence.crypto.keypair.KeyPair import fluence.crypto.{signature, KeyPair}
import io.circe.parser.decode import io.circe.parser.decode
import io.circe.syntax._ import io.circe.syntax._
@ -89,9 +89,9 @@ object FileKeyStorage {
* @param algo Sign algo * @param algo Sign algo
* @return Keypair, either loaded or freshly generated * @return Keypair, either loaded or freshly generated
*/ */
def getKeyPair[F[_]](keyPath: String, algo: SignAlgo)(implicit F: MonadError[F, Throwable]): F[KeyPair] = { def getKeyPair[F[_]](keyPath: String, algo: signature.SignAlgo)(implicit F: MonadError[F, Throwable]): F[KeyPair] = {
val keyFile = new File(keyPath) val keyFile = new File(keyPath)
val keyStorage = new FileKeyStorage[F](keyFile) val keyStorage = new FileKeyStorage[F](keyFile)
keyStorage.getOrCreateKeyPair(algo.generateKeyPair[F]().value.flatMap(F.fromEither)) keyStorage.getOrCreateKeyPair(algo.generateKeyPair.runF[F](None))
} }
} }

View File

@ -15,11 +15,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto package fluence.crypto.keystore
import cats.Monad import cats.Monad
import cats.data.EitherT import cats.data.EitherT
import fluence.crypto.keypair.KeyPair import fluence.crypto.KeyPair
import io.circe.parser.decode import io.circe.parser.decode
import io.circe.{Decoder, Encoder, HCursor, Json} import io.circe.{Decoder, Encoder, HCursor, Json}
import scodec.bits.{Bases, ByteVector} import scodec.bits.{Bases, ByteVector}

View File

@ -15,9 +15,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package fluence.crypto package fluence.crypto.keystore
import fluence.crypto.keypair.KeyPair import fluence.crypto.KeyPair
import org.scalatest.{Matchers, WordSpec} import org.scalatest.{Matchers, WordSpec}
import scodec.bits.{Bases, ByteVector} import scodec.bits.{Bases, ByteVector}

View File

@ -1,75 +0,0 @@
/*
* 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
import cats.Monad
import cats.data.EitherT
import fluence.crypto.SignAlgo.CheckerFn
import fluence.crypto.algorithm.{CryptoErr, DumbSign, KeyGenerator, SignatureFunctions}
import fluence.crypto.keypair.KeyPair
import fluence.crypto.signature.{Signature, SignatureChecker, Signer}
import scodec.bits.ByteVector
import scala.language.higherKinds
/**
* Class for generation keys, signers and checkers
* @param name Algo name for debugging
* @param algo implementation of sign alghoritms, e.g. ECDSA
*/
class SignAlgo(name: String, algo: KeyGenerator with SignatureFunctions) {
def generateKeyPair[F[_]: Monad](seed: Option[ByteVector] = None): EitherT[F, CryptoErr, KeyPair] =
algo.generateKeyPair(seed.map(_.toArray))
/**
* Signer is specific for each keypair
* @param kp Keypair, used to sign
* @return
*/
def signer(kp: KeyPair): Signer = new Signer {
override def sign[F[_]: Monad](plain: ByteVector): EitherT[F, CryptoErr, Signature] = algo.sign(kp, plain)
override def publicKey: KeyPair.Public = kp.publicKey
override def toString: String = s"Signer($name, ${kp.publicKey})"
}
/**
* Checker is specific for public key
* @param publicKey Public key of signature maker
* @return
*/
def checker(publicKey: KeyPair.Public): SignatureChecker = new SignatureChecker {
override def check[F[_]: Monad](signature: Signature, plain: ByteVector): EitherT[F, CryptoErr, Unit] =
algo.verify(publicKey, signature, plain)
override def toString: String = s"SignatureChecker($name)"
}
/** Fn for creating checker for specified public key */
implicit val checkerFn: CheckerFn = pubKey checker(pubKey)
override def toString: String = s"SignAlgo($name)"
}
object SignAlgo {
type CheckerFn = KeyPair.Public SignatureChecker
val dumb = new SignAlgo("dumb", new DumbSign())
}

View File

@ -1,20 +0,0 @@
/*
* 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
trait Algorithm

View File

@ -1,27 +0,0 @@
/*
* 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 fluence.crypto.keypair.KeyPair
import scala.language.higherKinds
trait CipherFunctions[F[_]] {
def encrypt(keyPair: KeyPair, message: Array[Byte]): F[Array[Byte]]
def decrypt(keyPair: KeyPair, message: Array[Byte]): F[Array[Byte]]
}

View File

@ -1,42 +0,0 @@
/*
* 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 java.security.SecureRandom
import cats.Monad
import cats.data.EitherT
import fluence.crypto.keypair.KeyPair
import fluence.crypto.signature.Signature
import scodec.bits.ByteVector
import scala.language.higherKinds
class DumbSign extends KeyGenerator with SignatureFunctions {
override def generateKeyPair[F[_]: Monad](seed: Option[Array[Byte]] = None): EitherT[F, CryptoErr, KeyPair] = {
val s = seed.getOrElse(new SecureRandom().generateSeed(10))
EitherT.pure(KeyPair.fromBytes(s, s))
}
override def sign[F[_]: Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoErr, Signature] =
EitherT.pure(Signature(message.reverse))
override def verify[F[_]: Monad](pubKey: KeyPair.Public, signature: Signature, message: ByteVector): EitherT[F, CryptoErr, Unit] =
EitherT.cond[F](signature.sign == message.reverse, (), CryptoErr("Invalid Signature"))
}

View File

@ -1,71 +0,0 @@
/*
* 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.cipher
import cats.Monad
import cats.syntax.flatMap._
import cats.syntax.functor._
import fluence.codec.Codec
import scala.language.higherKinds
/**
* Base interface for encrypting/decrypting.
* TODO: switch to Codec; notice that Crypt provides effect: decrypt may fail
*
* @tparam P The type of plain text, input
* @tparam C The type of cipher text, output
*/
trait Crypt[F[_], P, C] {
def encrypt(plainText: P): F[C]
def decrypt(cipherText: C): F[P]
}
object Crypt {
def apply[F[_], O, B](implicit crypt: Crypt[F, O, B]): Crypt[F, O, B] = crypt
implicit def transform[F[_]: Monad, K, K1, V, V1](
crypt: Crypt[F, K, V]
)(
implicit
plainTextCodec: Codec[F, K1, K],
cipherTextCodec: Codec[F, V1, V]
): Crypt[F, K1, V1] =
new Crypt[F, K1, V1] {
override def encrypt(plainText: K1): F[V1] =
for {
pt plainTextCodec.encode(plainText)
v crypt.encrypt(pt)
v1 cipherTextCodec.decode(v)
} yield v1
override def decrypt(cipherText: V1): F[K1] =
for {
ct cipherTextCodec.encode(cipherText)
v crypt.decrypt(ct)
v1 plainTextCodec.decode(v)
} yield v1
}
}

View File

@ -1,77 +0,0 @@
/*
* 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.cipher
import cats.Monad
import cats.syntax.functor._
import scala.collection.Searching.{Found, InsertionPoint, SearchResult}
import scala.language.implicitConversions
import scala.math.Ordering
/**
* Wrapper for indexedSeq that provide search functionality over encrypted data.
*
* Example usage:
* {{{
* import fluence.crypto.CryptoSearching._
* implicit val decryptFn = ???
* val l = List(enc("a"), enc("b"), enc("c"), enc("d"), enc("e"))
* l.search("c")
* // == Found(2)
* }}}
*/
object CryptoSearching {
class CryptoSearchImpl[F[_], A](coll: IndexedSeq[A])(implicit F: Monad[F]) {
/**
* Searches the specified indexedSeq for the search element using the binary search algorithm.
* The sequence should be sorted with the same `Ordering` before calling, otherwise, the results are undefined.
*
* @param searchElem Search plaintext element.
* @param decrypt Decryption function for sequence elements.
* @param ordering The ordering to be used to compare elements.
*
* @return A `Found` value containing the index corresponding to the search element in the
* sequence. A `InsertionPoint` value containing the index where the element would be inserted if
* the search element is not found in the sequence.
*/
final def binarySearch[B](searchElem: B)(implicit ordering: Ordering[B], decrypt: A F[B]): F[SearchResult] = {
F.tailRecM((0, coll.length)) {
case (from, to) if from == to F.pure(Right(InsertionPoint(from)))
case (from, to)
val idx = from + (to - from - 1) / 2
decrypt(coll(idx)).map { d
math.signum(ordering.compare(searchElem, d)) match {
case -1 Left((from, idx))
case 1 Left((idx + 1, to))
case _ Right(Found(idx))
}
}
}
}
}
implicit def search[F[_]: Monad, A](indexedSeq: IndexedSeq[A]): CryptoSearchImpl[F, A] =
new CryptoSearchImpl(indexedSeq)
implicit def search[F[_]: Monad, A](array: Array[A]): CryptoSearchImpl[F, A] =
new CryptoSearchImpl(array)
}

View File

@ -1,53 +0,0 @@
/*
* 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.cipher
import java.nio.ByteBuffer
import cats.Applicative
import cats.syntax.applicative._
import scala.language.higherKinds
/**
* No operation implementation. Just convert the element to bytes back and forth without any cryptography.
*/
class NoOpCrypt[F[_], T](serializer: T F[Array[Byte]], deserializer: Array[Byte] F[T])
extends Crypt[F, T, Array[Byte]] {
def encrypt(plainText: T): F[Array[Byte]] = serializer(plainText)
def decrypt(cipherText: Array[Byte]): F[T] = deserializer(cipherText)
}
object NoOpCrypt {
def forString[F[_]: Applicative]: NoOpCrypt[F, String] =
apply[F, String](serializer = _.getBytes.pure[F], deserializer = bytes new String(bytes).pure[F])
def forLong[F[_]: Applicative]: NoOpCrypt[F, Long] =
apply[F, Long](
serializer = ByteBuffer.allocate(java.lang.Long.BYTES).putLong(_).array().pure[F],
deserializer = bytes ByteBuffer.wrap(bytes).getLong().pure[F]
)
def apply[F[_], T](serializer: T F[Array[Byte]], deserializer: Array[Byte] F[T]): NoOpCrypt[F, T] =
new NoOpCrypt(serializer, deserializer)
}

View File

@ -1,76 +0,0 @@
/*
* 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.hash
import cats.kernel.Semigroup
import cats.{Contravariant, Functor, Monoid, Traverse}
import scala.language.{higherKinds, reflectiveCalls}
/**
* TODO add F[_] effect
* Base interface for hashing.
*
* @tparam M type of message for hashing
* @tparam H type of hashed message
*/
trait CryptoHasher[M, H] {
self
def hash(msg: M): H
def hash(msg1: M, msgN: M*): H
def hashM[F[_]: Traverse](ms: F[M])(implicit M: Monoid[M]): H =
hash(Traverse[F].fold(ms))
final def map[B](f: H B): CryptoHasher[M, B] = new CryptoHasher[M, B] {
override def hash(msg: M): B = f(self.hash(msg))
override def hash(msg1: M, msgN: M*): B = f(self.hash(msg1, msgN: _*))
}
final def contramap[N](f: N M): CryptoHasher[N, H] = new CryptoHasher[N, H] {
override def hash(msg: N): H = self.hash(f(msg))
override def hash(msg1: N, msgN: N*): H = self.hash(f(msg1), msgN.map(f): _*)
}
}
object CryptoHasher {
type Bytes = CryptoHasher[Array[Byte], Array[Byte]]
def buildM[M: Semigroup, H](h: M H): CryptoHasher[M, H] = new CryptoHasher[M, H] {
override def hash(msg: M): H = h(msg)
override def hash(msg1: M, msgN: M*): H = h(msgN.foldLeft(msg1)(Semigroup[M].combine))
}
implicit def cryptoHasherFunctor[M]: Functor[({ type λ[α] = CryptoHasher[M, α] })#λ] =
new Functor[({ type λ[α] = CryptoHasher[M, α] })#λ] {
override def map[A, B](fa: CryptoHasher[M, A])(f: A B): CryptoHasher[M, B] =
fa.map(f)
}
implicit def cryptoHasherContravariant[H]: Contravariant[({ type λ[α] = CryptoHasher[α, H] })#λ] =
new Contravariant[({ type λ[α] = CryptoHasher[α, H] })#λ] {
override def contramap[A, B](fa: CryptoHasher[A, H])(f: B A): CryptoHasher[B, H] =
fa.contramap(f)
}
}

View File

@ -1,28 +0,0 @@
/*
* 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.hash
/**
* No operation implementation. Do nothing, just return the same value.
*/
object NoOpCryptoHasher extends CryptoHasher[Array[Byte], Array[Byte]] {
override def hash(msg: Array[Byte]): Array[Byte] = msg
override def hash(msg1: Array[Byte], msgN: Array[Byte]*): Array[Byte] = msg1 ++ msgN.flatten
}

View File

@ -1,31 +0,0 @@
/*
* 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.hash
/** For testing and debugging. The result is human readable. */
object TestCryptoHasher extends CryptoHasher[Array[Byte], Array[Byte]] {
override def hash(msg: Array[Byte]): Array[Byte] = {
("H<" + new String(msg) + ">").getBytes()
}
override def hash(msg1: Array[Byte], msgN: Array[Byte]*): Array[Byte] = {
hash(msg1 ++ msgN.flatten)
}
}