crypto-toolbox 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []