mirror of
https://github.com/fluencelabs/crypto
synced 2025-04-24 14:22:18 +00:00
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:
parent
dc8806de44
commit
88034b371f
@ -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)
|
||||
}
|
@ -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
|
@ -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)
|
||||
}
|
@ -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
|
@ -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
|
@ -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))
|
||||
}
|
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
56
core/src/main/scala/fluence/crypto/DumbCrypto.scala
Normal file
56
core/src/main/scala/fluence/crypto/DumbCrypto.scala
Normal 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())
|
||||
}
|
@ -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
|
||||
|
63
core/src/main/scala/fluence/crypto/cipher/CipherSearch.scala
Normal file
63
core/src/main/scala/fluence/crypto/cipher/CipherSearch.scala
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
@ -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
|
||||
}
|
@ -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]
|
||||
}
|
@ -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, _))
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
@ -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
|
||||
}
|
@ -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)))
|
||||
}
|
||||
}
|
@ -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)
|
@ -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}
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
@ -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
|
||||
}
|
@ -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)))
|
||||
)
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
@ -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)
|
||||
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -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}
|
@ -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}
|
||||
|
@ -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())
|
||||
}
|
@ -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
|
@ -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]]
|
||||
}
|
@ -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"))
|
||||
}
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user