summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoriximeow <me@iximeow.net>2014-11-26 00:21:44 -0800
committeriximeow <me@iximeow.net>2014-11-26 00:21:44 -0800
commit62f1fe9b3ec2a5c05c49acbf47356c28a9b73f61 (patch)
tree65401804686d069eed9d5ac8a2a885171ffac566
parent0a3371042ff2f79f99be3ed2ac50be37338bb53c (diff)
Challenge 6
-rw-r--r--data/6.txt64
-rw-r--r--src/solvers/Challenge4.scala2
-rw-r--r--src/solvers/Challenge6.scala110
-rw-r--r--src/solvers/XorDecrypt.scala79
-rw-r--r--test/StreamUtilsSpec.scala38
5 files changed, 285 insertions, 8 deletions
diff --git a/data/6.txt b/data/6.txt
new file mode 100644
index 0000000..cecdb81
--- /dev/null
+++ b/data/6.txt
@@ -0,0 +1,64 @@
+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
diff --git a/src/solvers/Challenge4.scala b/src/solvers/Challenge4.scala
index f71f6c0..ba771a8 100644
--- a/src/solvers/Challenge4.scala
+++ b/src/solvers/Challenge4.scala
@@ -14,7 +14,7 @@ object Challenge4 {
.zipWithIndex
.map { pair => (pair._1._1, pair._1._2, pair._2) }
.filter(_._1 != 0)
- .map { triplet => (XorDecrypt.decryptToAscii(triplet._2)(triplet._1), triplet._3) }
+ .map { triplet => (XorDecrypt.decryptToAsciiSingleByteKey(triplet._2)(triplet._1), triplet._3) }
.map { pair => (pair._1, TextScorer.score(pair._1.asBytes), pair._2) }
.sortBy(_._2)
.map { triplet => (triplet._1, triplet._3) }
diff --git a/src/solvers/Challenge6.scala b/src/solvers/Challenge6.scala
new file mode 100644
index 0000000..9580fbb
--- /dev/null
+++ b/src/solvers/Challenge6.scala
@@ -0,0 +1,110 @@
+package ixee.cryptopals.solvers
+
+import scala.io.Source
+import ixee.cryptopals.utils.ByteUtils._
+import ixee.cryptopals.utils.ConversionUtils._
+import ixee.cryptopals.utils.FunctionUtils._
+import ixee.cryptopals.utils._
+import io.github.marklister.base64.Base64._
+
+object Challenge6 {
+ val path = "./data/6.txt"
+
+ lazy val ciphertext =
+ Source
+ .fromFile(path)
+ .getLines()
+ .toSeq
+ .flatten
+ .mkString
+ .toByteArray
+
+ def run = {
+ XorDecrypt.crackForMultiByteKey(ciphertext)
+ }
+
+ def findKey: Seq[Byte] =
+ XorDecrypt.findBestMultiByteKey(ciphertext)
+
+ val expectation =
+ """|I'm back and I'm ringin' the bell
+ |A rockin' on the mike while the fly girls yell
+ |In ecstasy in the back of me
+ |Well that's my DJ Deshay cuttin' all them Z's
+ |Hittin' hard and the girlies goin' crazy
+ |Vanilla's on the mike, man I'm not lazy.
+ |
+ |I'm lettin' my drug kick in
+ |It controls my mouth and I begin
+ |To just let it flow, let my concepts go
+ |My posse's to the side yellin', Go Vanilla Go!
+ |
+ |Smooth 'cause that's the way I will be
+ |And if you don't give a damn, then
+ |Why you starin' at me
+ |So get off 'cause I control the stage
+ |There's no dissin' allowed
+ |I'm in my own phase
+ |The girlies sa y they love me and that is ok
+ |And I can dance better than any kid n' play
+ |
+ |Stage 2 -- Yea the one ya' wanna listen to
+ |It's off my head so let the beat play through
+ |So I can funk it up and make it sound good
+ |1-2-3 Yo -- Knock on some wood
+ |For good luck, I like my rhymes atrocious
+ |Supercalafragilisticexpialidocious
+ |I'm an effect and that you can bet
+ |I can take a fly girl and make her wet.
+ |
+ |I'm like Samson -- Samson to Delilah
+ |There's no denyin', You can try to hang
+ |But you'll keep tryin' to get my style
+ |Over and over, practice makes perfect
+ |But not if you're a loafer.
+ |
+ |You'll get nowhere, no place, no time, no girls
+ |Soon -- Oh my God, homebody, you probably eat
+ |Spaghetti with a spoon! Come on and say it!
+ |
+ |VIP. Vanilla Ice yep, yep, I'm comin' hard like a rhino
+ |Intoxicating so you stagger like a wino
+ |So punks stop trying and girl stop cryin'
+ |Vanilla Ice is sellin' and you people are buyin'
+ |'Cause why the freaks are jockin' like Crazy Glue
+ |Movin' and groovin' trying to sing along
+ |All through the ghetto groovin' this here song
+ |Now you're amazed by the VIP posse.
+ |
+ |Steppin' so hard like a German Nazi
+ |Startled by the bases hittin' ground
+ |There's no trippin' on mine, I'm just gettin' down
+ |Sparkamatic, I'm hangin' tight like a fanatic
+ |You trapped me once and I thought that
+ |You might have it
+ |So step down and lend me your ear
+ |'89 in my time! You, '90 is my year.
+ |
+ |You're weakenin' fast, YO! and I can tell it
+ |Your body's gettin' hot, so, so I can smell it
+ |So don't be mad and don't be sad
+ |'Cause the lyrics belong to ICE, You can call me Dad
+ |You're pitchin' a fit, so step back and endure
+ |Let the witch doctor, Ice, do the dance to cure
+ |So come up close and don't be square
+ |You wanna battle me -- Anytime, anywhere
+ |
+ |You thought that I was weak, Boy, you're dead wrong
+ |So come on, everybody and sing this song
+ |
+ |Say -- Play that funky music Say, go white boy, go white boy go
+ |play that funky music Go white boy, go white boy, go
+ |Lay down and boogie and play that funky music till you die.
+ |
+ |Play that funky music Come on, Come on, let me hear
+ |Play that funky music white boy you say it, say it
+ |Play that funky music A little louder now
+ |Play that funky music, white boy Come on, Come on, Come on
+ |Play that funky music
+ |""".stripMargin
+}
diff --git a/src/solvers/XorDecrypt.scala b/src/solvers/XorDecrypt.scala
index 6b82c34..83fa923 100644
--- a/src/solvers/XorDecrypt.scala
+++ b/src/solvers/XorDecrypt.scala
@@ -2,24 +2,89 @@ package ixee.cryptopals.solvers
import ixee.cryptopals.utils._
import ixee.cryptopals.utils.ByteUtils._
+import ixee.cryptopals.utils.StreamUtils._
import ixee.cryptopals.utils.FunctionUtils._
+import ixee.cryptopals.utils.ConversionUtils._
object XorDecrypt {
- implicit val freq = Frequencies.cornell40kSample
+ implicit val freq = Frequencies.cryptologicalMathematics //cornell40kSample
- def decryptToAscii(ciphertext: Seq[Byte])(key: Byte): String = {
- new String((ciphertext xor Stream.continually(key)).toArray)
+ def inferKeySizes(xs: Seq[Byte]): Seq[Int] = {
+ inferKeySizesWithHammingDistance(xs).map(_._2)
}
- def tryBestDecrypt(ciphertext: Seq[Byte]): String = {
- (findBestSingleByteKey _ :| decryptToAscii(ciphertext) _)(ciphertext)
+ def inferKeySize(xs: Seq[Byte]): Int =
+ inferKeySizes(xs).head
+
+ // not so great for small key sizes.
+ // TODO: Why exactly does this work?
+ def inferKeySizesWithHammingDistance(xs: Seq[Byte]): Seq[(Double, Int)] = {
+ val MinKeySize = 1
+ val MaxKeySize = 40
+
+ (MinKeySize to MaxKeySize)
+ .map(normalizedHammingDistanceForKeySize(xs, _))
+ .zipWithIndex
+ .map( { case (distance, keySize) => (distance, keySize + 1) } )
+ .sortBy(_._1)
+ }
+
+ def normalizedHammingDistanceForKeySize(xs: Seq[Byte], keySize: Int) = {
+ val grouped = xs.grouped(keySize).toArray.dropRight(1)
+ // ez hack to drop non-`keySize` subcomponents of xs
+ (grouped.init zip grouped.tail).map(tup(hammingDistance(_, _))).reduce(_ + _) / (grouped.length - 1.0) / keySize
}
- def findBestSingleByteKey(ciphertext: Seq[Byte]): Byte =
+ def crackForMultiByteKey(ciphertext: Seq[Byte], inspectSet: Seq[Int] = Seq()): String =
+ decryptToAscii(ciphertext)(findBestMultiByteKey(ciphertext, inspectSet))
+
+ def findBestMultiByteKey(xs: Seq[Byte], inspectSet: Seq[Int] = Seq()): Seq[Byte] =
+ continuous(findBestMultiByteKeyOfSize(inferKeySize(xs))(xs, inspectSet))
+/*
+ def crackForKeySize(xs: Seq[Byte], keySize: Int): String = {
+ (findBestMultiByteKeyOfSize(keySize) _ :| continuous _ :| decryptToAscii(xs) _)(xs)
+ }
+*/
+ def decrypt(ciphertext: Seq[Byte])(key: Seq[Byte]): Seq[Byte] =
+ ciphertext xor key
+
+ def decryptToAscii(ciphertext: Seq[Byte])(key: Seq[Byte]): String =
+ decrypt(ciphertext)(key).asAscii
+
+ // TODO: make an xor key a proper type.
+ def decryptToAsciiSingleByteKey(ciphertext: Seq[Byte])(key: Byte): String =
+ decryptToAscii(ciphertext)(Stream.continually(key))
+
+ def tryBestDecrypt(ciphertext: Seq[Byte]): Seq[Byte] = {
+ (findBestSingleByteKey _ :| { x => Stream.continually(x) } :| decrypt(ciphertext) _)(ciphertext)
+ }
+
+ def tryBestDecryptToText: Seq[Byte] => String =
+ tryBestDecrypt _ :| ((_: Seq[Byte]).asAscii)
+
+ def findBestMultiByteKeyOfSize(keySize: Int)(ciphertext: Seq[Byte], inspectSet: Seq[Int] = Seq()): Seq[Byte] = {
+ // inspectSet is the set of key bytes for which we want debugging information
+ ciphertext.grouped(keySize).toSeq.dropRight(1).transpose.zipWithIndex
+ .map { case (byteCiphertext, idx) => {
+ if (inspectSet.contains(idx)) {
+ println("For idx = " + idx)
+ println(findBestSingleByteKeyWithCandidates(byteCiphertext))
+ }
+ findBestSingleByteKey(byteCiphertext)
+ }}
+ }
+
+ def findBestSingleByteKeyWithCandidates(ciphertext: Seq[Byte]): Seq[(Double, String, Byte)] =
+ candidates(ciphertext).sortBy(_._1).map {
+ case (score, key) => (score, decryptToAsciiSingleByteKey(ciphertext)(key.toByte), key.toByte)
+ }
+
+ def findBestSingleByteKey(ciphertext: Seq[Byte]): Byte = {
candidates(ciphertext)
.reduceOption( (a: (Double, Int), b: (Double, Int)) =>
if (a._1 < b._1) a else b
).map(_._2.toByte).getOrElse(0.toByte)
+ }
def candidates(ciphertext: Seq[Byte]) =
(0 until 256)
@@ -30,6 +95,6 @@ object XorDecrypt {
def candidatesAsAscii(ciphertext: Seq[Byte]) =
candidates(ciphertext).sortBy(_._1).map(x =>
- new String((ciphertext xor Stream.continually(x._2.toByte)).toArray)
+ decrypt(ciphertext)(Seq(x._2.toByte)).asAscii
)
}
diff --git a/test/StreamUtilsSpec.scala b/test/StreamUtilsSpec.scala
new file mode 100644
index 0000000..b9a710c
--- /dev/null
+++ b/test/StreamUtilsSpec.scala
@@ -0,0 +1,38 @@
+package ixee.cryptopals.test
+
+import com.ixee.IxeeSpec
+import ixee.cryptopals.utils.StreamUtils
+
+class StreamUtilsSpec extends IxeeSpec {
+
+ "StreamUtils" - {
+
+ ".fromSeq" - {
+ "is better written as xs.to[Stream]" in { }
+/*
+ "streams the given Seq's elements non-cyclically" in {
+
+ StreamUtils.fromSeq(Seq(1, 2, 3)) mustBe
+ (1 #:: 2 #:: 3 #:: Stream.empty)
+
+ }
+*/
+ }
+
+ ".continuous" - {
+
+ "streams the given Seq's elements cyclically" in {
+
+ assert(
+ StreamUtils.continuous(Seq(1, 2)).startsWith(
+ Seq(1, 2, 1, 2)
+ )
+ )
+
+ }
+
+ }
+
+ }
+
+}