From 0fefd4ccf61197171dcf9699611f3301901d0dac Mon Sep 17 00:00:00 2001 From: Dan Goldsmith Date: Mon, 18 Oct 2021 14:14:11 +0100 Subject: [PATCH] Tests for Bcrypt and Salted versions added --- HashCracker/test_bcrypt.py | 134 ++++++++++++++++++++++++++++ HashCracker/test_proper_salt.py | 150 ++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 HashCracker/test_bcrypt.py create mode 100644 HashCracker/test_proper_salt.py diff --git a/HashCracker/test_bcrypt.py b/HashCracker/test_bcrypt.py new file mode 100644 index 0000000..00dd09c --- /dev/null +++ b/HashCracker/test_bcrypt.py @@ -0,0 +1,134 @@ +""" +Test of the code with Bcrypt hashes + +Almost exactly the same as the test_salted cases. +But as Bcrypt deals with salts automagically, we dont really need to worry about that. + + +""" + +import unittest +import time +import hashlib + +import bcrypt + +TARGETS = [b'$2b$12$CQdfZxhjQH83jY.RZNfGPegUWSL05J5Amekp5pmjXkAgvtuBynZzy', + b'$2b$12$4YiyGckKJXQGKMkGwOqM5.JKTjJIMlwwdfHYHtLhfc38lFU9iZicK', + b'$2b$12$8vMax8thp0D7G2yns8gY7unUmdZiiJKJrjhW7pRtoo.X4YDObRVCK', + b'$2b$12$JE6BgAfbDdLgWNnMuaqyeuvhmXECqsnZPMXqO3Rv.pxrzT8Zn4wqy', + b'$2b$12$YINaek/4qU8/k2egmjZxn.MYU7lVehNi8p94iEDFvM7MaS3BUlZKK'] + +CORRECT_WORDS = [b"coffee",b"azerty",b"spitfire", b"f00tball", b"1qazxsw23edc"] + +CORRECT_MATCHES = dict(zip(TARGETS, CORRECT_WORDS)) + + + + +def crackHash_Salt(wordlist, target): + """ + This time we have a list of salted hashes. + + It means we can no longer take the approach we used before, of + cracking all hashes and stashing in a dictionary. + + Instead we extract the salt, append it to each of the items in the wordlist + Then check if we get a match + + + """ + + + #Go through the wordlist and get the hash for each item. + for plaintext in wordlist: + plaintext = plaintext.strip().encode() #Damn Bytestrings + + if bcrypt.checkpw(plaintext, target): + return plaintext + + +def crackList_Salt(wordlist, targets): + """ + Use the strategy above to crack a list of passowords that have been salted + + As we cant store we just iterate through the list, and collect the details + """ + + matches = {} + + for item in targets: + cracked = crackHash_Salt(wordlist, item) + matches[item] = cracked + + return matches + + + + + + + +class TestCases(unittest.TestCase): + @classmethod + def setUpClass(cls): + """ + A bit of magic to keep the stats. + + Called the first time the class is run + """ + + cls.statsDict = {} + + @classmethod + def tearDownClass(cls): + """ + And a bit more magic to print the stats. + """ + + print("\n\n{0} STATS (Bcrypt Salted) {0}".format("-"*20)) + + print("Crack Single Salted : {0}".format(cls.statsDict["singleSalt"])) + print("Crack List of Salted: {0}".format(cls.statsDict["saltList"])) + + def setUp(self): + """ + Load the wordlist each time we run a test case + + Here we open the wordlist file, then store it as an array + This lets us reuse the list multiple times + """ + + with open("10-million-password-list-top-10000.txt") as fd: + #Store as an array + self.wordlist = fd.readlines() + + #SO I am going to cheat here and use a very abbreviated wordlist (Otherwis it takes forever) + self.wordlist = ["foo","bar","coffee" ,"azerty","spitfire", "f00tball", "1qazxsw23edc"] + + def testSingle_ProperSalt(self): + """ + Check how long it takes to crack a single salted hash + """ + + t1 = time.time() + out = crackHash_Salt(self.wordlist, TARGETS[0]) + t2 = time.time() + out = out.decode() #Remember Bytes + self.assertEqual(out, "coffee") #check we were successful (And remember Bytes here) + self.statsDict["singleSalt"] = t2-t1 + + + def testList_ProperSalt(self): + """ + And Crack a list of salted passwords + """ + t1 = time.time() + out = crackList_Salt(self.wordlist, TARGETS) + t2 = time.time() + self.assertEqual(out, CORRECT_MATCHES) #check we were successful + self.statsDict["saltList"] = t2-t1 + + + + diff --git a/HashCracker/test_proper_salt.py b/HashCracker/test_proper_salt.py new file mode 100644 index 0000000..34c4b51 --- /dev/null +++ b/HashCracker/test_proper_salt.py @@ -0,0 +1,150 @@ +""" +Test Cases and Demo code for Salted Hashed +""" + +import unittest +import time +import hashlib + +TARGETS = ["rkKxLR$c35bf00b953186ec3be4916dd45deabc", + "MyEtoS$b0a56a1df2353c7629509a12a17f5a2d", + "kshhHk$9664b09bedf4ed95f1b7b024087cec12", + "FMVbrf$6751d1e9a8ee57b383836596869cd94a", + "wKBUOm$981132067d1a4ef9e943b8c300071a55" + ] + +CORRECT_WORDS = ["coffee","azerty","spitfire", "f00tball","1qazxsw23edc"] + +CORRECT_MATCHES = dict(zip(TARGETS, CORRECT_WORDS)) + +# CORRECT_MATCHES = {'283140d63e0937fb652ff7066bbf5c2f': 'coffee', +# 'ba7c94b0431f30103c7eb5cdae180be6': 'azerty', +# 'ff0e0cefdceb54618f47767d17b95a12': 'spitfire', +# 'ef98a984f8ab1341039f9f3344d80298': 'f00tball', +# '25e2262b5d8c95f7ece0bc4f30f5213d': '1qazxsw23edc'} + + + +def extractSalt(theHash): + """ + Break a Salted Hash into its compoenents. + + We are using the convention of $ + so can simply split on $ + + @return: Tuple of [salt, hash] + """ + + return theHash.split("$") +#And modify the function with lookups we used before + + +def applySalt(plaintext, salt): + """ + Apply some Salt to the plaintext + """ + return "{0}{1}".format(plaintext, salt) + +def crackHash_Salt(wordlist, target): + """ + This time we have a list of salted hashes. + + It means we can no longer take the approach we used before, of + cracking all hashes and stashing in a dictionary. + + Instead we extract the salt, append it to each of the items in the wordlist + Then check if we get a match + + + """ + + #Fetch the Salt from the first item + theSalt, targetNoSalt = extractSalt(target) + #print("Extract Salt {0} and Hash {1}".format(theSalt, targetNoSalt)) + + #Go through the wordlist and get the hash for each item. + for plaintext in wordlist: + plaintext = plaintext.strip() + saltedText = applySalt(plaintext, theSalt) + theHash = hashlib.md5(saltedText.encode()).hexdigest() + #Store in the "Database" + if theHash == targetNoSalt: + return plaintext + + + +def crackList_Salt(wordlist, targets): + """ + Use the strategy above to crack a list of passowords that have been salted + + As we cant store we just iterate through the list, and collect the details + """ + + matches = {} + + for item in targets: + cracked = crackHash_Salt(wordlist, item) + matches[item] = cracked + + return matches + + +class TestCases(unittest.TestCase): + @classmethod + def setUpClass(cls): + """ + A bit of magic to keep the stats. + + Called the first time the class is run + """ + + cls.statsDict = {} + + @classmethod + def tearDownClass(cls): + """ + And a bit more magic to print the stats. + """ + + print("\n\n{0} STATS (Salted) {0}".format("-"*20)) + + print("Crack Single Salted : {0}".format(cls.statsDict["singleSalt"])) + print("Crack List of Salted: {0}".format(cls.statsDict["saltList"])) + + def setUp(self): + """ + Load the wordlist each time we run a test case + + Here we open the wordlist file, then store it as an array + This lets us reuse the list multiple times + """ + + with open("10-million-password-list-top-10000.txt") as fd: + #Store as an array + self.wordlist = fd.readlines() + + + def testSingle_ProperSalt(self): + """ + Check how long it takes to crack a single salted hash + """ + + t1 = time.time() + out = crackHash_Salt(self.wordlist, TARGETS[0]) + t2 = time.time() + self.assertEqual(out, "coffee") #check we were successful + self.statsDict["singleSalt"] = t2-t1 + + + def testList_ProperSalt(self): + """ + And Crack a list of salted passwords + """ + t1 = time.time() + out = crackList_Salt(self.wordlist, TARGETS) + t2 = time.time() + self.assertEqual(out, CORRECT_MATCHES) #check we were successful + self.statsDict["saltList"] = t2-t1 + + +