4 Commits
0.0.2 ... 0.0.6

Author SHA1 Message Date
38608df192 Slogging dependency is kept only in keystore (#7) 2019-06-13 15:28:05 +03:00
1a8313b321 ed25519 implementation (#6)
* ed25519 implementation
2019-06-06 13:47:24 +03:00
e9b1e2d905 Updated dependencies (#5) 2019-04-18 18:39:48 +03:00
fcf7e08813 Updated dependencies (#3) 2019-01-23 21:30:50 +03:00
14 changed files with 382 additions and 35 deletions

View File

@ -1,5 +1,4 @@
[![Build Status](https://travis-ci.org/fluencelabs/crypto.svg?branch=master)](https://travis-ci.org/fluencelabs/crypto) [![Build Status](https://travis-ci.org/fluencelabs/crypto.svg?branch=master)](https://travis-ci.org/fluencelabs/crypto)
[![Gitter](https://badges.gitter.im/fluencelabs/crypto.svg)](https://gitter.im/fluencelabs/crypto?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
# Сrypto # Сrypto
@ -45,7 +44,7 @@ Simplified JWT implementation, meaning a JSON-serialized header and claim with s
// Bintray repo is used so far. Migration to Maven Central is planned // Bintray repo is used so far. Migration to Maven Central is planned
resolvers += Resolver.bintrayRepo("fluencelabs", "releases") resolvers += Resolver.bintrayRepo("fluencelabs", "releases")
val cryptoV = "0.0.1" val cryptoV = "0.0.3"
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"one.fluence" %%% "crypto-core" % cryptoV, // basic types and APIs "one.fluence" %%% "crypto-core" % cryptoV, // basic types and APIs

View File

@ -1,6 +1,6 @@
import de.heikoseeberger.sbtheader.License import de.heikoseeberger.sbtheader.License
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
import sbtcrossproject.crossProject import sbtcrossproject.CrossPlugin.autoImport.crossProject
name := "crypto" name := "crypto"
@ -10,11 +10,11 @@ javaOptions in Test ++= Seq("-ea")
skip in publish := true // Skip root project skip in publish := true // Skip root project
val scalaV = scalaVersion := "2.12.5" val scalaV = scalaVersion := "2.12.8"
val commons = Seq( val commons = Seq(
scalaV, scalaV,
version := "0.0.2", version := "0.0.6",
fork in Test := true, fork in Test := true,
parallelExecution in Test := false, parallelExecution in Test := false,
organization := "one.fluence", organization := "one.fluence",
@ -31,15 +31,15 @@ val commons = Seq(
commons commons
val CodecV = "0.0.3" val CodecV = "0.0.5"
val CatsEffectV = "1.0.0-RC3" val CatsEffectV = "1.2.0"
val SloggingV = "0.6.1" val SloggingV = "0.6.1"
val ScalatestV = "3.0.+" val ScalatestV = "3.0.+"
val bouncyCastle = "org.bouncycastle" % "bcprov-jdk15on" % "1.59" val bouncyCastle = "org.bouncycastle" % "bcprov-jdk15on" % "1.61"
enablePlugins(AutomateHeaderPlugin) enablePlugins(AutomateHeaderPlugin)
@ -93,7 +93,6 @@ lazy val `crypto-hashsign` = crossProject(JVMPlatform, JSPlatform)
.settings( .settings(
commons, commons,
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"biz.enef" %%% "slogging" % SloggingV,
"org.scalatest" %%% "scalatest" % ScalatestV % Test "org.scalatest" %%% "scalatest" % ScalatestV % Test
) )
) )
@ -105,7 +104,7 @@ lazy val `crypto-hashsign` = crossProject(JVMPlatform, JSPlatform)
) )
.jsSettings( .jsSettings(
npmDependencies in Compile ++= Seq( npmDependencies in Compile ++= Seq(
"elliptic" -> "6.4.0" "elliptic" -> "6.4.1"
), ),
scalaJSModuleKind in Test := ModuleKind.CommonJSModule, scalaJSModuleKind in Test := ModuleKind.CommonJSModule,
//all JavaScript dependencies will be concatenated to a single file *-jsdeps.js //all JavaScript dependencies will be concatenated to a single file *-jsdeps.js
@ -126,6 +125,7 @@ lazy val `crypto-cipher` = crossProject(JVMPlatform, JSPlatform)
.settings( .settings(
commons, commons,
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"biz.enef" %%% "slogging" % SloggingV % Test,
"org.scalatest" %%% "scalatest" % ScalatestV % Test "org.scalatest" %%% "scalatest" % ScalatestV % Test
) )
) )

View File

@ -127,7 +127,7 @@ class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) {
} }
} }
object AesCrypt extends slogging.LazyLogging { object AesCrypt {
def build(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[Array[Byte]] = { def build(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[Array[Byte]] = {
val aes = new AesCrypt(password.toHex.toCharArray, withIV, config) val aes = new AesCrypt(password.toHex.toCharArray, withIV, config)

View File

@ -220,7 +220,7 @@ class AesCrypt(password: Array[Char], withIV: Boolean, config: AesConfig) extend
} }
} }
object AesCrypt extends slogging.LazyLogging { object AesCrypt {
def build(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[Array[Byte]] = { def build(password: ByteVector, withIV: Boolean, config: AesConfig): Crypto.Cipher[Array[Byte]] = {
val aes = new AesCrypt(password.toHex.toCharArray, withIV, config) val aes = new AesCrypt(password.toHex.toCharArray, withIV, config)

View File

@ -15,13 +15,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package flyence.crypto package fluence.crypto
import cats.data.EitherT import cats.data.EitherT
import cats.instances.try_._ import cats.instances.try_._
import fluence.crypto.ecdsa.Ecdsa import fluence.crypto.ecdsa.Ecdsa
import fluence.crypto.signature.Signature import fluence.crypto.signature.Signature
import fluence.crypto.{CryptoError, KeyPair}
import org.scalatest.{Matchers, WordSpec} import org.scalatest.{Matchers, WordSpec}
import scodec.bits.ByteVector import scodec.bits.ByteVector

View File

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

View File

@ -62,14 +62,20 @@ class Ecdsa(curveType: String, scheme: String, hasher: Option[Crypto.Hasher[Arra
_ nonFatalHandling { _ nonFatalHandling {
g.initialize(ecSpec, input.map(new SecureRandom(_)).getOrElse(new SecureRandom())) g.initialize(ecSpec, input.map(new SecureRandom(_)).getOrElse(new SecureRandom()))
}(s"Could not initialize KeyPairGenerator") }(s"Could not initialize KeyPairGenerator")
p EitherT.fromOption(Option(g.generateKeyPair()), CryptoError("Could not generate KeyPair. Unexpected.")) p EitherT.fromOption(Option(g.generateKeyPair()), CryptoError("Generated key pair is null"))
keyPair nonFatalHandling { keyPair nonFatalHandling {
//store S number for private key and compressed Q point on curve for public key val pk = p.getPublic match {
val pk = ByteVector(p.getPublic.asInstanceOf[ECPublicKey].getQ.getEncoded(true)) case pk: ECPublicKey => ByteVector(p.getPublic.asInstanceOf[ECPublicKey].getQ.getEncoded(true))
case p => throw new ClassCastException(s"Cannot cast public key (${p.getClass}) to Ed25519PublicKeyParameters")
}
val sk = p.getPrivate match {
case sk: ECPrivateKey =>
val bg = p.getPrivate.asInstanceOf[ECPrivateKey].getS val bg = p.getPrivate.asInstanceOf[ECPrivateKey].getS
val sk = ByteVector.fromValidHex(bg.toString(HEXradix)) ByteVector.fromValidHex(bg.toString(HEXradix))
case s => throw new ClassCastException(s"Cannot cast private key (${p.getClass}) to Ed25519PrivateKeyParameters")
}
KeyPair.fromByteVectors(pk, sk) KeyPair.fromByteVectors(pk, sk)
}("Could not generate KeyPair. Unexpected.") }("Could not generate KeyPair")
} yield keyPair } yield keyPair
} }

View File

@ -0,0 +1,170 @@
/*
* 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.eddsa
import java.security._
import cats.Monad
import cats.data.EitherT
import fluence.crypto.KeyPair.Secret
import fluence.crypto.signature.{SignAlgo, SignatureChecker, Signer}
import fluence.crypto.{KeyPair, _}
import org.bouncycastle.crypto.KeyGenerationParameters
import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator
import org.bouncycastle.crypto.params.{Ed25519PrivateKeyParameters, Ed25519PublicKeyParameters}
import org.bouncycastle.crypto.signers.Ed25519Signer
import scodec.bits.ByteVector
import scala.language.higherKinds
/**
* Edwards-curve Digital Signature Algorithm (EdDSA)
*/
class Ed25519(strength: Int) extends JavaAlgorithm {
import CryptoError.nonFatalHandling
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 {
g getKeyPairGenerator
random = input.map(new SecureRandom(_)).getOrElse(new SecureRandom())
keyParameters = new KeyGenerationParameters(random, strength)
_ = g.init(keyParameters)
p EitherT.fromOption(Option(g.generateKeyPair()), CryptoError("Generated key pair is null"))
keyPair nonFatalHandling {
val pk = p.getPublic match {
case pk: Ed25519PublicKeyParameters => pk.getEncoded
case p => throw new ClassCastException(s"Cannot cast public key (${p.getClass}) to Ed25519PublicKeyParameters")
}
val sk = p.getPrivate match {
case sk: Ed25519PrivateKeyParameters => sk.getEncoded
case s => throw new ClassCastException(s"Cannot cast private key (${p.getClass}) to Ed25519PrivateKeyParameters")
}
KeyPair.fromBytes(pk, sk)
}("Could not generate KeyPair")
} yield keyPair
}
/**
* Restores pair of keys from the known secret key.
* The public key will be the same each method call with the same secret key.
* @param sk secret key
* @return key pair
*/
def restorePairFromSecret[F[_]: Monad](sk: Secret): EitherT[F, CryptoError, KeyPair] =
for {
keyPair nonFatalHandling {
val secret = new Ed25519PrivateKeyParameters(sk.bytes, 0)
KeyPair.fromBytes(secret.generatePublicKey().getEncoded, sk.bytes)
}("Could not generate KeyPair from private key")
} yield keyPair
def sign[F[_]: Monad](
keyPair: KeyPair,
message: ByteVector
): EitherT[F, CryptoError, signature.Signature] =
signMessage(keyPair.secretKey.bytes, message.toArray)
.map(bb fluence.crypto.signature.Signature(ByteVector(bb)))
def verify[F[_]: Monad](
publicKey: KeyPair.Public,
signature: fluence.crypto.signature.Signature,
message: ByteVector
): EitherT[F, CryptoError, Unit] =
verifySign(publicKey.bytes, signature.bytes, message.toArray)
private def signMessage[F[_]: Monad](
privateKey: Array[Byte],
message: Array[Byte]
): EitherT[F, CryptoError, Array[Byte]] =
for {
sign nonFatalHandling {
val privKey = new Ed25519PrivateKeyParameters(privateKey, 0)
val signer = new Ed25519Signer
signer.init(true, privKey)
signer.update(message, 0, message.length)
signer.generateSignature()
}("Cannot sign message")
} yield sign
private def verifySign[F[_]: Monad](
publicKey: Array[Byte],
signature: Array[Byte],
message: Array[Byte],
): EitherT[F, CryptoError, Unit] =
for {
verify nonFatalHandling {
val pubKey = new Ed25519PublicKeyParameters(publicKey, 0)
val signer = new Ed25519Signer
signer.init(false, pubKey)
signer.update(message, 0, message.length)
signer.verifySignature(signature)
}("Cannot verify message")
_ EitherT.cond[F](verify, (), CryptoError("Signature is not verified"))
} yield ()
private def getKeyPairGenerator[F[_]: Monad] =
nonFatalHandling {
new Ed25519KeyPairGenerator()
}(
"Cannot get key pair generator"
)
}
object Ed25519 {
/**
* Keys in tendermint are generating with a random seed of 32 bytes
*/
val tendermintEd25519 = new Ed25519(256)
val tendermintAlgo: SignAlgo = signAlgo(256)
def ed25519(strength: Int) = new Ed25519(strength)
def signAlgo(strength: Int): SignAlgo = {
val algo = ed25519(strength)
SignAlgo(
name = "ed25519",
generateKeyPair = algo.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] =
algo.sign(kp, input)
}
),
checker = pk
new SignatureChecker {
override def check[F[_]: Monad](
signature: fluence.crypto.signature.Signature,
plain: ByteVector
): EitherT[F, CryptoError, Unit] =
algo.verify(pk, signature, plain)
}
)
}
}

View File

@ -18,7 +18,6 @@
package fluence.crypto package fluence.crypto
import java.io.File import java.io.File
import java.math.BigInteger
import cats.data.EitherT import cats.data.EitherT
import cats.instances.try_._ import cats.instances.try_._
@ -30,7 +29,7 @@ import scodec.bits.ByteVector
import scala.util.{Random, Try} import scala.util.{Random, Try}
class SignatureSpec extends WordSpec with Matchers { class EcdsaSpec extends WordSpec with Matchers {
def rndBytes(size: Int): Array[Byte] = Random.nextString(10).getBytes def rndBytes(size: Int): Array[Byte] = Random.nextString(10).getBytes

View File

@ -0,0 +1,174 @@
/*
* 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.io.File
import cats.data.EitherT
import cats.instances.try_._
import fluence.crypto.eddsa.Ed25519
import fluence.crypto.keystore.FileKeyStorage
import fluence.crypto.signature.Signature
import org.scalatest.{Matchers, WordSpec}
import scodec.bits.ByteVector
import scala.util.{Random, Try}
class Ed25519Spec extends WordSpec with Matchers {
def rndBytes(size: Int): Array[Byte] = Random.nextString(10).getBytes
def rndByteVector(size: Int) = ByteVector(rndBytes(size))
private implicit class TryEitherTExtractor[A <: Throwable, B](et: EitherT[Try, A, B]) {
def extract: B =
et.value.map {
case Left(e) fail(e) // for making test fail message more describable
case Right(v) v
}.get
def isOk: Boolean = et.value.fold(_ false, _.isRight)
}
"ed25519 algorithm" should {
"correct sign and verify data" in {
val algorithm = Ed25519.ed25519(32)
val keys = algorithm.generateKeyPair.unsafe(None)
val pubKey = keys.publicKey
val data = rndByteVector(10)
val sign = algorithm.sign[Try](keys, data).extract
algorithm.verify[Try](pubKey, sign, data).isOk shouldBe true
val randomData = rndByteVector(10)
val randomSign = algorithm.sign(keys, randomData).extract
algorithm.verify(pubKey, randomSign, data).isOk shouldBe false
algorithm.verify(pubKey, sign, randomData).isOk shouldBe false
}
"correctly work with signer and checker" in {
val algo = Ed25519.signAlgo(32)
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
checker.check(sign, data).isOk shouldBe true
val randomSign = signer.sign(rndByteVector(10)).extract
checker.check(randomSign, data).isOk shouldBe false
}
"throw an errors on invalid data" in {
val algo = Ed25519.signAlgo(32)
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[CryptoError] thrownBy {
checker.check(Signature(rndByteVector(10)), data).value.flatMap(_.toTry).get
}
val invalidChecker = algo.checker(KeyPair.fromByteVectors(rndByteVector(10), rndByteVector(10)).publicKey)
the[CryptoError] thrownBy {
invalidChecker
.check(sign, data)
.value
.flatMap(_.toTry)
.get
}
}
"store and read key from file" in {
val algo = Ed25519.signAlgo(32)
val keys = algo.generateKeyPair.unsafe(None)
val keyFile = File.createTempFile("test", "")
if (keyFile.exists()) keyFile.delete()
val storage = new FileKeyStorage(keyFile)
storage.storeKeyPair(keys).unsafeRunSync()
val keysReadE = storage.readKeyPair
val keysRead = keysReadE.unsafeRunSync()
val signer = algo.signer(keys)
val data = rndByteVector(10)
val sign = signer.sign(data).extract
algo.checker(keys.publicKey).check(sign, data).isOk shouldBe true
algo.checker(keysRead.publicKey).check(sign, data).isOk shouldBe true
//try to store key into previously created file
storage.storeKeyPair(keys).attempt.unsafeRunSync().isLeft shouldBe true
}
"restore key pair from secret key" in {
val algo = Ed25519.signAlgo(32)
val testKeys = algo.generateKeyPair.unsafe(None)
val ed25519 = Ed25519.ed25519(32)
val newKeys = ed25519.restorePairFromSecret(testKeys.secretKey).extract
testKeys shouldBe newKeys
}
"work with tendermint keys" in {
/*
{
"address": "C08269A8AACD53C3488F16F285821DAC77CF5DEF",
"pub_key": {
"type": "tendermint/PubKeyEd25519",
"value": "FWB5lXZ/TT2132+jXp/8aQzNwISwp9uuFz4z0TXDdxY="
},
"priv_key": {
"type": "tendermint/PrivKeyEd25519",
"value": "P6jw9q/Rytdxpv5Wxs1aYA8w82uS0x3CpmS9+GpaMGIVYHmVdn9NPbXfb6Nen/xpDM3AhLCn264XPjPRNcN3Fg=="
}
}
*/
val privKeyBase64 = "P6jw9q/Rytdxpv5Wxs1aYA8w82uS0x3CpmS9+GpaMGIVYHmVdn9NPbXfb6Nen/xpDM3AhLCn264XPjPRNcN3Fg=="
val pubKeyBase64 = "FWB5lXZ/TT2132+jXp/8aQzNwISwp9uuFz4z0TXDdxY="
val privKey = ByteVector.fromBase64Descriptive(privKeyBase64).right.get
val pubKey = ByteVector.fromBase64Descriptive(pubKeyBase64).right.get
val restored = Ed25519.tendermintEd25519
.restorePairFromSecret[Try](KeyPair.Secret(privKey.dropRight(32)))
.value
.get
.right
.get
.publicKey
.bytes
restored shouldBe pubKey.toArray
restored shouldBe privKey.drop(32).toArray
}
}
}

View File

@ -88,7 +88,7 @@ object CryptoJwt {
private val alphabet = Bases.Alphabets.Base64Url private val alphabet = Bases.Alphabets.Base64Url
private val strVec = BitsCodecs.base64AlphabetToVector(alphabet).swap private val strVec = BitsCodecs.Base64.alphabetToVector(alphabet).swap
val signatureCodec: PureCodec[Signature, String] = val signatureCodec: PureCodec[Signature, String] =
PureCodec.liftB[Signature, ByteVector](_.sign, Signature(_)) andThen strVec PureCodec.liftB[Signature, ByteVector](_.sign, Signature(_)) andThen strVec

View File

@ -69,7 +69,7 @@ object KeyStore {
) )
// ByteVector to/from String, with the chosen alphabet // ByteVector to/from String, with the chosen alphabet
private val vecToStr = BitsCodecs.base64AlphabetToVector(alphabet).swap private val vecToStr = BitsCodecs.Base64.alphabetToVector(alphabet).swap
implicit val keyPairJsonCodec: PureCodec[KeyPair, Json] = implicit val keyPairJsonCodec: PureCodec[KeyPair, Json] =
PureCodec.liftB[KeyPair, (ByteVector, ByteVector)]( PureCodec.liftB[KeyPair, (ByteVector, ByteVector)](

View File

@ -1 +1 @@
sbt.version = 1.2.1 sbt.version = 1.2.8

View File

@ -1,15 +1,15 @@
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.4.0") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.2") addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.20")
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "4.1.0") addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.2.0")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.22") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.27")
addSbtPlugin("org.portable-scala" % "sbt-crossproject" % "0.3.1") addSbtPlugin("org.portable-scala" % "sbt-crossproject" % "0.6.0")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.3.1") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.6.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.10.0") addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.14.0")
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.0") addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2")
addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.4") addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.4")