From 47888049de29336c3f753d0372b37923d8c71121 Mon Sep 17 00:00:00 2001 From: iximeow Date: Fri, 28 Nov 2014 04:22:20 -0800 Subject: Challenge 11 --- src/utils/CryptoUtils.scala | 22 +++++++++++++++ src/utils/crypto/CBCCipher.scala | 50 ---------------------------------- src/utils/crypto/CbcCipher.scala | 50 ++++++++++++++++++++++++++++++++++ src/utils/crypto/CipherGenerator.scala | 14 ++++++++-- src/utils/crypto/EcbCipher.scala | 49 +++++++++++++++++++++++++++++++++ test/Gloria.scala | 33 ++++++++++++++++++++++ test/Set2Spec.scala | 11 ++++++++ 7 files changed, 176 insertions(+), 53 deletions(-) delete mode 100644 src/utils/crypto/CBCCipher.scala create mode 100644 src/utils/crypto/CbcCipher.scala create mode 100644 src/utils/crypto/EcbCipher.scala create mode 100644 test/Gloria.scala diff --git a/src/utils/CryptoUtils.scala b/src/utils/CryptoUtils.scala index 54e5034..7a31002 100644 --- a/src/utils/CryptoUtils.scala +++ b/src/utils/CryptoUtils.scala @@ -1,6 +1,8 @@ package ixee.cryptopals.utils import ixee.cryptopals.utils.crypto._ +import ixee.cryptopals.utils.StreamUtils._ +import ixee.cryptopals.utils.FunctionUtils._ import javax.crypto.Cipher import javax.crypto.spec.SecretKeySpec @@ -19,4 +21,24 @@ object CryptoUtils { def cbcDecrypt(builder: CbcBuilder)(data: Seq[Byte]) = stripPkcs7Pad(builder.decrypt.end(data)) + + def ecbEncrypt(builder: EcbBuilder)(data: Seq[Byte]) = + builder.encrypt.end(data) + + def ecbDecrypt(builder: EcbBuilder)(data: Seq[Byte]) = + stripPkcs7Pad(builder.decrypt.end(data)) + + def detectMode(xs: Seq[Byte]): String = { + def dupBlocks(xs: Seq[Seq[Byte]]) = + pairsOf(xs).map(tup(_ == _)).count(_ == true) + + def countDupBlocks(xs: Seq[Byte]): Int = + dupBlocks(xs.grouped(16).toSeq.init.toStream) + + //.... well very probably. + if (countDupBlocks(xs) > 0) "ECB" + else "CBC" + + } + } diff --git a/src/utils/crypto/CBCCipher.scala b/src/utils/crypto/CBCCipher.scala deleted file mode 100644 index 227b635..0000000 --- a/src/utils/crypto/CBCCipher.scala +++ /dev/null @@ -1,50 +0,0 @@ -package ixee.cryptopals.utils.crypto - -import javax.crypto.Cipher -import ixee.cryptopals.utils.ConversionUtils._ -import ixee.cryptopals.utils.FunctionUtils._ -import ixee.cryptopals.utils.CryptoUtils._ -import ixee.cryptopals.utils.TupleUtils._ -import ixee.cryptopals.utils.ByteUtils._ - -class CBCCipher(private[this] val cipher: Cipher, private[this] val iv: Seq[Byte], mode: Int) extends IxeeCipher { - val blockSize = cipher.getBlockSize - - var state: Seq[Byte] = iv - var leftover: Seq[Byte] = Seq() - - lazy val handleBlock: Seq[Byte] => Seq[Byte] = - if (mode == Cipher.ENCRYPT_MODE) encBlock _ - else decBlock _ - - def update(data: Seq[Byte]): Seq[Byte] = { - val blocks = blockized(leftover ++ data).tap(updateLeftover)._1 - blocks.foldLeft(Seq[Byte]())(_ ++ handleBlock(_)) - } - - private def decBlock(data: Seq[Byte]): Seq[Byte] = - (cipher.update(data.toArray).toSeq xor state).tap(_ => state = data) - - private def encBlock(data: Seq[Byte]): Seq[Byte] = - cipher.update((data xor state).toArray).tap(state = _) - - // wouldn't hurt to invalidate this object afterward, but meh - // TODO: strip padding! - // to do it right really requires writing decryption as its own part - // it's already obvious that's necessary, but to do padding stripping - // properly, the last block must be withheld until an end() call is made - // which is much different stateful behavior from encryption. - // - // in cryptoUtils for now. - def end(): Seq[Byte] = - if (mode == Cipher.DECRYPT_MODE) Seq() - else cipher.update((pkcs7pad(leftover, blockSize) xor state).toArray) - - def blockized(data: Seq[Byte]): (Seq[Seq[Byte]], Seq[Byte]) = - groupBlocks <-: data.splitAt(data.length - (data.length % blockSize)) - - def groupBlocks: Seq[Byte] => Seq[Seq[Byte]] = _.grouped(blockSize).toSeq - - def updateLeftover(pair: (Seq[Seq[Byte]], Seq[Byte])) = - leftover = pair._2 -} diff --git a/src/utils/crypto/CbcCipher.scala b/src/utils/crypto/CbcCipher.scala new file mode 100644 index 0000000..28a88cd --- /dev/null +++ b/src/utils/crypto/CbcCipher.scala @@ -0,0 +1,50 @@ +package ixee.cryptopals.utils.crypto + +import javax.crypto.Cipher +import ixee.cryptopals.utils.ConversionUtils._ +import ixee.cryptopals.utils.FunctionUtils._ +import ixee.cryptopals.utils.CryptoUtils._ +import ixee.cryptopals.utils.TupleUtils._ +import ixee.cryptopals.utils.ByteUtils._ + +class CbcCipher(private[this] val cipher: Cipher, private[this] val iv: Seq[Byte], mode: Int) extends IxeeCipher { + val blockSize = cipher.getBlockSize + + var state: Seq[Byte] = iv + var leftover: Seq[Byte] = Seq() + + lazy val handleBlock: Seq[Byte] => Seq[Byte] = + if (mode == Cipher.ENCRYPT_MODE) encBlock _ + else decBlock _ + + def update(data: Seq[Byte]): Seq[Byte] = { + val blocks = blockized(leftover ++ data).tap(updateLeftover)._1 + blocks.foldLeft(Seq[Byte]())(_ ++ handleBlock(_)) + } + + private def decBlock(data: Seq[Byte]): Seq[Byte] = + (cipher.update(data.toArray).toSeq xor state).tap(_ => state = data) + + private def encBlock(data: Seq[Byte]): Seq[Byte] = + cipher.update((data xor state).toArray).tap(state = _) + + // wouldn't hurt to invalidate this object afterward, but meh + // TODO: strip padding! + // to do it right really requires writing decryption as its own part + // it's already obvious that's necessary, but to do padding stripping + // properly, the last block must be withheld until an end() call is made + // which is much different stateful behavior from encryption. + // + // in cryptoUtils for now. + def end(): Seq[Byte] = + if (mode == Cipher.DECRYPT_MODE) Seq() + else cipher.update((pkcs7pad(leftover, blockSize) xor state).toArray) + + def blockized(data: Seq[Byte]): (Seq[Seq[Byte]], Seq[Byte]) = + groupBlocks <-: data.splitAt(data.length - (data.length % blockSize)) + + def groupBlocks: Seq[Byte] => Seq[Seq[Byte]] = _.grouped(blockSize).toSeq + + def updateLeftover(pair: (Seq[Seq[Byte]], Seq[Byte])) = + leftover = pair._2 +} diff --git a/src/utils/crypto/CipherGenerator.scala b/src/utils/crypto/CipherGenerator.scala index e7651cb..391710b 100644 --- a/src/utils/crypto/CipherGenerator.scala +++ b/src/utils/crypto/CipherGenerator.scala @@ -9,8 +9,16 @@ sealed trait CipherGenerator { case class EcbBuilder(primitives: SchemeBuilder) extends CipherGenerator { // lol - def encrypt = ??? - def decrypt = ??? + def encrypt = + setup(Cipher.ENCRYPT_MODE) + def decrypt = + setup(Cipher.DECRYPT_MODE) + + private[this] def setup(cipherMode: Int) = { + val cipher = primitives.cipher + cipher.init(cipherMode, primitives.key) + new EcbCipher(cipher, cipherMode) + } } case class CbcBuilder(primitives: SchemeBuilder, iv: Seq[Byte]) extends CipherGenerator { @@ -22,6 +30,6 @@ case class CbcBuilder(primitives: SchemeBuilder, iv: Seq[Byte]) extends CipherGe private[this] def setup(cipherMode: Int) = { val cipher = primitives.cipher cipher.init(cipherMode, primitives.key) - new CBCCipher(cipher, iv, cipherMode) + new CbcCipher(cipher, iv, cipherMode) } } diff --git a/src/utils/crypto/EcbCipher.scala b/src/utils/crypto/EcbCipher.scala new file mode 100644 index 0000000..306d5fa --- /dev/null +++ b/src/utils/crypto/EcbCipher.scala @@ -0,0 +1,49 @@ + +package ixee.cryptopals.utils.crypto + +import javax.crypto.Cipher +import ixee.cryptopals.utils.ConversionUtils._ +import ixee.cryptopals.utils.FunctionUtils._ +import ixee.cryptopals.utils.CryptoUtils._ +import ixee.cryptopals.utils.TupleUtils._ +import ixee.cryptopals.utils.ByteUtils._ + +class EcbCipher(private[this] val cipher: Cipher, mode: Int) extends IxeeCipher { + val blockSize = cipher.getBlockSize + + var leftover: Seq[Byte] = Seq() + + lazy val handleBlock: Seq[Byte] => Seq[Byte] = + if (mode == Cipher.ENCRYPT_MODE) encBlock _ + else decBlock _ + + def update(data: Seq[Byte]): Seq[Byte] = { + val blocks = blockized(leftover ++ data).tap(updateLeftover)._1 + blocks.foldLeft(Seq[Byte]())(_ ++ handleBlock(_)) + } + + private def decBlock(data: Seq[Byte]): Seq[Byte] = + cipher.update(data.toArray).toSeq + + private def encBlock(data: Seq[Byte]): Seq[Byte] = + cipher.update(data.toArray).toSeq + // wouldn't hurt to invalidate this object afterward, but meh + // TODO: strip padding! + // to do it right really requires writing decryption as its own part + // it's already obvious that's necessary, but to do padding stripping + // properly, the last block must be withheld until an end() call is made + // which is much different stateful behavior from encryption. + // + // in cryptoUtils for now. + def end(): Seq[Byte] = + if (mode == Cipher.DECRYPT_MODE) Seq() + else cipher.update(pkcs7pad(leftover, blockSize).toArray) + + def blockized(data: Seq[Byte]): (Seq[Seq[Byte]], Seq[Byte]) = + groupBlocks <-: data.splitAt(data.length - (data.length % blockSize)) + + def groupBlocks: Seq[Byte] => Seq[Seq[Byte]] = _.grouped(blockSize).toSeq + + def updateLeftover(pair: (Seq[Seq[Byte]], Seq[Byte])) = + leftover = pair._2 +} diff --git a/test/Gloria.scala b/test/Gloria.scala new file mode 100644 index 0000000..3141a3a --- /dev/null +++ b/test/Gloria.scala @@ -0,0 +1,33 @@ +package ixee.cryptopals.test + +import ixee.cryptopals.utils._ +import CryptoUtils._ +import crypto.SchemeBuilder + +object Gloria { + + val decide = new java.util.Random() + + def encryptify(input: Seq[Byte]): (String, Seq[Byte]) = { + val mode = if (Gloria.decide.nextBoolean) "ECB" else "CBC" + + val betterText = + Gloria.say(atLeast = 5, atMost = 10) ++ input ++ Gloria.say(atLeast = 5, atMost = 10) + + val builder = SchemeBuilder("AES", Gloria.sayExactly(16)) + + val encryptor = + if (mode == "ECB") ecbEncrypt(builder.ecb) _ + else cbcEncrypt(builder.cbc(Gloria.sayExactly(16))) _ + + (mode, encryptor(betterText)) + } + + def voice: Iterator[Byte] = Stream.continually(Gloria.decide.nextInt.toByte).iterator + + def sayExactly(n: Int) = voice.take(n).toSeq + + def say(atLeast: Int, atMost: Int): Seq[Byte] = + (voice.take(atLeast) ++ voice.take(Gloria.decide.nextInt(atMost - atLeast))).toSeq + +} diff --git a/test/Set2Spec.scala b/test/Set2Spec.scala index d230a18..156949b 100644 --- a/test/Set2Spec.scala +++ b/test/Set2Spec.scala @@ -73,6 +73,17 @@ class Set2Spec extends IxeeSpec { } } + + "Challenge 11: ECB/CBC detection" - { + + val input = "keke" * 32 + + val (mode, ciphertext) = Gloria.encryptify(input.asBytes) + + CryptoUtils.detectMode(ciphertext) mustBe mode // ...always. + + } + } } -- cgit v1.1