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 +7 -0
- data/lib/crypto-toolbox.rb +1 -0
- data/lib/crypto-toolbox/break_vigenere.rb +97 -0
- data/lib/crypto-toolbox/crypt_buffer.rb +147 -0
- data/lib/crypto-toolbox/key_filter.rb +41 -0
- data/lib/crypto-toolbox/spell_checker.rb +48 -0
- metadata +77 -0
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: []
|