From c2e624b49a1bc2cf817ecbb75a41671f88bc92a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20P=C3=A9rez-Llamas?= Date: Wed, 28 May 2014 17:36:58 +0200 Subject: [PATCH 1/4] first implementation of the TernarySearchTree --- .../src/main/scala/PasswordFinder.scala | 15 ++ .../001/chris_zen/src/main/scala/TST.scala | 183 ++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 micro/backlog/001/chris_zen/src/main/scala/PasswordFinder.scala create mode 100644 micro/backlog/001/chris_zen/src/main/scala/TST.scala diff --git a/micro/backlog/001/chris_zen/src/main/scala/PasswordFinder.scala b/micro/backlog/001/chris_zen/src/main/scala/PasswordFinder.scala new file mode 100644 index 0000000..3ea5bef --- /dev/null +++ b/micro/backlog/001/chris_zen/src/main/scala/PasswordFinder.scala @@ -0,0 +1,15 @@ +import java.io.InputStream + +class PasswordFinder(val inputStream: InputStream) { + + +} + +object Main { + def main(args: Array[String]) { + val inputStream = getClass().getResourceAsStream("/cain_test.txt") + + //val pf = new PasswordFnder(inputStream) + println(TST.fromInputStream(inputStream).toString) + } +} \ No newline at end of file diff --git a/micro/backlog/001/chris_zen/src/main/scala/TST.scala b/micro/backlog/001/chris_zen/src/main/scala/TST.scala new file mode 100644 index 0000000..8ba0459 --- /dev/null +++ b/micro/backlog/001/chris_zen/src/main/scala/TST.scala @@ -0,0 +1,183 @@ +/** + * Immutable Ternary Search Tree + * + * Created by cperez on 28/05/14. + */ + +import java.io.{InputStreamReader, BufferedReader, InputStream} +import java.security.MessageDigest +import scala.util.Random + +trait TernarySearchTree { + + def isEmpty(): Boolean = true + + def put(s: String, index: Int): TernarySearchTree = { + if (index >= s.length()) + TST.Empty + else + new TST( + TST.Empty, put(s, index + 1), TST.Empty, + s.charAt(index), index == s.length() - 1) + } + + def put(s: String): TernarySearchTree = { + put(s, 0) + } + + def toString(sb: StringBuilder, margin: String) { + sb.append("-\n") + } + + override def toString: String = "Empty" +} + +object TST { + + val Empty = new TernarySearchTree {} + + def fromInputStream(is: InputStream, charsetName: String = "UTF-8") = { + val bis = new BufferedReader(new InputStreamReader(is, charsetName)) + + var tree = Empty + + // suffle buffer + val bufSize = 1000 + val buffer = new Array[String](bufSize) + var bufIndex: Int = 0 + val r = new Random + + // fill the buffer + var s: String = null + while ({s = bis.readLine(); s != null && bufIndex < bufSize}) { + buffer(bufIndex) = s + bufIndex += 1 + } + bufIndex -= 1 + + // grow tree while keeping the buffer filled + while ({s = bis.readLine(); s != null}) { + val nextIndex = r.nextInt(bufIndex + 1) + val nextWord = buffer(nextIndex) + buffer(nextIndex) = buffer(bufIndex) + buffer(bufIndex) = s + + //println(s"1:Putting $nextWord ...") + tree = tree.put(nextWord) + //println(tree.toString) + //println(buffer.toList) + } + + // flush the buffer + while (bufIndex > 0) { + val nextIndex = r.nextInt(bufIndex + 1) + val nextWord = buffer(nextIndex) + buffer(nextIndex) = buffer(bufIndex) + buffer(bufIndex) = s + bufIndex -= 1 + + //println(s"2:Putting $nextWord ...") + tree = tree.put(nextWord) + //println(tree.toString) + //println((for (i <- 0 to bufIndex) yield buffer(i)).toList) + } + + // pop the last element + if (bufIndex >= 0) { + val v = buffer(0) + println(s"3:Putting $v ...") + tree = tree.put(v) + //println(tree.toString) + } + + tree + } +} + +/*class HashDigest(prev: HashDigest, ch: Int) { + val m: MessageDigest = prev.clone() + m.update(ch) + + override def equals(other: Any): Boolean = { + if (!other.isInstanceOf[HashDigest]) + false + else { + val o = other.asInstanceOf[HashDigest] + if (o.bytes.size != bytes.size) + false + else { + var i = 0; + while (i < bytes.size && bytes(i) == o(i)) + i += 1 + i == bytes.size + } + } + } +}*/ + +class TST( + val left: TernarySearchTree, + val mid: TernarySearchTree, + val right: TernarySearchTree, + val key: Int, + val word: Boolean + //val hash: Option[HashDigest] + ) extends TernarySearchTree { + + override def isEmpty() = false + + override def put(s: String, index: Int): TernarySearchTree = { + if (index >= s.length()) + TST.Empty + else { + val currentChar = s.charAt(index) + if (currentChar == key) + new TST( + left, mid.put(s, index + 1), right, + key, word || index == s.length() - 1) + else if (currentChar < key) + new TST( + left.put(s, index), mid, right, + key, word || index == s.length() - 1) + else //if (currentChar > ch) + new TST( + left, mid, right.put(s, index), + key, word || index == s.length() - 1) + } + } + + override def put(s: String): TernarySearchTree = { + put(s, 0) + } + + override def toString(sb: StringBuilder, margin: String) { + sb.append(s"[$key, $word]") + if (left != TST.Empty || mid != TST.Empty || right != TST.Empty) { + sb.append("\n") + if (left != TST.Empty) { + sb.append(s"${margin}l: ") + left.toString(sb, margin + " ") + } + if (mid != TST.Empty) { + sb.append(s"${margin}m: ") + mid.toString(sb, margin + " ") + } + if (right != TST.Empty) { + sb.append(s"${margin}r: ") + right.toString(sb, margin + " ") + } + //sb.append(margin) + //sb.append(")\n") + } + else { + //sb.append(")\n") + sb.append("\n") + } + } + + override def toString: String = { + val sb = new StringBuilder + toString(sb, "") + sb.toString + } +} From 41987d8b671b2bce16515d19cf2f5ded02babd2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20P=C3=A9rez-Llamas?= Date: Thu, 29 May 2014 11:27:32 +0200 Subject: [PATCH 2/4] Two solutions: One is a simple solution that iterates all the dictionary keys and compares and the other is based on ternary search trees to not having to calculate the digest for already found substrings. --- .../src/main/scala/PasswordFinder.scala | 15 -- .../001/chris_zen/src/main/scala/TST.scala | 183 ------------------ .../src/main/scala/chriszen/Hash.scala | 34 ++++ .../src/main/scala/chriszen/Main.scala | 26 +++ .../chriszen/solution1/PasswordFinder.scala | 42 ++++ .../scala/chriszen/solution2/HashedTST.scala | 135 +++++++++++++ .../chriszen/solution2/PasswordFinder.scala | 133 +++++++++++++ 7 files changed, 370 insertions(+), 198 deletions(-) delete mode 100644 micro/backlog/001/chris_zen/src/main/scala/PasswordFinder.scala delete mode 100644 micro/backlog/001/chris_zen/src/main/scala/TST.scala create mode 100644 micro/backlog/001/chris_zen/src/main/scala/chriszen/Hash.scala create mode 100644 micro/backlog/001/chris_zen/src/main/scala/chriszen/Main.scala create mode 100644 micro/backlog/001/chris_zen/src/main/scala/chriszen/solution1/PasswordFinder.scala create mode 100644 micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/HashedTST.scala create mode 100644 micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/PasswordFinder.scala diff --git a/micro/backlog/001/chris_zen/src/main/scala/PasswordFinder.scala b/micro/backlog/001/chris_zen/src/main/scala/PasswordFinder.scala deleted file mode 100644 index 3ea5bef..0000000 --- a/micro/backlog/001/chris_zen/src/main/scala/PasswordFinder.scala +++ /dev/null @@ -1,15 +0,0 @@ -import java.io.InputStream - -class PasswordFinder(val inputStream: InputStream) { - - -} - -object Main { - def main(args: Array[String]) { - val inputStream = getClass().getResourceAsStream("/cain_test.txt") - - //val pf = new PasswordFnder(inputStream) - println(TST.fromInputStream(inputStream).toString) - } -} \ No newline at end of file diff --git a/micro/backlog/001/chris_zen/src/main/scala/TST.scala b/micro/backlog/001/chris_zen/src/main/scala/TST.scala deleted file mode 100644 index 8ba0459..0000000 --- a/micro/backlog/001/chris_zen/src/main/scala/TST.scala +++ /dev/null @@ -1,183 +0,0 @@ -/** - * Immutable Ternary Search Tree - * - * Created by cperez on 28/05/14. - */ - -import java.io.{InputStreamReader, BufferedReader, InputStream} -import java.security.MessageDigest -import scala.util.Random - -trait TernarySearchTree { - - def isEmpty(): Boolean = true - - def put(s: String, index: Int): TernarySearchTree = { - if (index >= s.length()) - TST.Empty - else - new TST( - TST.Empty, put(s, index + 1), TST.Empty, - s.charAt(index), index == s.length() - 1) - } - - def put(s: String): TernarySearchTree = { - put(s, 0) - } - - def toString(sb: StringBuilder, margin: String) { - sb.append("-\n") - } - - override def toString: String = "Empty" -} - -object TST { - - val Empty = new TernarySearchTree {} - - def fromInputStream(is: InputStream, charsetName: String = "UTF-8") = { - val bis = new BufferedReader(new InputStreamReader(is, charsetName)) - - var tree = Empty - - // suffle buffer - val bufSize = 1000 - val buffer = new Array[String](bufSize) - var bufIndex: Int = 0 - val r = new Random - - // fill the buffer - var s: String = null - while ({s = bis.readLine(); s != null && bufIndex < bufSize}) { - buffer(bufIndex) = s - bufIndex += 1 - } - bufIndex -= 1 - - // grow tree while keeping the buffer filled - while ({s = bis.readLine(); s != null}) { - val nextIndex = r.nextInt(bufIndex + 1) - val nextWord = buffer(nextIndex) - buffer(nextIndex) = buffer(bufIndex) - buffer(bufIndex) = s - - //println(s"1:Putting $nextWord ...") - tree = tree.put(nextWord) - //println(tree.toString) - //println(buffer.toList) - } - - // flush the buffer - while (bufIndex > 0) { - val nextIndex = r.nextInt(bufIndex + 1) - val nextWord = buffer(nextIndex) - buffer(nextIndex) = buffer(bufIndex) - buffer(bufIndex) = s - bufIndex -= 1 - - //println(s"2:Putting $nextWord ...") - tree = tree.put(nextWord) - //println(tree.toString) - //println((for (i <- 0 to bufIndex) yield buffer(i)).toList) - } - - // pop the last element - if (bufIndex >= 0) { - val v = buffer(0) - println(s"3:Putting $v ...") - tree = tree.put(v) - //println(tree.toString) - } - - tree - } -} - -/*class HashDigest(prev: HashDigest, ch: Int) { - val m: MessageDigest = prev.clone() - m.update(ch) - - override def equals(other: Any): Boolean = { - if (!other.isInstanceOf[HashDigest]) - false - else { - val o = other.asInstanceOf[HashDigest] - if (o.bytes.size != bytes.size) - false - else { - var i = 0; - while (i < bytes.size && bytes(i) == o(i)) - i += 1 - i == bytes.size - } - } - } -}*/ - -class TST( - val left: TernarySearchTree, - val mid: TernarySearchTree, - val right: TernarySearchTree, - val key: Int, - val word: Boolean - //val hash: Option[HashDigest] - ) extends TernarySearchTree { - - override def isEmpty() = false - - override def put(s: String, index: Int): TernarySearchTree = { - if (index >= s.length()) - TST.Empty - else { - val currentChar = s.charAt(index) - if (currentChar == key) - new TST( - left, mid.put(s, index + 1), right, - key, word || index == s.length() - 1) - else if (currentChar < key) - new TST( - left.put(s, index), mid, right, - key, word || index == s.length() - 1) - else //if (currentChar > ch) - new TST( - left, mid, right.put(s, index), - key, word || index == s.length() - 1) - } - } - - override def put(s: String): TernarySearchTree = { - put(s, 0) - } - - override def toString(sb: StringBuilder, margin: String) { - sb.append(s"[$key, $word]") - if (left != TST.Empty || mid != TST.Empty || right != TST.Empty) { - sb.append("\n") - if (left != TST.Empty) { - sb.append(s"${margin}l: ") - left.toString(sb, margin + " ") - } - if (mid != TST.Empty) { - sb.append(s"${margin}m: ") - mid.toString(sb, margin + " ") - } - if (right != TST.Empty) { - sb.append(s"${margin}r: ") - right.toString(sb, margin + " ") - } - //sb.append(margin) - //sb.append(")\n") - } - else { - //sb.append(")\n") - sb.append("\n") - } - } - - override def toString: String = { - val sb = new StringBuilder - toString(sb, "") - sb.toString - } -} diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/Hash.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/Hash.scala new file mode 100644 index 0000000..27f1493 --- /dev/null +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/Hash.scala @@ -0,0 +1,34 @@ +package chriszen + +import javax.xml.bind.DatatypeConverter + +object Hash { + def apply(b: Array[Byte]) = { + new Hash(b) + } + + def fromBase64(s: String) = { + new Hash(DatatypeConverter.parseBase64Binary(s)) + } +} + +class Hash(val bytes: Array[Byte]) { + + override def equals(other: Any) = { + if (!other.isInstanceOf[Hash]) + false + else { + val o = other.asInstanceOf[Hash] + if (bytes.length != o.bytes.length) + false + else { + var i = 0 + while (i < bytes.length && bytes(i) == o.bytes(i)) + i += 1 + i == bytes.length + } + } + } + + override def toString = DatatypeConverter.printBase64Binary(bytes) +} \ No newline at end of file diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/Main.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/Main.scala new file mode 100644 index 0000000..b6ae470 --- /dev/null +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/Main.scala @@ -0,0 +1,26 @@ +package chriszen + +import java.io.FileInputStream + +object Main { + val hashAlgorithm = "SHA-256" + val charsetName = "UTF-8" + + def main(args: Array[String]) { + + val defaultTargetHash = "tMWuKulh/ojRKCG9+UWxVILvAhcD4fkAzL4aJ/It8H8=" + + val targetHash = if (args.length > 0) args(0) else defaultTargetHash + + val inputStream = if (args.length > 1) + new FileInputStream(args(1)) + else + getClass.getResourceAsStream("/cain.txt") + + val passwordFinder = new solution1.PasswordFinder() + val password = passwordFinder.findPassword( + Hash.fromBase64(targetHash), inputStream, charsetName, hashAlgorithm) + + println(password.fold(s"The password for '$targetHash' has not been found :-(")(password => s"The password for '$targetHash' is $password")) + } +} diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution1/PasswordFinder.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution1/PasswordFinder.scala new file mode 100644 index 0000000..90ea0ae --- /dev/null +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution1/PasswordFinder.scala @@ -0,0 +1,42 @@ +package chriszen.solution1 + +import java.io.{InputStreamReader, BufferedReader, InputStream} +import chriszen.Hash +import java.security.MessageDigest + +/** + * This is the simple solution that just calculates the hash for each dictionary key and compares with the target. + * + * It is more efficient than the solution2 for a single target search. + */ +class PasswordFinder { + + def findPassword( + targetHash: Hash, + inputStream: InputStream, + charsetName: String = "UTF-8", + hashAlgorithm: String = "SHA-256" + ) = { + + val bis = new BufferedReader(new InputStreamReader(inputStream, charsetName)) + + var password: Option[String] = None + + def check(key: String) = { + //println(s"Checking $key ...") + + val validationHash = new Hash(MessageDigest.getInstance(hashAlgorithm).digest(key.getBytes)) + + if (targetHash equals validationHash) + Some(key) + else + None + } + + var s: String = null + while (password.isEmpty && {s = bis.readLine(); s != null}) + password = check(s) + + password + } +} diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/HashedTST.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/HashedTST.scala new file mode 100644 index 0000000..65ceae4 --- /dev/null +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/HashedTST.scala @@ -0,0 +1,135 @@ +package chriszen.solution2 + +import java.security.MessageDigest +import chriszen.Hash + +/** + * Immutable hashed ternary search tree. + * + * http://en.wikipedia.org/wiki/Ternary_search_tree + * + * It keeps the digest and the previously calculated hash for each node + * so the calculation of the hash for strings with common prefixes is optimized. + * + * The hash algorithm is hard-coded and requires recompilation to change it. + */ + +object HashedTST { + val Algorithm = "SHA-256" + val Empty = new HashedTST {} +} + +trait HashedTST { + + def isEmpty: Boolean = true + + def put(s: String, index: Int, prevDigest: MessageDigest): (HashedTST, Option[Hash]) = { + if (index >= s.length) + (HashedTST.Empty, None) + else { + val currentChar = s.charAt(index) + + val digest = prevDigest.clone.asInstanceOf[MessageDigest] + + digest.update(currentChar.toByte) + + val (subtree, foundHash) = put(s, index + 1, digest) + + val nextHash = if (index == s.length - 1) + Some(new Hash(digest.clone.asInstanceOf[MessageDigest].digest())) + else None + + (new HashedTSTNode(HashedTST.Empty, subtree, HashedTST.Empty, currentChar, + /*s.substring(0, index + 1),*/ digest, nextHash), foundHash orElse nextHash) + } + } + + def put(s: String): (HashedTST, Hash) = { + val (nextTree, foundHash) = put(s, 0, createDigest) + (nextTree, foundHash.get) + } + + def createDigest = { MessageDigest.getInstance(HashedTST.Algorithm) } + + def toStringHelper(sb: StringBuilder, margin: String) { + sb.append("-\n") + } + + override def toString: String = "Empty" +} + +class HashedTSTNode( + val left: HashedTST, + val mid: HashedTST, + val right: HashedTST, + val key: Char, + //val path: String, + val digest: MessageDigest, + val hash: Option[Hash] + ) extends HashedTST { + + override def isEmpty() = false + + override def put(s: String, index: Int, prevDigest: MessageDigest): (HashedTST, Option[Hash]) = { + if (index >= s.length()) + (HashedTST.Empty, None) + else { + val currentChar = s.charAt(index) + + val nextHash = if (index == s.length - 1) + Some(hash getOrElse new Hash(digest.clone.asInstanceOf[MessageDigest].digest())) + else None + + if (currentChar == key) { + val (subtree, foundHash) = mid.put(s, index + 1, digest) + (new HashedTSTNode(left, subtree, right, key, /*path,*/ digest, nextHash), foundHash orElse nextHash) + } + else if (currentChar < key) { + val (subtree, foundHash) = left.put(s, index, prevDigest) + (new HashedTSTNode(subtree, mid, right, key, /*path,*/ digest, nextHash), foundHash orElse nextHash) + } + else {//if (currentChar > ch) + val (subtree, foundHash) = right.put(s, index, prevDigest) + (new HashedTSTNode(left, mid, subtree, key, /*path,*/ digest, nextHash), foundHash orElse nextHash) + } + } + } + + override def put(s: String): (HashedTST, Hash) = { + val (nextTree, foundHash) = put(s, 0, createDigest) + (nextTree, foundHash.get) + } + + override def toStringHelper(sb: StringBuilder, margin: String) { + //sb.append(s"[$key, $path") + sb.append(s"[$key") + sb.append(hash.fold("]")(h => ", " + h.toString + "]")) + + if (left != HashedTST.Empty || mid != HashedTST.Empty || right != HashedTST.Empty) { + sb.append("\n") + if (left != HashedTST.Empty) { + sb.append(s"${margin}l: ") + left.toStringHelper(sb, margin + " ") + } + if (mid != HashedTST.Empty) { + sb.append(s"${margin}m: ") + mid.toStringHelper(sb, margin + " ") + } + if (right != HashedTST.Empty) { + sb.append(s"${margin}r: ") + right.toStringHelper(sb, margin + " ") + } + } + else + sb.append("\n") + } + + override def toString: String = { + val sb = new StringBuilder + toStringHelper(sb, "") + sb.toString() + } +} + + + diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/PasswordFinder.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/PasswordFinder.scala new file mode 100644 index 0000000..ff8f8c9 --- /dev/null +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/PasswordFinder.scala @@ -0,0 +1,133 @@ +package chriszen.solution2 + +import java.io.{InputStreamReader, BufferedReader, InputStream} +import scala.util.Random +import chriszen.Hash + +/** + * This solution uses a Ternary Search Tree to save the pre-calculated digests for all the substrings already found. + * + * This would be a good solution in case that many target hashes were checked + * but for a single one the immutability (and its associated heap use) has a big penalty + */ + +class PasswordFinder { + + //TODO HashedTST does not support defining the hash algorithm yet + + def findPassword( + targetHash: Hash, + inputStream: InputStream, + charsetName: String = "UTF-8", + hashAlgorithm: String = "SHA-256" + ) = { + + val bis = new BufferedReader(new InputStreamReader(inputStream, charsetName)) + + var password: Option[String] = None + + var tree = HashedTST.Empty + + def check(key: String) = { + //println(s"Putting $key ...") + val (nextTree, foundHash) = tree.put(key) + //println(tree.toString) + //println(buffer.toList) + + tree = nextTree + + /*println(s"$key = " + foundHash.toString) + val validationHash = new Hash(MessageDigest.getInstance("SHA-256").digest(key.getBytes)) + if (!(validationHash equals foundHash)) + println(s"[----------- $x ]")*/ + + if (targetHash equals foundHash) + Some(key) + else + None + } + + var s: String = null + while (password.isEmpty && {s = bis.readLine(); s != null}) + password = check(s) + + //println(tree.toString) + + password + } + + def findPasswordWithRandomization( + targetHash: Hash, + inputStream: InputStream, + charsetName: String = "UTF-8", + hashAlgorithm: String = "SHA-256", + bufSize: Int = 1000) = { + + val bis = new BufferedReader(new InputStreamReader(inputStream, charsetName)) + + // we use a shuffle buffer to keep the tree as balanced as possible + val buffer = new Array[String](bufSize) + var bufIndex: Int = 0 + val r = new Random + + var password: Option[String] = None + + var tree = HashedTST.Empty + + def check(key: String) = { + //println(s"Putting $key ...") + val (nextTree, foundHash) = tree.put(key) + //println(tree.toString) + //println(buffer.toList) + + tree = nextTree + + /*println(s"$key = " + foundHash.toString) + val validationHash = new Hash(MessageDigest.getInstance("SHA-256").digest(key.getBytes)) + if (!(validationHash equals foundHash)) + println(s"[----------- $x ]")*/ + + if (targetHash equals foundHash) + Some(key) + else + None + } + + // fill the buffer + var s: String = null + while ({s = bis.readLine(); s != null && bufIndex < bufSize}) { + buffer(bufIndex) = s + bufIndex += 1 + } + bufIndex -= 1 + + // grow the tree while keeping the buffer filled + while (password.isEmpty && {s = bis.readLine(); s != null}) { + val nextIndex = r.nextInt(bufIndex + 1) + val nextWord = buffer(nextIndex) + buffer(nextIndex) = buffer(bufIndex) + buffer(bufIndex) = s + + password = check(nextWord) + } + + // flush the buffer + while (password.isEmpty && bufIndex > 0) { + val nextIndex = r.nextInt(bufIndex + 1) + val nextWord = buffer(nextIndex) + buffer(nextIndex) = buffer(bufIndex) + buffer(bufIndex) = s + bufIndex -= 1 + + password = check(nextWord) + } + + // pop the last element + if (password.isEmpty && bufIndex >= 0) + password = check(buffer(0)) + + //println(tree.toString) + + password + } +} From 2dbc4bcff93cda802f91b285eafa290e3015ac34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20P=C3=A9rez-Llamas?= Date: Thu, 29 May 2014 18:57:40 +0200 Subject: [PATCH 3/4] Simple multi hash password finder. --- .../main/scala/chriszen/MultiHashEval.scala | 75 +++++++++++++++++++ .../multihash/MapPasswordFinder.scala | 27 +++++++ .../chriszen/multihash/PasswordFinder.scala | 12 +++ .../chriszen/{ => singlehash}/Hash.scala | 2 +- .../SingleHashEval.scala} | 9 +-- .../solution1/PasswordFinder.scala | 9 ++- .../solution2/HashedTST.scala | 25 ++++++- .../solution2/PasswordFinder.scala | 12 ++- 8 files changed, 156 insertions(+), 15 deletions(-) create mode 100644 micro/backlog/001/chris_zen/src/main/scala/chriszen/MultiHashEval.scala create mode 100644 micro/backlog/001/chris_zen/src/main/scala/chriszen/multihash/MapPasswordFinder.scala create mode 100644 micro/backlog/001/chris_zen/src/main/scala/chriszen/multihash/PasswordFinder.scala rename micro/backlog/001/chris_zen/src/main/scala/chriszen/{ => singlehash}/Hash.scala (95%) rename micro/backlog/001/chris_zen/src/main/scala/chriszen/{Main.scala => singlehash/SingleHashEval.scala} (71%) rename micro/backlog/001/chris_zen/src/main/scala/chriszen/{ => singlehash}/solution1/PasswordFinder.scala (83%) rename micro/backlog/001/chris_zen/src/main/scala/chriszen/{ => singlehash}/solution2/HashedTST.scala (87%) rename micro/backlog/001/chris_zen/src/main/scala/chriszen/{ => singlehash}/solution2/PasswordFinder.scala (92%) diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/MultiHashEval.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/MultiHashEval.scala new file mode 100644 index 0000000..a64add8 --- /dev/null +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/MultiHashEval.scala @@ -0,0 +1,75 @@ +package chriszen + +import java.io.{InputStreamReader, BufferedReader, InputStream, FileInputStream} +import scala.io.Source +import javax.xml.bind.DatatypeConverter +import java.security.MessageDigest +import scala.collection.mutable.ArrayBuffer + +object MultiHashEval { + val hashAlgorithm = "SHA-256" + val charsetName = "UTF-8" + val dictResource = "/cain.txt" + + def main(args: Array[String]) { + + val hashes = if (args.length > 0) + Source.fromFile(args(0), charsetName).getLines + else + loadHashesFromDictionary( + getClass.getResourceAsStream(dictResource), charsetName, hashAlgorithm) + + val inputStream = if (args.length > 1) + new FileInputStream(args(1)) + else + getClass.getResourceAsStream(dictResource) + + val passwordFinder = new chriszen.multihash.MapPasswordFinder + + time("Loading dictionary ...") { + passwordFinder.loadDictionary(inputStream, charsetName, hashAlgorithm) + } + + time("Looking for passwords ...") { + val hits = hashes.map { hash => + passwordFinder.findPassword(hash).fold(0)(pass => 1) + }.sum + + println(s"Found $hits passwords out of " + hashes.size) + } + + /*time("Looking for passwords ...") { + hashes.foreach { hash => + passwordFinder.findPassword(hash).foreach { password => + println(s"The password for '$hash' is $password") + } + } + }*/ + } + + def loadHashesFromDictionary(inputStream: InputStream, + charsetName: String = "UTF-8", + hashAlgorithm: String = "SHA-256") = { + + val bis = new BufferedReader(new InputStreamReader(inputStream, charsetName)) + + val hashes = new ArrayBuffer[String]() + + var s: String = null + while ({ s = bis.readLine(); s != null }) + hashes += DatatypeConverter.printBase64Binary( + MessageDigest.getInstance(hashAlgorithm).digest(s.getBytes)) + + hashes + } + + // Adapted from http://stackoverflow.com/questions/15436593/how-to-measure-and-display-the-running-time-of-a-single-test + def time[T](title: String)(code: => T): T = { + println(title) + val start = System.currentTimeMillis + val x = code + val elapsed = ((System.currentTimeMillis - start) / 1000.0) + println("Done in %.3f secs" format elapsed) + x + } +} diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/multihash/MapPasswordFinder.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/multihash/MapPasswordFinder.scala new file mode 100644 index 0000000..37965c5 --- /dev/null +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/multihash/MapPasswordFinder.scala @@ -0,0 +1,27 @@ +package chriszen.multihash + +import java.io.{InputStreamReader, BufferedReader, InputStream} +import java.security.MessageDigest +import scala.collection.mutable +import javax.xml.bind.DatatypeConverter + +class MapPasswordFinder extends PasswordFinder { + + private val map = new mutable.HashMap[String, String]() + + override def loadDictionary(inputStream: InputStream, + charsetName: String = "UTF-8", + hashAlgorithm: String = "SHA-256") { + + val bis = new BufferedReader(new InputStreamReader(inputStream, charsetName)) + + var s: String = null + while ({ s = bis.readLine(); s != null }) { + val hash = DatatypeConverter.printBase64Binary( + MessageDigest.getInstance(hashAlgorithm).digest(s.getBytes)) + map.put(hash, s) + } + } + + override def findPassword(hash: String): Option[String] = map.get(hash) +} diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/multihash/PasswordFinder.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/multihash/PasswordFinder.scala new file mode 100644 index 0000000..518a2af --- /dev/null +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/multihash/PasswordFinder.scala @@ -0,0 +1,12 @@ +package chriszen.multihash + +import java.io.InputStream + +abstract class PasswordFinder { + + def loadDictionary(inputStream: InputStream, + charsetName: String = "UTF-8", + hashAlgorithm: String = "SHA-256") + + def findPassword(hash: String): Option[String] +} diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/Hash.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/Hash.scala similarity index 95% rename from micro/backlog/001/chris_zen/src/main/scala/chriszen/Hash.scala rename to micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/Hash.scala index 27f1493..3bf45ef 100644 --- a/micro/backlog/001/chris_zen/src/main/scala/chriszen/Hash.scala +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/Hash.scala @@ -1,4 +1,4 @@ -package chriszen +package chriszen.singlehash import javax.xml.bind.DatatypeConverter diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/Main.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/SingleHashEval.scala similarity index 71% rename from micro/backlog/001/chris_zen/src/main/scala/chriszen/Main.scala rename to micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/SingleHashEval.scala index b6ae470..79a0dd0 100644 --- a/micro/backlog/001/chris_zen/src/main/scala/chriszen/Main.scala +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/SingleHashEval.scala @@ -1,8 +1,8 @@ -package chriszen +package chriszen.singlehash import java.io.FileInputStream -object Main { +object SingleHashEval { val hashAlgorithm = "SHA-256" val charsetName = "UTF-8" @@ -17,9 +17,8 @@ object Main { else getClass.getResourceAsStream("/cain.txt") - val passwordFinder = new solution1.PasswordFinder() - val password = passwordFinder.findPassword( - Hash.fromBase64(targetHash), inputStream, charsetName, hashAlgorithm) + val passwordFinder = new chriszen.singlehash.solution1.PasswordFinder + val password = passwordFinder.findPassword(targetHash, inputStream, charsetName, hashAlgorithm) println(password.fold(s"The password for '$targetHash' has not been found :-(")(password => s"The password for '$targetHash' is $password")) } diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution1/PasswordFinder.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/solution1/PasswordFinder.scala similarity index 83% rename from micro/backlog/001/chris_zen/src/main/scala/chriszen/solution1/PasswordFinder.scala rename to micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/solution1/PasswordFinder.scala index 90ea0ae..fe6cca2 100644 --- a/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution1/PasswordFinder.scala +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/solution1/PasswordFinder.scala @@ -1,8 +1,9 @@ -package chriszen.solution1 +package chriszen.singlehash.solution1 import java.io.{InputStreamReader, BufferedReader, InputStream} -import chriszen.Hash import java.security.MessageDigest +import javax.xml.bind.DatatypeConverter +import chriszen.singlehash.Hash /** * This is the simple solution that just calculates the hash for each dictionary key and compares with the target. @@ -12,7 +13,7 @@ import java.security.MessageDigest class PasswordFinder { def findPassword( - targetHash: Hash, + targetHashString: String, inputStream: InputStream, charsetName: String = "UTF-8", hashAlgorithm: String = "SHA-256" @@ -22,6 +23,8 @@ class PasswordFinder { var password: Option[String] = None + val targetHash = Hash.fromBase64(targetHashString) + def check(key: String) = { //println(s"Checking $key ...") diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/HashedTST.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/solution2/HashedTST.scala similarity index 87% rename from micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/HashedTST.scala rename to micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/solution2/HashedTST.scala index 65ceae4..b52ba47 100644 --- a/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/HashedTST.scala +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/solution2/HashedTST.scala @@ -1,7 +1,7 @@ -package chriszen.solution2 +package chriszen.singlehash.solution2 import java.security.MessageDigest -import chriszen.Hash +import chriszen.singlehash.Hash /** * Immutable hashed ternary search tree. @@ -49,6 +49,9 @@ trait HashedTST { (nextTree, foundHash.get) } + def get(key: String, index: Int): Option[Hash] = None + def get(key: String): Option[Hash] = None + def createDigest = { MessageDigest.getInstance(HashedTST.Algorithm) } def toStringHelper(sb: StringBuilder, margin: String) { @@ -100,6 +103,24 @@ class HashedTSTNode( (nextTree, foundHash.get) } + override def get(s: String, index: Int): Option[Hash] = { + val currentChar = s.charAt(index) + if (currentChar < key) + left.get(s, index) + else if (currentChar > key) + right.get(s, index) + else {// if (currentChar == key) + if (index < s.length - 1) + get(s, index + 1) + else + hash + } + } + + override def get(key: String): Option[Hash] = { + get(key, 0) + } + override def toStringHelper(sb: StringBuilder, margin: String) { //sb.append(s"[$key, $path") sb.append(s"[$key") diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/PasswordFinder.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/solution2/PasswordFinder.scala similarity index 92% rename from micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/PasswordFinder.scala rename to micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/solution2/PasswordFinder.scala index ff8f8c9..d02c19d 100644 --- a/micro/backlog/001/chris_zen/src/main/scala/chriszen/solution2/PasswordFinder.scala +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/singlehash/solution2/PasswordFinder.scala @@ -1,8 +1,8 @@ -package chriszen.solution2 +package chriszen.singlehash.solution2 import java.io.{InputStreamReader, BufferedReader, InputStream} import scala.util.Random -import chriszen.Hash +import chriszen.singlehash.Hash /** * This solution uses a Ternary Search Tree to save the pre-calculated digests for all the substrings already found. @@ -16,7 +16,7 @@ class PasswordFinder { //TODO HashedTST does not support defining the hash algorithm yet def findPassword( - targetHash: Hash, + targetHashString: String, inputStream: InputStream, charsetName: String = "UTF-8", hashAlgorithm: String = "SHA-256" @@ -28,6 +28,8 @@ class PasswordFinder { var tree = HashedTST.Empty + val targetHash = Hash.fromBase64(targetHashString) + def check(key: String) = { //println(s"Putting $key ...") val (nextTree, foundHash) = tree.put(key) @@ -57,7 +59,7 @@ class PasswordFinder { } def findPasswordWithRandomization( - targetHash: Hash, + targetHashString: String, inputStream: InputStream, charsetName: String = "UTF-8", hashAlgorithm: String = "SHA-256", @@ -74,6 +76,8 @@ class PasswordFinder { var tree = HashedTST.Empty + val targetHash = Hash.fromBase64(targetHashString) + def check(key: String) = { //println(s"Putting $key ...") val (nextTree, foundHash) = tree.put(key) From 3fb88d127c249a71bb6ee28fc550a65cc6a124b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20P=C3=A9rez-Llamas?= Date: Thu, 29 May 2014 19:04:45 +0200 Subject: [PATCH 4/4] Moved MultiHashEval --- .../src/main/scala/chriszen/{ => multihash}/MultiHashEval.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename micro/backlog/001/chris_zen/src/main/scala/chriszen/{ => multihash}/MultiHashEval.scala (98%) diff --git a/micro/backlog/001/chris_zen/src/main/scala/chriszen/MultiHashEval.scala b/micro/backlog/001/chris_zen/src/main/scala/chriszen/multihash/MultiHashEval.scala similarity index 98% rename from micro/backlog/001/chris_zen/src/main/scala/chriszen/MultiHashEval.scala rename to micro/backlog/001/chris_zen/src/main/scala/chriszen/multihash/MultiHashEval.scala index a64add8..369d65e 100644 --- a/micro/backlog/001/chris_zen/src/main/scala/chriszen/MultiHashEval.scala +++ b/micro/backlog/001/chris_zen/src/main/scala/chriszen/multihash/MultiHashEval.scala @@ -1,4 +1,4 @@ -package chriszen +package chriszen.multihash import java.io.{InputStreamReader, BufferedReader, InputStream, FileInputStream} import scala.io.Source