Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Christian solutions for the micro challenge 001 #51

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package chriszen.multihash

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
}
}
Original file line number Diff line number Diff line change
@@ -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]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package chriszen.singlehash

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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package chriszen.singlehash

import java.io.FileInputStream

object SingleHashEval {
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 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"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package chriszen.singlehash.solution1

import java.io.{InputStreamReader, BufferedReader, InputStream}
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.
*
* It is more efficient than the solution2 for a single target search.
*/
class PasswordFinder {

def findPassword(
targetHashString: String,
inputStream: InputStream,
charsetName: String = "UTF-8",
hashAlgorithm: String = "SHA-256"
) = {

val bis = new BufferedReader(new InputStreamReader(inputStream, charsetName))

var password: Option[String] = None

val targetHash = Hash.fromBase64(targetHashString)

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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package chriszen.singlehash.solution2

import java.security.MessageDigest
import chriszen.singlehash.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 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) {
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 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")
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()
}
}



Loading