crypto-toolbox 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dff3731a08e88906d365f475991967f6f29800ce
4
+ data.tar.gz: 622f4078f5a13226208ab4aff323b911bebb9d82
5
+ SHA512:
6
+ metadata.gz: 6601d0c7bff0a3904557992cefbd36935b103e666f3c7d4616066ce33c6715b17d657b59527ad9e69e5043f09878efe5f19779117cb1f11d7a66b30e420cf609
7
+ data.tar.gz: af42b570b9ef6eb7240f23134290d4b0854fc4d2c5a03911449bdd7acf22491d570532755501ac0da0a8fab7d89b40c4fa76cce81716909610ef87e0079f9ead
@@ -0,0 +1 @@
1
+ require 'crypto-toolbox/crypt_buffer.rb'
@@ -0,0 +1,97 @@
1
+ # coding: utf-8
2
+ require_relative './crypt_buffer.rb'
3
+ require_relative './key_filter.rb'
4
+ require 'shellwords'
5
+ require 'ffi/hunspell'
6
+
7
+ ##
8
+ # http://www.ulduzsoft.com/2015/03/breaking-the-vigenere-cipher/
9
+ # https://github.com/trekawek/vigenere/blob/master/vig.rb
10
+
11
+ def find_pattern(buf)
12
+ bitstring = buf.bits.map{|b| b[0]}.join("")
13
+ 1.upto([buf.bytes.length,62].min).map do |ksize|
14
+ parts = bitstring.scan(/.{#{ksize}}/)
15
+ if parts.uniq.length == 1
16
+ parts.first
17
+ else
18
+ nil
19
+ end
20
+ end.compact.first
21
+ end
22
+
23
+ input = ARGV[0] || "F96DE8C227A259C87EE1DA2AED57C93FE5DA36ED4EC87EF2C63AAE5B9A7EFFD673BE4ACF7BE8923CAB1ECE7AF2DA3DA44FCF7AE29235A24C963FF0DF3CA3599A70E5DA36BF1ECE77F8DC34BE129A6CF4D126BF5B9A7CFEDF3EB850D37CF0C63AA2509A76FF9227A55B9A6FE3D720A850D97AB1DD35ED5FCE6BF0D138A84CC931B1F121B44ECE70F6C032BD56C33FF9D320ED5CDF7AFF9226BE5BDE3FF7DD21ED56CF71F5C036A94D963FF8D473A351CE3FE5DA3CB84DDB71F5C17FED51DC3FE8D732BF4D963FF3C727ED4AC87EF5DB27A451D47EFD9230BF47CA6BFEC12ABE4ADF72E29224A84CDF3FF5D720A459D47AF59232A35A9A7AE7D33FB85FCE7AF5923AA31EDB3FF7D33ABF52C33FF0D673A551D93FFCD33DA35BC831B1F43CBF1EDF67F0DF23A15B963FE5DA36ED68D378F4DC36BF5B9A7AFFD121B44ECE76FEDC73BE5DD27AFCD773BA5FC93FE5DA3CB859D26BB1C63CED5CDF3FE2D730B84CDF3FF7DD21ED5ADF7CF0D636BE1EDB79E5D721ED57CE3FE6D320ED57D469F4DC27A85A963FF3C727ED49DF3FFFDD24ED55D470E69E73AC50DE3FE5DA3ABE1EDF67F4C030A44DDF3FF5D73EA250C96BE3D327A84D963FE5DA32B91ED36BB1D132A31ED87AB1D021A255DF71B1C436BF479A7AF0C13AA14794"
24
+
25
+ buf = CryptBuffer.new(input)
26
+ result = find_pattern(buf)
27
+
28
+ if result.nil?
29
+ $stderr.puts "failed to find keylength by ASCII-8-Bit anlysis"
30
+ exit(1)
31
+ end
32
+
33
+ keylen = result.length
34
+ puts "Found recurring key pattern: #{result}"
35
+ puts "Detected key length: #{keylen}"
36
+
37
+ candidate_map ={}
38
+ (0..(keylen-1)).each do |key_byte|
39
+
40
+ nth_stream = (key_byte).step(buf.bytes.length() -1, keylen).map{|i| buf.bytes[i]}
41
+ smart_buf = CryptBuffer.new(nth_stream)
42
+
43
+ candidate_map[key_byte]=[]
44
+ 1.upto(255).each do |possible_key_value|
45
+ if smart_buf.xor_all_with(possible_key_value).bytes.all?{|e| e > 31 && e < 123 && e != 60 && e !=64}
46
+ #puts "YES: " + smart_buf.xor_all_with(possible_key_value).to_s
47
+ candidate_map[key_byte] << possible_key_value
48
+ else
49
+ #puts "NO: " + smart_buf.xor_all_with(possible_key_value).to_s
50
+ end
51
+ end
52
+ end
53
+
54
+
55
+
56
+ head,*tail = candidate_map.map{|k,v|v}
57
+
58
+ puts "Amount of candidate keys: #{candidate_map.map{|k,v| v.length}.reduce(&:*)}. Starting Permutation (RAM intensive)"
59
+
60
+ combinations = head.product(*tail)
61
+ # make sure all permutations are still according to the bytes per position map
62
+ #x = combinations.select do |arr|
63
+ # #binding.pry
64
+ # arr.map.with_index{|e,i| candidate_map[i].include?(e) }.all?{|e| e ==true}
65
+ #end
66
+
67
+ # printout for debugging. (Manual analysis of the characters)
68
+ puts "======= Candidate decryption result of first #{keylen} bytes ======="
69
+ (0..keylen-1).each do|i|
70
+ candidate_map[i].each do |byte|
71
+ print CryptBuffer.new(buf.bytes[i,keylen]).xor(byte).to_s + " "
72
+ end
73
+ print "\n"
74
+ end
75
+ puts "====================================================================="
76
+
77
+
78
+ result = KeySearch::Filter::AsciiPlain.new(combinations,buf).filter
79
+ unless result.empty?
80
+ puts "[Success] Found valid result(s)"
81
+ result.each do |r|
82
+ puts r.xor(buf).str
83
+ end
84
+ end
85
+
86
+ =begin
87
+ NOTE: we may at digram and trigram support?
88
+ #trigram="the "
89
+ #x = CryptBuffer.new(trigram)
90
+ =end
91
+
92
+
93
+
94
+
95
+
96
+
97
+
@@ -0,0 +1,147 @@
1
+ require 'rubygems'
2
+ require 'pry'
3
+ require 'pp'
4
+ require 'aes'
5
+
6
+ class CryptBuffer
7
+ attr_accessor :bytes
8
+
9
+ include Enumerable
10
+
11
+ def initialize(input)
12
+ @bytes = bytes_from_any(input)
13
+ end
14
+
15
+ def each(&block)
16
+ @bytes.each(&block)
17
+ end
18
+
19
+ alias_method :b, :bytes
20
+
21
+ def hex
22
+ bytes2hex(bytes).upcase
23
+ end
24
+ alias_method :h, :hex
25
+
26
+ def chars
27
+ map{|b| b.to_i.chr}
28
+ end
29
+ alias_method :c, :chars
30
+
31
+ def str
32
+ chars.join
33
+ end
34
+ alias_method :s, :str
35
+
36
+ def bits
37
+ map{|b| "%08d" % b.to_s(2) }
38
+ end
39
+
40
+ def xor(input,expand_input: false)
41
+ if expand_input
42
+ xor_all_with(input)
43
+ else
44
+ xor_bytes(bytes_from_any(input))
45
+ end
46
+ end
47
+
48
+ def xor_all_with(input)
49
+ expanded = expand_bytes(bytes_from_any(input),self.bytes.length)
50
+ xor_bytes(expanded)
51
+ end
52
+
53
+ def pp
54
+ puts pretty_hexstr
55
+ end
56
+
57
+ def xor_space
58
+ xor(0x20,expand_input: true)
59
+ end
60
+
61
+ def ==(other)
62
+ bytes == bytes_from_any(other)
63
+ end
64
+
65
+ def to_s
66
+ str
67
+ end
68
+
69
+ private
70
+ def expand_bytes(input,total)
71
+ if input.length >= total
72
+ input
73
+ else
74
+ n = total / input.length
75
+ rest = total % input.length
76
+
77
+ # expand the input to the full length of the internal data
78
+ (input * n) + input[0,rest]
79
+ end
80
+ end
81
+ def bytes_from_any(input)
82
+ case input
83
+ when Array
84
+ input
85
+ when String
86
+ if input.match(/^(0x)?[0-9a-fA-F]+$/).nil?
87
+ str2bytes(input)
88
+ else
89
+ hex2bytes(normalize_hex(input))
90
+ end
91
+ when CryptBuffer
92
+ input.b
93
+ when Fixnum
94
+ # integers as strings dont have a 0x prefix
95
+ if input.to_s(16).match(/^[0-9a-fA-F]+$/)
96
+ # assume 0x prefixed integer
97
+ hex2bytes(normalize_hex(input.to_s(16)))
98
+ else
99
+ # regular number
100
+ [input].pack('C*').bytes
101
+ end
102
+ else
103
+ raise "Unsupported input: #{input.inspect} of class #{input.class}"
104
+ end
105
+ end
106
+
107
+ def normalize_hex(str)
108
+ tmp = (str.length == 1) ? "0#{str}" : "#{str}"
109
+ tmp.gsub(/(^0x|\s)/,"").upcase
110
+ end
111
+
112
+ def strip_hex_prefix(hex)
113
+ raise "remove 0x from hexinput"
114
+ end
115
+
116
+ def xor_bytes(byt)
117
+ len = [self.bytes.size,byt.size].min
118
+ result = self.bytes[0...len].map.with_index{|b,i| b ^ byt[i] } + self.bytes[len,self.bytes.length - len]
119
+ CryptBuffer.new(result)
120
+ end
121
+
122
+ def xor_hex(hex)
123
+ x = hex2bytes(hex)
124
+ xor_bytes(x)
125
+ end
126
+
127
+ def hex2bytes(hexstr)
128
+ hexstr.scan(/../).map{|h| h.to_i(16) }
129
+ end
130
+
131
+ def str2bytes(str)
132
+ str.bytes.to_a
133
+ end
134
+
135
+ def bytes2hex(bytes)
136
+ bytes.map{|b| b.to_s(16)}.map{|hs| hs.length == 1 ? "0#{hs}" : hs }.join
137
+ end
138
+
139
+ def pretty_hexstr
140
+ str = h.scan(/.{2}/).to_a.join(" ")
141
+ "0x#{h.upcase} (#{str.upcase})"
142
+ end
143
+ end
144
+
145
+ def CryptBuffer(input)
146
+ CryptBuffer.new(input)
147
+ end
@@ -0,0 +1,41 @@
1
+ require_relative './crypt_buffer.rb'
2
+ require_relative './spell_checker.rb'
3
+ require 'pry'
4
+
5
+ module KeySearch
6
+ module Filter
7
+ class AsciiPlain
8
+ def initialize(keys,ciphertext,dict_lang="en_GB")
9
+ @keys = keys
10
+ @c = @ciphertext = ciphertext
11
+ @keylen = keys.first.length
12
+ @dict = FFI::Hunspell.dict(dict_lang)
13
+ end
14
+
15
+
16
+ def filter
17
+ # how often is the key repeated
18
+ reps = @c.bytes.length / @keylen
19
+ result =[]
20
+ spell_checker = SpellChecker.new("en_GB")
21
+
22
+ @keys.each_with_index do |key,i| # i is used as a simple counter only !
23
+ test = CryptBuffer.new(@c.bytes[0,@keylen]).xor(key).str
24
+ repkey = CryptBuffer.new((key*reps) + key[0,(@c.bytes.length % reps).to_i])
25
+ str = @c.xor(repkey).to_s
26
+
27
+ if spell_checker.human_language?(str)
28
+ result << repkey
29
+ break
30
+ else
31
+ if (i % 50000).zero?
32
+ puts "[Progress] #{i}/#{@keys.length} (#{(i.to_f/@keys.length*100).round(4)}%)"
33
+ end
34
+ end
35
+ end
36
+ return result
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,48 @@
1
+ require 'ffi/hunspell'
2
+ class SpellChecker
3
+ def initialize(dict_lang="en_GB")
4
+ @dict = FFI::Hunspell.dict(dict_lang)
5
+ end
6
+ =begin
7
+ NOTE: About spelling error rates and language detection:
8
+
9
+ missing punctuation support may lead to > 2% errors on valid texts, thus we use a high value .
10
+ invalid decryptions tend to have spell error rates > 70
11
+ Some statistics about it:
12
+ > summary(invalids)
13
+ Min. 1st Qu. Median Mean 3rd Qu. Max.
14
+ 0.6000 1.0000 1.0000 0.9878 1.0000 1.0000
15
+ > summary(cut(invalids,10))
16
+ (0.6,0.64] (0.64,0.68] (0.68,0.72] (0.72,0.76] (0.76,0.8] (0.8,0.84]
17
+ 8 13 9 534 1319 2809
18
+ (0.84,0.88] (0.88,0.92] (0.92,0.96] (0.96,1]
19
+ 10581 46598 198477 1440651
20
+ =end
21
+ def known_words(str)
22
+ words = str.split(" ").select{|w| @dict.check?(w) }
23
+ end
24
+
25
+ def suggest(str)
26
+ @dict.suggest(str)
27
+ end
28
+
29
+ def human_language?(str)
30
+ words = str.split(" ").length
31
+ errors = str.split(" ").map{|e| @dict.check?(e) }.count{|e| e == false}
32
+ # using shell instead of hunspell ffi causes lots of escaping errors, even with shellwords.escape
33
+ #errors = Float(`echo '#{Shellwords.escape(str)}' |hunspell -l |wc -l `.split.first)
34
+
35
+ error_rate = errors.to_f/words
36
+
37
+ $stderr.puts error_rate.round(4) if ENV["CRYPTO_TOOBOX_PRINT_ERROR_RATES"]
38
+
39
+ if error_rate < 0.5
40
+ puts "[Success] Found valid result (spell error_rate: #{error_rate*100}% is below threshold: 20%)"
41
+ return true
42
+ else
43
+ return false
44
+ end
45
+ end
46
+
47
+
48
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crypto-toolbox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Dennis Sivia
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aes
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ffi-hunspell
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.3'
41
+ description: The Crypto Toolbox offers some tools to easily work with cryptographic
42
+ primitives like arrays of Bytes or hextrings
43
+ email: dev@d-coded.de
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - lib/crypto-toolbox.rb
49
+ - lib/crypto-toolbox/break_vigenere.rb
50
+ - lib/crypto-toolbox/crypt_buffer.rb
51
+ - lib/crypto-toolbox/key_filter.rb
52
+ - lib/crypto-toolbox/spell_checker.rb
53
+ homepage: https://github.com/scepticulous/crypto-toolbox
54
+ licenses:
55
+ - GPLv3
56
+ metadata: {}
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 2.4.6
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: Toolbox for crypto analysis
77
+ test_files: []