├── README.md └── bozocrack.rb /README.md: -------------------------------------------------------------------------------- 1 | # BozoCrack 2 | BozoCrack is a depressingly effective MD5 password hash cracker with almost zero CPU/GPU load. Instead of rainbow tables, dictionaries, or brute force, BozoCrack simply *finds* the plaintext password. Specifically, it googles the MD5 hash and hopes the plaintext appears somewhere on the first page of results. 3 | 4 | It works way better than it ever should. 5 | 6 | 7 | ## How? 8 | Basic usage: 9 | 10 | $ ruby bozocrack.rb my_md5_hashes.txt 11 | 12 | The input file has no specified format. BozoCrack automatically picks up strings that look like MD5 hashes. A single line shouldn't contain more than one hash. 13 | 14 | Example with output: 15 | 16 | $ ruby bozocrack.rb example.txt 17 | Loaded 5 unique hashes 18 | fcf1eed8596699624167416a1e7e122e:octopus 19 | bed128365216c019988915ed3add75fb:passw0rd 20 | d0763edaa9d9bd2a9516280e9044d885:monkey 21 | dfd8c10c1b9b58c8bf102225ae3be9eb:12081977 22 | ede6b50e7b5826fe48fc1f0fe772c48f:1q2w3e4r5t6y 23 | 24 | 25 | ## Why? 26 | To show just how bad an idea it is to use plain MD5 as a password hashing mechanism. Honestly, if the passwords can be cracked with *this software*, there are no excuses. 27 | 28 | 29 | ## Who? 30 | BozoCrack was written by [Juuso Salonen](http://twitter.com/juusosalonen), the guy behind [Radio Silence](http://radiosilenceapp.com) and [Private Eye](http://radiosilenceapp.com/private-eye). 31 | 32 | 33 | ## License 34 | Do whatever you wish. Public domain, yadda yadda. 35 | -------------------------------------------------------------------------------- /bozocrack.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'digest/md5' 4 | require 'net/http' 5 | 6 | class BozoCrack 7 | 8 | def initialize(filename) 9 | @hashes = Array.new 10 | @cache = Hash.new 11 | 12 | File.new(filename).each_line do |line| 13 | if m = line.chomp.match(/\b([a-fA-F0-9]{32})\b/) 14 | @hashes << m[1] 15 | end 16 | end 17 | @hashes.uniq! 18 | puts "Loaded #{@hashes.count} unique hashes" 19 | 20 | load_cache 21 | end 22 | 23 | def crack 24 | @hashes.each do |hash| 25 | if plaintext = @cache[hash] 26 | puts "#{hash}:#{plaintext}" 27 | next 28 | end 29 | if plaintext = crack_single_hash(hash) 30 | puts "#{hash}:#{plaintext}" 31 | append_to_cache(hash, plaintext) 32 | end 33 | sleep 1 34 | end 35 | end 36 | 37 | private 38 | 39 | def crack_single_hash(hash) 40 | response = Net::HTTP.get URI("http://www.google.com/search?q=#{hash}") 41 | wordlist = response.split(/\s+/) 42 | if plaintext = dictionary_attack(hash, wordlist) 43 | return plaintext 44 | end 45 | nil 46 | end 47 | 48 | def dictionary_attack(hash, wordlist) 49 | wordlist.each do |word| 50 | if Digest::MD5.hexdigest(word) == hash.downcase 51 | return word 52 | end 53 | end 54 | nil 55 | end 56 | 57 | def load_cache(filename = "cache") 58 | if File.file? filename 59 | File.new(filename).each_line do |line| 60 | if m = line.chomp.match(/^([a-fA-F0-9]{32}):(.*)$/) 61 | @cache[m[1]] = m[2] 62 | end 63 | end 64 | end 65 | end 66 | 67 | def append_to_cache(hash, plaintext, filename = "cache") 68 | File.open(filename, "a") do |file| 69 | file.write "#{hash}:#{plaintext}\n" 70 | end 71 | end 72 | 73 | end 74 | 75 | if ARGV.size == 1 76 | BozoCrack.new(ARGV[0]).crack 77 | else 78 | puts "Usage example: ruby bozocrack.rb file_with_md5_hashes.txt" 79 | end 80 | --------------------------------------------------------------------------------