From ea2469e768b46f44a70cdd6f9775d866996887ab Mon Sep 17 00:00:00 2001 From: Richard Searle Date: Sun, 7 Sep 2014 18:12:31 -0500 Subject: [PATCH] generalize SignatureCodec to support checksums and Digests add Digest and checksums from Java API add Fletcher16 checksum --- .../scala/scodec/codecs/ChecksumCodec.scala | 86 +++++++++++++++++++ .../scala/scodec/codecs/SignatureCodec.scala | 65 +++++++++----- src/main/scala/scodec/codecs/package.scala | 14 ++- .../scodec/codecs/FletcherChecksumTest.scala | 55 ++++++++++++ .../scodec/codecs/SignatureCodecTest.scala | 33 ++++++- 5 files changed, 224 insertions(+), 29 deletions(-) create mode 100644 src/main/scala/scodec/codecs/ChecksumCodec.scala create mode 100644 src/test/scala/scodec/codecs/FletcherChecksumTest.scala diff --git a/src/main/scala/scodec/codecs/ChecksumCodec.scala b/src/main/scala/scodec/codecs/ChecksumCodec.scala new file mode 100644 index 00000000..5c6d497b --- /dev/null +++ b/src/main/scala/scodec/codecs/ChecksumCodec.scala @@ -0,0 +1,86 @@ +package scodec +package codecs + +import java.security.MessageDigest +import java.util.Arrays +import java.util.zip.{ CRC32, Adler32, Checksum } +import scodec.bits.ByteVector + +/** + * Create "checksum" implementations for [[SignerFactory]] + * @group checksum + */ +object ChecksumFactory { + /** + * Creates a `java.security.Digest` factory for the specified algorithm + * + */ + def digest(algorithm: String): SignerFactory = + new DigestFactory(algorithm) + + /** + * Fletcher-16 checksum + */ + val fletcher16: SignerFactory = new ChecksumFactory { + def newSigner = new Fletcher16Checksum + } + + /** + * CRC-32 checksum + */ + val crc32: SignerFactory = new ChecksumFactory { + def newSigner = new ZipChecksumSigner(new CRC32()) + } + + /** + * Adler-32 checksum + */ + val adler32: SignerFactory = new ChecksumFactory { + def newSigner = new ZipChecksumSigner(new Adler32()) + } + + /** + * `java.security.Digest` implementation of Signer + */ + private class DigestSigner(impl: MessageDigest) extends Signer { + def update(data: Array[Byte]): Unit = impl.update(data) + def sign: Array[Byte] = impl.digest + def verify(signature: Array[Byte]): Boolean = MessageDigest.isEqual(impl.digest(), signature) + } + + /** + * A checksum does not have a distinct verify implementation + */ + private trait ChecksumFactory extends SignerFactory { + def newVerifier: Signer = newSigner + } + + private class DigestFactory(val algorithm: String) extends ChecksumFactory { + def newSigner: Signer = new DigestSigner(MessageDigest.getInstance(algorithm)) + } + + /** + * http://en.wikipedia.org/wiki/Fletcher's_checksum + */ + private class Fletcher16Checksum extends Signer { + var checksum = (0, 0) + def update(data: Array[Byte]): Unit = { + checksum = data.foldLeft(checksum) { (p, b) => + val lsb = (p._2 + (0xff & b)) % 255 + ((p._1 + lsb) % 255, lsb) + } + } + def sign: Array[Byte] = Array(checksum._1.asInstanceOf[Byte], checksum._2.asInstanceOf[Byte]) + def verify(signature: Array[Byte]): Boolean = Arrays.equals(sign, signature) + } + + /** + * `java.util.zip.Checksum` implementation of Signer + */ + private class ZipChecksumSigner(impl: Checksum) extends Signer { + def update(data: Array[Byte]): Unit = impl.update(data, 0, data.length) + def sign: Array[Byte] = ByteVector.fromLong(impl.getValue()).drop(4).toArray + def verify(signature: Array[Byte]): Boolean = MessageDigest.isEqual(sign, signature) + } + +} \ No newline at end of file diff --git a/src/main/scala/scodec/codecs/SignatureCodec.scala b/src/main/scala/scodec/codecs/SignatureCodec.scala index fbd5477d..4cae5be9 100644 --- a/src/main/scala/scodec/codecs/SignatureCodec.scala +++ b/src/main/scala/scodec/codecs/SignatureCodec.scala @@ -9,50 +9,70 @@ import java.security.cert.Certificate import scodec.bits.{ BitVector, ByteVector } /** - * Represents the ability to create a `java.security.Signature` for use with [[fixedSizeSignature]] and [[variableSizeSignature]]. + * Represents the ability to create a "checksum" for use with [[fixedSizeSignature]] and [[variableSizeSignature]]. * @group crypto */ -trait SignatureFactory { +trait Signer { + def update(data:Array[Byte]):Unit + def sign:Array[Byte] + def verify(signature:Array[Byte]):Boolean +} - /** Creates a signature initialized for signing. */ - def newSigner: Signature +/** + * Signer implementation for `java.security.Signature` + * @group crypto + */ +class SignatureSigner(impl:Signature) extends Signer { + def update(data:Array[Byte]):Unit = impl.update(data) + def sign:Array[Byte] = impl.sign + def verify(signature:Array[Byte]):Boolean = impl.verify(signature) +} - /** Creates a signature initialized for verifying. */ - def newVerifier: Signature +/** + * Represents the ability to create a [[Signer]] for use with [[fixedSizeSignature]] and [[variableSizeSignature]]. + * @group crypto + */ +trait SignerFactory { + + /** Creates a [[Signer]] initialized for signing. */ + def newSigner: Signer + + /** Creates a [[Signer]] initialized for verifying. */ + def newVerifier: Signer } /** - * Companion for [[SignatureFactory]]. + * Create `java.security.Signature` implementations for [[SignerFactory]] * @group crypto */ object SignatureFactory { - + /** Creates a signature factory for the specified algorithm using the specified private and public keys. */ - def apply(algorithm: String, privateKey: PrivateKey, publicKey: PublicKey): SignatureFactory = + def apply(algorithm: String, privateKey: PrivateKey, publicKey: PublicKey): SignerFactory = new SimpleSignatureFactory(algorithm, privateKey, publicKey) /** Creates a signature factory for the specified algorithm using the specified key pair. */ - def apply(algorithm: String, keyPair: KeyPair): SignatureFactory = + def apply(algorithm: String, keyPair: KeyPair): SignerFactory = new SimpleSignatureFactory(algorithm, keyPair.getPrivate, keyPair.getPublic) /** Creates a signature factory for the specified algorithm using the specified key pair. */ - def apply(algorithm: String, privateKey: PrivateKey, certificate: Certificate): SignatureFactory = + def apply(algorithm: String, privateKey: PrivateKey, certificate: Certificate): SignerFactory = new SimpleSignatureFactory(algorithm, privateKey, certificate.getPublicKey) /** Creates a signature factory that only supports signing for the specified algorithm using the specified private key. */ - def signing(algorithm: String, privateKey: PrivateKey): SignatureFactory = - new SimpleSignatureFactorySigning(algorithm, privateKey) with SignatureFactory { + def signing(algorithm: String, privateKey: PrivateKey): SignerFactory = + new SimpleSignatureFactorySigning(algorithm, privateKey) with SignerFactory { def newVerifier = sys.error("Cannot verify with a signature factory that only supports signing.") } /** Creates a signature factory that only supports signing for the specified algorithm using the specified public key. */ - def verifying(algorithm: String, publicKey: PublicKey): SignatureFactory = - new SimpleSignatureFactoryVerifying(algorithm, publicKey) with SignatureFactory { + def verifying(algorithm: String, publicKey: PublicKey): SignerFactory = + new SimpleSignatureFactoryVerifying(algorithm, publicKey) with SignerFactory { def newSigner = sys.error("Cannot sign with a signature factory that only supports verifying.") } /** Creates a signature factory that only supports signing for the specified algorithm using the specified public key. */ - def verifying(algorithm: String, certificate: Certificate): SignatureFactory = + def verifying(algorithm: String, certificate: Certificate): SignerFactory = verifying(algorithm, certificate.getPublicKey) @@ -64,10 +84,10 @@ object SignatureFactory { private trait SignatureFactorySigning extends WithSignature { protected def privateKey: PrivateKey - def newSigner: Signature = { + def newSigner: Signer = { val sig = newSignature sig.initSign(privateKey) - sig + new SignatureSigner(sig) } } @@ -78,10 +98,10 @@ object SignatureFactory { private trait SignatureFactoryVerifying extends WithSignature { protected def publicKey: PublicKey - def newVerifier: Signature = { + def newVerifier: Signer = { val sig = newSignature sig.initVerify(publicKey) - sig + new SignatureSigner(sig) } } @@ -94,11 +114,12 @@ object SignatureFactory { protected val algorithm: String, protected val privateKey: PrivateKey, protected val publicKey: PublicKey - ) extends SignatureFactory with SignatureFactorySigning with SignatureFactoryVerifying + ) extends SignerFactory with SignatureFactorySigning with SignatureFactoryVerifying + } /** @see [[fixedSizeSignature]] and [[variableSizeSignature]] */ -private[codecs] final class SignatureCodec[A](codec: Codec[A], signatureCodec: Codec[BitVector])(implicit signatureFactory: SignatureFactory) extends Codec[A] { +private[codecs] final class SignatureCodec[A](codec: Codec[A], signatureCodec: Codec[BitVector])(implicit signatureFactory: SignerFactory) extends Codec[A] { import Codec._ override def encode(a: A) = for { diff --git a/src/main/scala/scodec/codecs/package.scala b/src/main/scala/scodec/codecs/package.scala index 7c5a41e3..fe6e8a71 100644 --- a/src/main/scala/scodec/codecs/package.scala +++ b/src/main/scala/scodec/codecs/package.scala @@ -86,11 +86,16 @@ import scodec.bits.{ BitVector, ByteOrdering, ByteVector } * Note: this design is heavily based on Scala's parser combinator library and the syntax it provides. * * - * === Cryptograhpy Codecs === + * === Cryptography Codecs === * * There are codecs that support working with encrypted data ([[encrypted]]) and digital signatures * ([[fixedSizeSignature]] and [[variableSizeSignature]]). Additionally, support for `java.security.cert.Certificate`s * is provided by [[certificate]] and [[x509Certificate]]. + * + * === Checksum Codecs === + * + * There are codecs that support working with checksums and digests, extending the + * [[fixedSizeSignature]] and [[variableSizeSignature]] functionality. * * * @groupname bits Bits and Bytes Codecs @@ -110,6 +115,9 @@ import scodec.bits.{ BitVector, ByteOrdering, ByteVector } * * @groupname crypto Cryptography * @groupprio crypto 4 + * + * @groupname checksum Checksums and Digests + * @groupprio checksum 5 */ package object codecs { @@ -826,7 +834,7 @@ package object codecs { * @param signatureFactory factory to use for signing/verifying * @group crypto */ - def fixedSizeSignature[A](size: Int)(codec: Codec[A])(implicit signatureFactory: SignatureFactory): Codec[A] = + def fixedSizeSignature[A](size: Int)(codec: Codec[A])(implicit signatureFactory: SignerFactory): Codec[A] = new SignatureCodec(codec, fixedSizeBytes(size, BitVectorCodec))(signatureFactory) /** @@ -840,7 +848,7 @@ package object codecs { * @param signatureFactory factory to use for signing/verifying * @group crypto */ - def variableSizeSignature[A](size: Codec[Int])(codec: Codec[A])(implicit signatureFactory: SignatureFactory): Codec[A] = + def variableSizeSignature[A](size: Codec[Int])(codec: Codec[A])(implicit signatureFactory: SignerFactory): Codec[A] = new SignatureCodec(codec, variableSizeBytes(size, BitVectorCodec))(signatureFactory) /** diff --git a/src/test/scala/scodec/codecs/FletcherChecksumTest.scala b/src/test/scala/scodec/codecs/FletcherChecksumTest.scala new file mode 100644 index 00000000..c2d17d26 --- /dev/null +++ b/src/test/scala/scodec/codecs/FletcherChecksumTest.scala @@ -0,0 +1,55 @@ +package scodec.codecs + +import org.scalacheck._ +import org.scalatest.WordSpec +import org.scalatest.prop.GeneratorDrivenPropertyChecks +import scodec.bits.ByteVector + +/** + * Test the Fletcher checksum functionality + * + */ +class FletcherChecksumTest extends WordSpec with GeneratorDrivenPropertyChecks { + + "fletcher16 checksum" should { + /** + * http://en.wikipedia.org/wiki/Fletcher's_checksum + * + * "Example calculation of the Fletcher-16 checksum" + * + */ + "wikipedia" in { + val signer = ChecksumFactory.fletcher16.newSigner + signer.update(Array(0x01, 0x02)) + assert(signer.verify(Array(0x04, 0x03))) + } + + "0xAA * (3N+2) => Array(0x0,0x55)" in { + forAll(Gen.posNum[Int]) { (n: Int) => + pattern(n, 2, + Array(0x0, 0x55)) + } + } + + "0xAA * (3N + 1) => Array(0xAA, 0xAA)" in { + forAll(Gen.posNum[Int]) { (n: Int) => + pattern(n, 1, + Array(0xAA.asInstanceOf[Byte], 0xAA.asInstanceOf[Byte])) + } + } + + "0xAA * (3N) => Array(0x0, 0x0)" in { + forAll(Gen.posNum[Int]) { (n: Int) => + pattern(n, 0, + Array(0x0, 0x0)) + } + } + } + + private def pattern(n: Int, delta: Int, expected: Array[Byte]): Unit = { + val signer = ChecksumFactory.fletcher16.newSigner + signer.update(ByteVector.fill(3 * n + delta)(0xAA).toArray) + assert(signer.verify(expected)) + } + +} \ No newline at end of file diff --git a/src/test/scala/scodec/codecs/SignatureCodecTest.scala b/src/test/scala/scodec/codecs/SignatureCodecTest.scala index 9c1642ad..f95ef404 100644 --- a/src/test/scala/scodec/codecs/SignatureCodecTest.scala +++ b/src/test/scala/scodec/codecs/SignatureCodecTest.scala @@ -2,6 +2,7 @@ package scodec package codecs import java.security.KeyPairGenerator +import java.util.Arrays class SignatureCodecTest extends CodecSuite { @@ -13,7 +14,23 @@ class SignatureCodecTest extends CodecSuite { "fixedSizeSignature codec" should { "roundtrip using SHA256withRSA" in { - testFixedSizeSignature(SignatureFactory("SHA256withRSA", keyPair)) + testFixedSizeSignature(1024/8)(SignatureFactory("SHA256withRSA", keyPair)) + } + + "roundtrip using MD5" in { + testFixedSizeSignature(16)(ChecksumFactory.digest("MD5")) + } + + "roundtrip using Fletcher" in { + testFixedSizeSignature(2)(ChecksumFactory.fletcher16) + } + + "roundtrip using crc32" in { + testFixedSizeSignature(4)(ChecksumFactory.crc32) + } + + "roundtrip using adler32" in { + testFixedSizeSignature(4)(ChecksumFactory.adler32) } } @@ -22,14 +39,22 @@ class SignatureCodecTest extends CodecSuite { testVariableSizeSignature(SignatureFactory("SHA256withRSA", keyPair)) } } + + "variableSizeSignature codec" should { + "roundtrip using MD5" in { + testVariableSizeSignature(ChecksumFactory.digest("MD5")) + } + } - protected def testFixedSizeSignature(implicit sf: SignatureFactory) { - val codec = fixedSizeSignature(1024/8) { int32 ~ variableSizeBytes(int32, utf8) } + protected def testFixedSizeSignature(size:Int)(implicit sf: SignerFactory) { + val codec = fixedSizeSignature(size) { int32 ~ variableSizeBytes(int32, utf8) } forAll { (n: Int, s: String, x: Int) => roundtrip(codec, n ~ s) } } - protected def testVariableSizeSignature(implicit sf: SignatureFactory) { + protected def testVariableSizeSignature(implicit sf: SignerFactory) { val codec = variableSizeSignature(uint16) { int32 ~ variableSizeBytes(int32, utf8) } ~ int32 forAll { (n: Int, s: String, x: Int) => roundtrip(codec, n ~ s ~ x) } } + + }