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/>.
*/
package fluence.crypto.algorithm
package fluence.crypto.aes
import cats.Monad
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 cats.syntax.compose._
import fluence.codec.PureCodec
import fluence.crypto.CryptoError.nonFatalHandling
import fluence.crypto.facade.cryptojs.{CryptOptions, CryptoJS, Key, KeyOptions}
import fluence.crypto.{Crypto, CryptoError}
import scodec.bits.ByteVector
import scalajs.js.JSConverters._
import scala.language.higherKinds
import scala.scalajs.js.JSConverters._
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]] {
class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) {
private val salt = config.salt
@ -52,28 +48,26 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
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
val encrypt: Crypto.Func[Array[Byte], Array[Byte]] =
new Crypto.Func[Array[Byte], Array[Byte]] {
override def apply[F[_]: Monad](input: Array[Byte]): EitherT[F, CryptoError, Array[Byte]] =
for {
key initSecretKey()
encrypted encryptData(input, 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)
}
val decrypt: Crypto.Func[Array[Byte], Array[Byte]] =
new Crypto.Func[Array[Byte], Array[Byte]] {
override def apply[F[_]: Monad](input: Array[Byte]): EitherT[F, CryptoError, Array[Byte]] =
for {
detachedData detachData(input)
(iv, base64) = detachedData
key initSecretKey()
decData decryptData(key, base64, iv)
_ EitherT.cond(decData.nonEmpty, decData, CryptoError("Cannot decrypt message with this password."))
} yield decData.toArray
}
/**
* Encrypt data.
@ -81,7 +75,7 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
* @param key Salted and hashed password
* @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 {
//transform data to JS type
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.")
}
private def decryptData(key: Key, base64Data: String, iv: Option[String]) = {
private def decryptData[F[_]: Monad](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)
@ -108,7 +102,7 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
* @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)] = {
private def detachData[F[_]: Monad](cipherText: Array[Byte]): EitherT[F, CryptoError, (Option[String], String)] = {
nonFatalHandling {
val dataWithParams = if (withIV) {
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
*/
private def initSecretKey(): EitherT[F, CryptoErr, Key] = {
private def initSecretKey[F[_]: Monad](): EitherT[F, CryptoError, Key] = {
nonFatalHandling {
// get raw key from password and salt
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 {
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 build(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[Array[Byte]] = {
val aes = new AesCrypt(password.toHex.toCharArray, withIV, config)
Crypto.Bijection(aes.encrypt, aes.decrypt)
}
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)
def forString(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[String] = {
implicit val codec: PureCodec[String, Array[Byte]] =
PureCodec.build(_.getBytes, bytes new String(bytes))
apply[String](password, 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
import cats.instances.try_._
import fluence.crypto.algorithm.{AesConfig, AesCrypt, CryptoErr}
import fluence.crypto.aes.{AesConfig, AesCrypt}
import org.scalactic.source.Position
import org.scalatest.{Assertion, Matchers, WordSpec}
import scodec.bits.ByteVector
@ -31,50 +31,51 @@ class AesJSSpec extends WordSpec with Matchers with slogging.LazyLogging {
val conf = AesConfig()
// TODO: use properties testing
"aes crypto" should {
"work with IV" in {
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 crypted = crypt.encrypt(str).get
crypt.decrypt(crypted).get shouldBe str
val crypted = crypt.direct.unsafe(str)
crypt.inverse.unsafe(crypted) shouldBe str
val fakeAes = AesCrypt.forString[Try](ByteVector("wrong".getBytes()), withIV = true, config = conf)
checkCryptoError(fakeAes.decrypt(crypted), str)
val fakeAes = AesCrypt.forString(ByteVector("wrong".getBytes()), withIV = true, config = conf)
checkCryptoError(fakeAes.inverse.runF[Try](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 aesWithoutIV = AesCrypt.forString(pass, withIV = false, config = conf)
aesWithoutIV.inverse.unsafe(crypted) shouldNot be(str)
val aesWrongSalt = AesCrypt.forString[Try](pass, withIV = true, config = conf.copy(salt = rndString(10)))
checkCryptoError(aesWrongSalt.decrypt(crypted), str)
val aesWrongSalt = AesCrypt.forString(pass, withIV = true, config = conf.copy(salt = rndString(10)))
checkCryptoError(aesWrongSalt.inverse.runF[Try](crypted), str)
}
"work without IV" in {
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 crypted = crypt.encrypt(str).get
crypt.decrypt(crypted).get shouldBe str
val crypted = crypt.direct.unsafe(str)
crypt.inverse.unsafe(crypted) shouldBe str
val fakeAes = AesCrypt.forString[Try](ByteVector("wrong".getBytes()), withIV = false, config = conf)
checkCryptoError(fakeAes.decrypt(crypted), str)
val fakeAes = AesCrypt.forString(ByteVector("wrong".getBytes()), withIV = false, config = conf)
checkCryptoError(fakeAes.inverse.runF[Try](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 aesWithIV = AesCrypt.forString(pass, withIV = true, config = conf)
aesWithIV.inverse.unsafe(crypted) shouldNot be(str)
val aesWrongSalt = AesCrypt.forString[Try](pass, withIV = false, config = conf.copy(salt = rndString(10)))
checkCryptoError(aesWrongSalt.decrypt(crypted), str)
val aesWrongSalt = AesCrypt.forString(pass, withIV = false, config = conf.copy(salt = rndString(10)))
checkCryptoError(aesWrongSalt.inverse.runF[Try](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: CryptoError true
case e
logger.error("Unexpected error", e)
false

View File

@ -15,20 +15,20 @@
* 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.syntax.flatMap._
import cats.{Applicative, Monad, MonadError}
import cats.syntax.compose._
import fluence.codec.PureCodec
import fluence.crypto.cipher.Crypt
import org.bouncycastle.crypto.{CipherParameters, PBEParametersGenerator}
import fluence.crypto.{Crypto, CryptoError, JavaAlgorithm}
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, PaddedBufferedBlockCipher}
import org.bouncycastle.crypto.params.{KeyParameter, ParametersWithIV}
import org.bouncycastle.crypto.{CipherParameters, PBEParametersGenerator}
import scodec.bits.ByteVector
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
* message
*/
class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: AesConfig)(
implicit ME: MonadError[F, Throwable],
codec: PureCodec[T, Array[Byte]]
) extends Crypt[F, T, Array[Byte]] with JavaAlgorithm {
import CryptoErr._
class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) extends JavaAlgorithm {
import CryptoError.nonFatalHandling
private val rnd = Random
private val salt = config.salt.getBytes()
@ -69,35 +66,34 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
iv
}
override def encrypt(plainText: T): F[Array[Byte]] = {
val e = for {
data codec
.direct[F](plainText)
.leftMap(err CryptoErr("Cannot encode plain text to bytes to encrypt it", Some(err)))
key initSecretKey(password, salt)
extDataWithParams extDataWithParams(key)
encData processData(DataWithParams(data, extDataWithParams._2), extDataWithParams._1, encrypt = true)
} yield encData
val encrypt: Crypto.Func[Array[Byte], Array[Byte]] =
new Crypto.Func[Array[Byte], Array[Byte]] {
override def apply[F[_]: Monad](input: Array[Byte]): EitherT[F, CryptoError, Array[Byte]] =
for {
key initSecretKey(password, salt)
extDataWithParams extDataWithParams(key)
encData processData(DataWithParams(input, extDataWithParams._2), extDataWithParams._1, encrypt = true)
} yield encData
e.value.flatMap(ME.fromEither)
}
}
override def decrypt(cipherText: Array[Byte]): F[T] = {
val e = for {
dataWithParams detachDataAndGetParams(cipherText, password, salt, withIV)
decData processData(dataWithParams, None, encrypt = false)
plain codec.inverse[F](decData).leftMap(err CryptoErr("Cannot decode decrypted text", Some(err)))
} yield plain
e.value.flatMap(ME.fromEither)
}
val decrypt: Crypto.Func[Array[Byte], Array[Byte]] =
new Crypto.Func[Array[Byte], Array[Byte]] {
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)
} yield decData
}
/**
* 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)] = {
def extDataWithParams[F[_]: Monad](
key: Array[Byte]
): EitherT[F, CryptoError, (Option[Array[Byte]], CipherParameters)] = {
if (withIV) {
val ivData = generateIV
@ -111,7 +107,10 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
/**
* 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 {
PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password)
}("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
* @return cipher
*/
private def setupAesCipher(
private def setupAesCipher[F[_]: Monad](
params: CipherParameters,
encrypt: Boolean
): EitherT[F, CryptoErr, PaddedBufferedBlockCipher] = {
): EitherT[F, CryptoError, PaddedBufferedBlockCipher] = {
nonFatalHandling {
// setup AES cipher in CBC mode with PKCS7 padding
val padding = new PKCS7Padding
@ -136,7 +135,10 @@ class AesCrypt[F[_]: Monad, T](password: Array[Char], withIV: Boolean, config: A
}("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 {
// create a temporary buffer to decode into (it'll include padding)
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
* @return Crypted bytes
*/
private def processData(
private def processData[F[_]: Monad](
dataWithParams: DataWithParams,
addData: Option[Array[Byte]],
encrypt: Boolean
): EitherT[F, CryptoErr, Array[Byte]] = {
): EitherT[F, CryptoError, Array[Byte]] = {
for {
cipher setupAesCipher(dataWithParams.params, encrypt = encrypt)
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
*/
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 {
val ivData = data.slice(0, ivSize)
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.")
}
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
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 {
val pGen = new PKCS5S2ParametersGenerator(new SHA256Digest)
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")
}
private def detachDataAndGetParams(
private def detachDataAndGetParams[F[_]: Monad](
data: Array[Byte],
password: Array[Char],
salt: Array[Byte],
withIV: Boolean
): EitherT[F, CryptoErr, DataWithParams] = {
): EitherT[F, CryptoError, DataWithParams] = {
if (withIV) {
for {
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 {
def forString[F[_]: Applicative](password: ByteVector, withIV: Boolean, config: AesConfig)(
implicit ME: MonadError[F, Throwable]
): AesCrypt[F, String] = {
implicit val codec: PureCodec[String, Array[Byte]] =
PureCodec.liftB[String, Array[Byte]](_.getBytes, new String(_))
apply[F, String](password, withIV, config)
def build(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[Array[Byte]] = {
val aes = new AesCrypt(password.toHex.toCharArray, withIV, config)
Crypto.Bijection(aes.encrypt, aes.decrypt)
}
def apply[F[_]: Applicative, T](password: ByteVector, withIV: Boolean, config: AesConfig)(
implicit ME: MonadError[F, Throwable],
codec: PureCodec[T, Array[Byte]]
): AesCrypt[F, T] =
new AesCrypt(password.toHex.toCharArray, withIV, config)
def forString(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[String] = {
implicit val codec: PureCodec[String, Array[Byte]] =
PureCodec.build(_.getBytes, bytes new String(bytes))
apply[String](password, 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
import fluence.crypto.algorithm.{AesConfig, AesCrypt, CryptoErr}
import cats.instances.try_._
import fluence.crypto.aes.{AesConfig, AesCrypt}
import org.scalactic.source.Position
import org.scalatest.{Assertion, Matchers, WordSpec}
import scodec.bits.ByteVector
@ -34,46 +34,46 @@ class AesSpec extends WordSpec with Matchers with slogging.LazyLogging {
"work with IV" in {
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 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))
val crypted = crypt.direct.unsafe(str)
crypt.inverse.unsafe(crypted) shouldBe str
val fakeAes = AesCrypt.forString(ByteVector("wrong".getBytes()), withIV = true, config = conf)
checkCryptoError(fakeAes.inverse.runF[Try](crypted))
//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 aesWithoutIV = AesCrypt.forString(pass, withIV = false, config = conf)
aesWithoutIV.inverse.unsafe(crypted) shouldNot be(str)
val aesWrongSalt = AesCrypt.forString[Try](pass, withIV = true, config = conf.copy(salt = rndString(10)))
checkCryptoError(aesWrongSalt.decrypt(crypted))
val aesWrongSalt = AesCrypt.forString(pass, withIV = true, config = conf.copy(salt = rndString(10)))
checkCryptoError(aesWrongSalt.inverse.runF[Try](crypted))
}
"work without IV" in {
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 crypted = crypt.encrypt(str).get
crypt.decrypt(crypted).get shouldBe str
val crypted = crypt.direct.unsafe(str)
crypt.inverse.unsafe(crypted) shouldBe str
val fakeAes = AesCrypt.forString[Try](ByteVector("wrong".getBytes()), withIV = false, config = conf)
checkCryptoError(fakeAes.decrypt(crypted))
val fakeAes = AesCrypt.forString(ByteVector("wrong".getBytes()), withIV = false, config = conf)
checkCryptoError(fakeAes.inverse.runF[Try](crypted))
//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 aesWithIV = AesCrypt.forString(pass, withIV = true, config = conf)
aesWithIV.inverse.unsafe(crypted) shouldNot be(str)
val aesWrongSalt = AesCrypt.forString[Try](pass, withIV = true, config = conf.copy(salt = rndString(10)))
checkCryptoError(aesWrongSalt.decrypt(crypted))
val aesWrongSalt = AesCrypt.forString(pass, withIV = true, config = conf.copy(salt = rndString(10)))
checkCryptoError(aesWrongSalt.inverse.runF[Try](crypted))
}
}
def checkCryptoError(tr: Try[String])(implicit pos: Position): Assertion = {
tr.map(_ false)
.recover {
case e: CryptoErr true
case e: CryptoError true
case e
logger.error("Unexpected error", e)
false

View File

@ -15,7 +15,7 @@
* 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

View File

@ -15,18 +15,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fluence.crypto.algorithm
package fluence.crypto
import cats.Monad
import cats.data.EitherT
import fluence.crypto.keypair.KeyPair
import fluence.crypto.signature.Signature
import scodec.bits.ByteVector
import fluence.codec.{CodecError, MonadicalEitherArrow, PureCodec}
import scala.language.higherKinds
object Crypto extends MonadicalEitherArrow[CryptoError] {
type Hasher[A, B] = Func[A, B]
trait SignatureFunctions {
def sign[F[_]: Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoErr, Signature]
type Cipher[A] = Bijection[A, Array[Byte]]
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/>.
*/
package fluence.crypto.algorithm
package fluence.crypto
import cats.Applicative
import cats.data.EitherT
import scala.language.higherKinds
import scala.util.control.{NoStackTrace, NonFatal}
case class CryptoErr(errorMessage: String, causedBy: Option[Throwable] = None)
extends Throwable(errorMessage) with NoStackTrace {
case class CryptoError(message: String, causedBy: Option[Throwable] = None) extends NoStackTrace {
override def getMessage: String = message
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
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)
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/>.
*/
package fluence.crypto.keypair
package fluence.crypto
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
import fluence.crypto.keypair.KeyPair
import fluence.crypto.KeyPair
/**
* Container for public key of signer and a signature.

View File

@ -17,30 +17,25 @@
package fluence.crypto.signature
import cats.Monad
import cats.data.EitherT
import fluence.crypto.algorithm.CryptoErr
import fluence.crypto.keypair.KeyPair
import scodec.bits.ByteVector
import scala.language.higherKinds
import fluence.crypto.{Crypto, KeyPair}
/**
* 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 {
def publicKey: KeyPair.Public
def sign[F[_]: Monad](plain: ByteVector): EitherT[F, CryptoErr, Signature]
}
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))
}
case class SignAlgo(
name: String,
generateKeyPair: Crypto.KeyPairGenerator,
signer: SignAlgo.SignerFn,
implicit val checker: SignAlgo.CheckerFn,
)
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.data.EitherT
import fluence.crypto.algorithm.CryptoErr
import fluence.crypto.CryptoError
import scodec.bits.ByteVector
import scala.language.higherKinds
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/>.
*/
package fluence.crypto.algorithm
package fluence.crypto.signature
import cats.Monad
import cats.data.EitherT
import fluence.crypto.keypair.KeyPair
import cats.syntax.profunctor._
import fluence.crypto.{Crypto, KeyPair}
import scodec.bits.ByteVector
import scala.language.higherKinds
trait KeyGenerator {
def generateKeyPair[F[_]: Monad](seed: Option[Array[Byte]] = None): EitherT[F, CryptoErr, KeyPair]
case class Signer(publicKey: KeyPair.Public, sign: Crypto.Func[ByteVector, Signature]) {
lazy val signWithPK: Crypto.Func[ByteVector, PubKeyAndSignature] = sign.rmap(PubKeyAndSignature(publicKey, _))
}

View File

@ -17,33 +17,30 @@
package fluence.crypto
import cats.instances.try_._
import fluence.crypto.cipher.NoOpCrypt
import fluence.crypto.cipher.CipherSearch
import org.scalatest.{Matchers, WordSpec}
import scala.collection.Searching.{Found, InsertionPoint}
import scala.util.Try
class CryptoSearchingSpec extends WordSpec with Matchers {
"search" should {
"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 encryptedElements = plainTextElements.map(t crypt.encrypt(t).get)
val encryptedElements = plainTextElements.map(t crypt.direct.unsafe(t))
import fluence.crypto.cipher.CryptoSearching._
implicit val decryptFn: Array[Byte] Try[String] = crypt.decrypt
val search = CipherSearch.binarySearch(encryptedElements, crypt.inverse)
encryptedElements.binarySearch("B").get shouldBe Found(1)
encryptedElements.binarySearch("D").get shouldBe Found(3)
encryptedElements.binarySearch("E").get shouldBe Found(4)
search.unsafe("B") shouldBe Found(1)
search.unsafe("D") shouldBe Found(3)
search.unsafe("E") shouldBe Found(4)
encryptedElements.binarySearch("0").get shouldBe InsertionPoint(0)
encryptedElements.binarySearch("BB").get shouldBe InsertionPoint(2)
encryptedElements.binarySearch("ZZ").get shouldBe InsertionPoint(5)
search.unsafe("0") shouldBe InsertionPoint(0)
search.unsafe("BB") shouldBe InsertionPoint(2)
search.unsafe("ZZ") shouldBe InsertionPoint(5)
}
}

View File

@ -17,7 +17,6 @@
package fluence.crypto
import fluence.crypto.cipher.NoOpCrypt
import org.scalatest.{Matchers, WordSpec}
class NoOpCryptSpec extends WordSpec with Matchers {
@ -25,22 +24,14 @@ class NoOpCryptSpec extends WordSpec with Matchers {
"NoOpCrypt" should {
"convert a string to bytes back and forth without any cryptography" in {
val noOpCrypt = NoOpCrypt.forString
val noOpCrypt = DumbCrypto.cipherString
val emptyString = ""
noOpCrypt.decrypt(noOpCrypt.encrypt(emptyString)) shouldBe emptyString
noOpCrypt.inverse.unsafe(noOpCrypt.direct.unsafe(emptyString)) shouldBe emptyString
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)
noOpCrypt.encrypt(noOpCrypt.decrypt(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
noOpCrypt.direct.unsafe(noOpCrypt.inverse.unsafe(byteArray)) shouldBe byteArray
}
}
}

View File

@ -15,15 +15,14 @@
* 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.data.EitherT
import fluence.crypto.SignAlgo
import fluence.crypto._
import fluence.crypto.facade.ecdsa.EC
import fluence.crypto.hash.{CryptoHasher, JsCryptoHasher}
import fluence.crypto.keypair.KeyPair
import fluence.crypto.signature.Signature
import fluence.crypto.hash.JsCryptoHasher
import fluence.crypto.signature.{SignAlgo, Signature, SignatureChecker, Signer}
import scodec.bits.ByteVector
import scala.language.higherKinds
@ -35,23 +34,24 @@ import scala.scalajs.js.typedarray.Uint8Array
* 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 Ecdsa(ec: EC, hasher: Option[CryptoHasher[Array[Byte], Array[Byte]]])
extends Algorithm with SignatureFunctions with KeyGenerator {
import CryptoErr._
class Ecdsa(ec: EC, hasher: Option[Crypto.Hasher[Array[Byte], Array[Byte]]]) {
import CryptoError.nonFatalHandling
override def generateKeyPair[F[_]: Monad](seed: Option[Array[Byte]] = None): EitherT[F, CryptoErr, KeyPair] = {
nonFatalHandling {
val seedJs = seed.map(bs js.Dynamic.literal(entropy = bs.toJSArray))
val key = ec.genKeyPair(seedJs)
val publicHex = key.getPublic(true, "hex")
val secretHex = key.getPrivate("hex")
val public = ByteVector.fromValidHex(publicHex)
val secret = ByteVector.fromValidHex(secretHex)
KeyPair.fromByteVectors(public, secret)
}("Failed to generate key pair.")
}
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 {
val seedJs = input.map(bs js.Dynamic.literal(entropy = bs.toJSArray))
val key = ec.genKeyPair(seedJs)
val publicHex = key.getPublic(true, "hex")
val secretHex = key.getPrivate("hex")
val public = ByteVector.fromValidHex(publicHex)
val secret = ByteVector.fromValidHex(secretHex)
KeyPair.fromByteVectors(public, secret)
}("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 {
secret nonFatalHandling {
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)
signHex nonFatalHandling(secret.sign(new Uint8Array(hash)).toDER("hex"))("Cannot sign message")
} 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
hasher
.fold(EitherT.pure[F, CryptoErr](arr)) { h
nonFatalHandling {
h.hash(message.toArray)
}("Cannot hash message.")
.fold(EitherT.pure[F, CryptoError](arr)) { h
h[F](arr)
}
.map(_.toJSArray)
}
override def verify[F[_]: Monad](
def verify[F[_]: Monad](
pubKey: KeyPair.Public,
signature: Signature,
message: ByteVector
): EitherT[F, CryptoErr, Unit] = {
): EitherT[F, CryptoError, Unit] =
for {
public nonFatalHandling {
val hex = pubKey.value.toHex
@ -84,14 +81,33 @@ class Ecdsa(ec: EC, hasher: Option[CryptoHasher[Array[Byte], Array[Byte]]])
}("Incorrect public key format.")
hash hash(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 ()
}
}
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)
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
object CryptoHashers {
lazy val Sha1: CryptoHasher.Bytes = JdkCryptoHasher.Sha1
import fluence.crypto.Crypto
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
import fluence.crypto.{Crypto, CryptoError}
import fluence.crypto.facade.ecdsa.{SHA1, SHA256}
import scodec.bits.ByteVector
import scala.scalajs.js.JSConverters._
import scala.scalajs.js.typedarray.Uint8Array
import scala.util.Try
object JsCryptoHasher {
lazy val Sha256: CryptoHasher.Bytes =
CryptoHasher.buildM[Array[Byte], Array[Byte]] { msg
val sha256 = new SHA256()
sha256.update(new Uint8Array(msg.toJSArray))
ByteVector.fromValidHex(sha256.digest("hex")).toArray
}(_ ++ _)
lazy val Sha256: Crypto.Hasher[Array[Byte], Array[Byte]] =
Crypto.liftFuncEither[Array[Byte], Array[Byte]] { msg
Try {
val sha256 = new SHA256()
sha256.update(new Uint8Array(msg.toJSArray))
ByteVector.fromValidHex(sha256.digest("hex")).toArray
}.toEither.left.map(err CryptoError("Cannot calculate Sha256 hash", Some(err)))
}
lazy val Sha1: CryptoHasher.Bytes =
CryptoHasher.buildM[Array[Byte], Array[Byte]] { msg
val sha1 = new SHA1()
sha1.update(new Uint8Array(msg.toJSArray))
ByteVector.fromValidHex(sha1.digest("hex")).toArray
}(_ ++ _)
lazy val Sha1: Crypto.Hasher[Array[Byte], Array[Byte]] =
Crypto.liftFuncEither[Array[Byte], Array[Byte]] { msg
Try {
val sha1 = new SHA1()
sha1.update(new Uint8Array(msg.toJSArray))
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/>.
*/
package fluence.crypto
package flyence.crypto
import cats.data.EitherT
import cats.instances.try_._
import fluence.crypto.algorithm.{CryptoErr, Ecdsa}
import fluence.crypto.keypair.KeyPair
import fluence.crypto.ecdsa.Ecdsa
import fluence.crypto.signature.Signature
import fluence.crypto.{CryptoError, KeyPair}
import org.scalatest.{Matchers, WordSpec}
import scodec.bits.ByteVector
@ -48,7 +48,7 @@ class EcdsaSpec extends WordSpec with Matchers {
"correct sign and verify data" in {
val algorithm = Ecdsa.ecdsa_secp256k1_sha256
val keys = algorithm.generateKeyPair[Try]().extract
val keys = algorithm.generateKeyPair.unsafe(None)
val pubKey = keys.publicKey
val data = rndByteVector(10)
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 {
val algo = Ecdsa.signAlgo
val keys = algo.generateKeyPair().extract
val keys = algo.generateKeyPair.unsafe(None)
val signer = algo.signer(keys)
val checker = algo.checker(keys.publicKey)
@ -80,16 +80,16 @@ class EcdsaSpec extends WordSpec with Matchers {
"throw an errors on invalid data" in {
val algo = Ecdsa.signAlgo
val keys = algo.generateKeyPair().extract
val keys = algo.generateKeyPair.unsafe(None)
val signer = algo.signer(keys)
val checker = algo.checker(keys.publicKey)
val data = rndByteVector(10)
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)
the[CryptoErr] thrownBy invalidChecker
the[CryptoError] thrownBy invalidChecker
.check(sign, data)
.value
.flatMap(_.toTry)

View File

@ -15,7 +15,7 @@
* 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 org.scalatest.{Matchers, WordSpec}

View File

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

View File

@ -15,17 +15,17 @@
* 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.security._
import java.security.interfaces.ECPrivateKey
import cats.data.EitherT
import cats.Monad
import fluence.crypto.SignAlgo
import fluence.crypto.hash.{CryptoHasher, JdkCryptoHasher}
import fluence.crypto.keypair.KeyPair
import cats.data.EitherT
import fluence.crypto.{KeyPair, _}
import fluence.crypto.hash.JdkCryptoHasher
import fluence.crypto.signature.{SignAlgo, SignatureChecker, Signer}
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.interfaces.ECPublicKey
import org.bouncycastle.jce.provider.BouncyCastleProvider
@ -39,89 +39,88 @@ import scala.language.higherKinds
* @param curveType http://www.bouncycastle.org/wiki/display/JA1/Supported+Curves+%28ECDSA+and+ECGOST%29
* @param scheme https://bouncycastle.org/specifications.html
*/
class Ecdsa(curveType: String, scheme: String, hasher: Option[CryptoHasher[Array[Byte], Array[Byte]]])
extends JavaAlgorithm with SignatureFunctions with KeyGenerator {
import CryptoErr._
class Ecdsa(curveType: String, scheme: String, hasher: Option[Crypto.Hasher[Array[Byte], Array[Byte]]])
extends JavaAlgorithm {
import CryptoError.nonFatalHandling
import Ecdsa._
val HEXradix = 16
override def generateKeyPair[F[_]: Monad](seed: Option[Array[Byte]]): EitherT[F, CryptoErr, KeyPair] = {
for {
ecSpec EitherT.fromOption(
Option(ECNamedCurveTable.getParameterSpec(curveType)),
CryptoErr("Parameter spec for the curve is not available.")
)
g getKeyPairGenerator
_ nonFatalHandling {
g.initialize(ecSpec, seed.map(new SecureRandom(_)).getOrElse(new SecureRandom()))
}(s"Could not initialize KeyPairGenerator")
p EitherT.fromOption(Option(g.generateKeyPair()), CryptoErr("Could not generate KeyPair. Unexpected."))
keyPair nonFatalHandling {
//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 bg = p.getPrivate.asInstanceOf[ECPrivateKey].getS
val sk = ByteVector.fromValidHex(bg.toString(HEXradix))
KeyPair.fromByteVectors(pk, sk)
}("Could not generate KeyPair. Unexpected.")
} yield 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 {
ecSpec EitherT.fromOption(
Option(ECNamedCurveTable.getParameterSpec(curveType)),
CryptoError("Parameter spec for the curve is not available.")
)
g getKeyPairGenerator
_ nonFatalHandling {
g.initialize(ecSpec, input.map(new SecureRandom(_)).getOrElse(new SecureRandom()))
}(s"Could not initialize KeyPairGenerator")
p EitherT.fromOption(Option(g.generateKeyPair()), CryptoError("Could not generate KeyPair. Unexpected."))
keyPair nonFatalHandling {
//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 bg = p.getPrivate.asInstanceOf[ECPrivateKey].getS
val sk = ByteVector.fromValidHex(bg.toString(HEXradix))
KeyPair.fromByteVectors(pk, sk)
}("Could not generate KeyPair. Unexpected.")
} yield keyPair
}
override def sign[F[_]: Monad](
def sign[F[_]: Monad](
keyPair: KeyPair,
message: ByteVector
): EitherT[F, CryptoErr, fluence.crypto.signature.Signature] = {
): EitherT[F, CryptoError, signature.Signature] =
signMessage(new BigInteger(keyPair.secretKey.value.toHex, HEXradix), message.toArray)
.map(bb fluence.crypto.signature.Signature(ByteVector(bb)))
}
override def verify[F[_]: Monad](
def verify[F[_]: Monad](
publicKey: KeyPair.Public,
signature: fluence.crypto.signature.Signature,
message: ByteVector
): EitherT[F, CryptoErr, Unit] = {
): EitherT[F, CryptoError, Unit] =
verifySign(publicKey.bytes, signature.bytes, message.toArray)
}
private def signMessage[F[_]: Monad](
privateKey: BigInteger,
message: Array[Byte]
): EitherT[F, CryptoErr, Array[Byte]] = {
): EitherT[F, CryptoError, Array[Byte]] =
for {
ec curveSpec
keySpec nonFatalHandling(new ECPrivateKeySpec(privateKey, ec))("Cannot read private key.")
keyFactory getKeyFactory
signProvider getSignatureProvider
sign {
nonFatalHandling {
signProvider.initSign(keyFactory.generatePrivate(keySpec))
signProvider.update(hasher.map(h h.hash(message)).getOrElse(message))
signProvider.sign()
}("Cannot sign message.")
}
sign nonFatalHandling {
signProvider.initSign(keyFactory.generatePrivate(keySpec))
signProvider.update(hasher.map(_.unsafe(message)).getOrElse(message))
signProvider.sign()
}("Cannot sign message.")
} yield sign
}
private def verifySign[F[_]: Monad](
publicKey: Array[Byte],
signature: Array[Byte],
message: Array[Byte],
): EitherT[F, CryptoErr, Unit] = {
): EitherT[F, CryptoError, Unit] =
for {
ec curveSpec
keySpec nonFatalHandling(new ECPublicKeySpec(ec.getCurve.decodePoint(publicKey), ec))("Cannot read public key.")
keyFactory getKeyFactory
signProvider getSignatureProvider
verify {
nonFatalHandling {
signProvider.initVerify(keyFactory.generatePublic(keySpec))
signProvider.update(hasher.map(h h.hash(message)).getOrElse(message))
signProvider.verify(signature)
}("Cannot verify message.")
}
_ EitherT.cond[F](verify, (), CryptoErr("Signature is not verified"))
verify nonFatalHandling {
signProvider.initVerify(keyFactory.generatePublic(keySpec))
signProvider.update(hasher.map(_.unsafe(message)).getOrElse(message))
signProvider.verify(signature)
}("Cannot verify message.")
_ EitherT.cond[F](verify, (), CryptoError("Signature is not verified"))
} yield ()
}
private def curveSpec[F[_]: Monad] =
nonFatalHandling(ECNamedCurveTable.getParameterSpec(curveType).asInstanceOf[ECParameterSpec])(
@ -156,5 +155,26 @@ object Ecdsa {
*/
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
object CryptoHashers {
lazy val Sha1: CryptoHasher.Bytes = JsCryptoHasher.Sha1
import fluence.crypto.Crypto
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 hasher = JdkCryptoHasher.Sha256
ByteVector(hasher.hash(str.getBytes())).toHex shouldBe sha256TesterHex
ByteVector(hasher.unsafe(str.getBytes())).toHex shouldBe sha256TesterHex
}
"work with sha1" in {
@ -37,7 +37,7 @@ class JvmHashSpec extends WordSpec with Matchers {
val sha1TesterHex = "879db20eabcecea7d4736a8bae5bc64564b76b2f"
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 {
@ -49,7 +49,7 @@ class JvmHashSpec extends WordSpec with Matchers {
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.instances.try_._
import fluence.crypto.algorithm.{CryptoErr, Ecdsa}
import fluence.crypto.keypair.KeyPair
import fluence.crypto.ecdsa.Ecdsa
import fluence.crypto.keystore.FileKeyStorage
import fluence.crypto.signature.Signature
import org.scalatest.{Matchers, WordSpec}
import scodec.bits.ByteVector
@ -50,7 +50,7 @@ class SignatureSpec extends WordSpec with Matchers {
"correct sign and verify data" in {
val algorithm = Ecdsa.ecdsa_secp256k1_sha256
val keys = algorithm.generateKeyPair[Try]().extract
val keys = algorithm.generateKeyPair.unsafe(None)
val pubKey = keys.publicKey
val data = rndByteVector(10)
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 {
val algo = Ecdsa.signAlgo
val keys = algo.generateKeyPair().extract
val keys = algo.generateKeyPair.unsafe(None)
val signer = algo.signer(keys)
val checker = algo.checker(keys.publicKey)
@ -82,18 +82,18 @@ class SignatureSpec extends WordSpec with Matchers {
"throw an errors on invalid data" in {
val algo = Ecdsa.signAlgo
val keys = algo.generateKeyPair().extract
val keys = algo.generateKeyPair.unsafe(None)
val signer = algo.signer(keys)
val checker = algo.checker(keys.publicKey)
val data = rndByteVector(10)
val sign = signer.sign(data).extract
the[CryptoErr] thrownBy {
the[CryptoError] thrownBy {
checker.check(Signature(rndByteVector(10)), data).value.flatMap(_.toTry).get
}
val invalidChecker = algo.checker(KeyPair.fromByteVectors(rndByteVector(10), rndByteVector(10)).publicKey)
the[CryptoErr] thrownBy {
the[CryptoError] thrownBy {
invalidChecker
.check(sign, data)
.value
@ -104,7 +104,7 @@ class SignatureSpec extends WordSpec with Matchers {
"store and read key from file" in {
val algo = Ecdsa.signAlgo
val keys = algo.generateKeyPair().extract
val keys = algo.generateKeyPair.unsafe(None)
val keyFile = File.createTempFile("test", "")
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/>.
*/
package fluence.crypto
package fluence.crypto.keystore
import java.io.File
import java.nio.file.Files
@ -23,7 +23,7 @@ import java.nio.file.Files
import cats.MonadError
import cats.syntax.flatMap._
import cats.syntax.functor._
import fluence.crypto.keypair.KeyPair
import fluence.crypto.{signature, KeyPair}
import io.circe.parser.decode
import io.circe.syntax._
@ -89,9 +89,9 @@ object FileKeyStorage {
* @param algo Sign algo
* @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 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/>.
*/
package fluence.crypto
package fluence.crypto.keystore
import cats.Monad
import cats.data.EitherT
import fluence.crypto.keypair.KeyPair
import fluence.crypto.KeyPair
import io.circe.parser.decode
import io.circe.{Decoder, Encoder, HCursor, Json}
import scodec.bits.{Bases, ByteVector}

View File

@ -15,9 +15,9 @@
* 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 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)
}
}