crypto-toolbox 0.2.6 → 0.2.7
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 +4 -4
- data/bin/break-ecb-string-appender +21 -0
- data/lib/crypto-toolbox.rb +9 -1
- data/lib/crypto-toolbox/analyzers/ecb_string_appender.rb +207 -0
- data/lib/crypto-toolbox/ciphers/aes.rb +65 -41
- data/lib/crypto-toolbox/crypt_buffer/concerns/convertable.rb +3 -0
- data/lib/crypto-toolbox/matasano/sets/set1.rb +52 -0
- data/lib/crypto-toolbox/matasano/sets/set2.rb +62 -0
- data/lib/crypto-toolbox/matasano/solver.rb +9 -0
- data/lib/crypto-toolbox/oracles/user_profile_encryption_oracle.rb +28 -0
- data/lib/crypto-toolbox/utils/ecb_detector.rb +21 -8
- data/lib/crypto-toolbox/utils/ecb_oracle.rb +65 -0
- data/lib/crypto-toolbox/utils/reporting/console.rb +9 -2
- metadata +11 -3
- data/lib/crypto-toolbox/crypto_challanges/solver.rb +0 -59
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b760901ea5c2ae1dea432a632e4d9d48e3bb58f
|
4
|
+
data.tar.gz: 866e4c2c574a4ad9f8a43891574087e74022d55f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90b498603ff15a35d88d7c37295f9d45146ba69b58e3ca3c0348a45d40a61d053e9ea962d8388fa8e31e9879bdce5516857ca696c00e86217011c08585246a40
|
7
|
+
data.tar.gz: 4453d964ccd6f3c10dbf39102ee1cadabf1273d2e7cd0ac3c736bf7eeea0081448a27d716eb56edc19c901134622c0b4743ae8944f1dd96426b7e3feade1e794
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'crypto-toolbox'
|
4
|
+
require 'pry'
|
5
|
+
require 'stackprof'
|
6
|
+
|
7
|
+
|
8
|
+
suffix = plaintext = Base64.decode64("Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkgaGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBqdXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUgYnkK")
|
9
|
+
key = "my-secret-key"
|
10
|
+
mode = :ecb
|
11
|
+
oracle = Utils::EcbOracle.new(static_key: key,static_mode: mode,static_suffix: suffix,prepend: false, append: true)
|
12
|
+
|
13
|
+
|
14
|
+
StackProf.run(mode: :cpu, out: 'tmp/stackprof-ecb-decryption.dump') do
|
15
|
+
result = Matasano::Solver.new.solve12(oracle)
|
16
|
+
puts "\nBroke ECB! Result: \n#{result}\n" if result
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
data/lib/crypto-toolbox.rb
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
require 'crypto-toolbox/utils/reporting/console.rb'
|
3
3
|
require 'crypto-toolbox/utils/hamming_distance_filter.rb'
|
4
4
|
require 'crypto-toolbox/utils/ecb_detector.rb'
|
5
|
+
require 'crypto-toolbox/utils/ecb_oracle.rb'
|
6
|
+
|
7
|
+
require 'crypto-toolbox/oracles/user_profile_encryption_oracle.rb'
|
8
|
+
|
9
|
+
|
5
10
|
|
6
11
|
require 'crypto-toolbox/crypt_buffer_input_converter.rb'
|
7
12
|
require 'crypto-toolbox/crypt_buffer.rb'
|
@@ -12,9 +17,12 @@ require 'crypto-toolbox/analyzers/utils/ascii_language_detector.rb'
|
|
12
17
|
require 'crypto-toolbox/analyzers/utils/spell_checker.rb'
|
13
18
|
require 'crypto-toolbox/analyzers/utils/human_language_detector.rb'
|
14
19
|
|
20
|
+
|
15
21
|
require 'crypto-toolbox/analyzers/padding_oracle.rb'
|
16
22
|
require 'crypto-toolbox/analyzers/cbc_mac.rb'
|
17
23
|
require 'crypto-toolbox/analyzers/vigenere_xor.rb'
|
24
|
+
require 'crypto-toolbox/analyzers/ecb_string_appender.rb'
|
25
|
+
|
18
26
|
|
19
27
|
require 'crypto-toolbox/ciphers/aes.rb'
|
20
28
|
require 'crypto-toolbox/ciphers/caesar.rb'
|
@@ -23,4 +31,4 @@ require 'crypto-toolbox/ciphers/rot13.rb'
|
|
23
31
|
require 'crypto-toolbox/forgers/stream_ciphers/forge_generator.rb'
|
24
32
|
|
25
33
|
|
26
|
-
require 'crypto-toolbox/
|
34
|
+
require 'crypto-toolbox/matasano/solver.rb'
|
@@ -0,0 +1,207 @@
|
|
1
|
+
module Analyzers
|
2
|
+
|
3
|
+
# Public: This analyzer attacks oracles that append any unknown string to
|
4
|
+
# its input messages and decrypts the appended string.
|
5
|
+
# In practice Email autoresponders sometimes append a data to a given input.
|
6
|
+
# Thus this analyzer can break any ecb encryption that works this way
|
7
|
+
#
|
8
|
+
# it is also capable of detecting prefixes created by the oracle and pad them
|
9
|
+
# to correctly analyze the target message
|
10
|
+
#
|
11
|
+
|
12
|
+
class EcbStringAppender
|
13
|
+
|
14
|
+
class DuplicateDecryptionDictionaryEntry < RuntimeError; end
|
15
|
+
|
16
|
+
DUMMY = "A".freeze
|
17
|
+
PREFIX_PAD_DUMMY="P".freeze
|
18
|
+
MAX_KNOWN_BLOCK_LENGTH = 64 # 512 Bit block length
|
19
|
+
attr_reader :oracle
|
20
|
+
|
21
|
+
include ::Utils::Reporting::Console
|
22
|
+
|
23
|
+
def initialize(oracle)
|
24
|
+
@oracle = oracle
|
25
|
+
detect_block_size!
|
26
|
+
raise "None-ECB oracle" unless ::Utils::EcbDetector.new.is_ecb?(@oracle.encipher(DUMMY * (block_size * 6)))
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def analyze
|
31
|
+
analyze_oracle!
|
32
|
+
|
33
|
+
suffix_block_ids.with_object("") do |block_id, hits|
|
34
|
+
each_block_position do |pos|
|
35
|
+
# stop as soon as we have all the bytes that are appended ( without and ciphermode padding )
|
36
|
+
break if hits.length >= real_suffix_length
|
37
|
+
hits << attempt_match(hits, block_id, pos)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# first we pre calculate all the data we can deduce from the oracle's behaviour
|
45
|
+
def analyze_oracle!
|
46
|
+
detect_prefix
|
47
|
+
detect_suffix
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# left align our attack vector
|
52
|
+
# this means full any left randomness to a full block
|
53
|
+
# example
|
54
|
+
#
|
55
|
+
# <random[0-16]> || <input-blocks[any]> || <target-data[any]>
|
56
|
+
def detect_prefix
|
57
|
+
# 0 * "X" == "" for no prefix
|
58
|
+
@prefix_pad = PREFIX_PAD_DUMMY * calculate_prefix_length
|
59
|
+
@prefix_blocks = @prefix_pad.empty? ? 0 : 1
|
60
|
+
@prefix_bytes = @prefix_blocks * block_size
|
61
|
+
jot("detected oracle prefix with length: #{@prefix_pad.length} (#{@prefix_blocks} blocks)",debug: true) unless @prefix_pad.empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
def detect_block_size!
|
65
|
+
@block_size = calculate_block_size
|
66
|
+
end
|
67
|
+
|
68
|
+
def detect_suffix
|
69
|
+
@aligned_suffix_length ||= calculate_aligned_suffix_length
|
70
|
+
@suffix_blocks ||= aligned_suffix_length / block_size
|
71
|
+
@real_suffix_length ||= calculate_real_suffix_length(oracle,block_size,aligned_suffix_length)
|
72
|
+
end
|
73
|
+
|
74
|
+
def block_size
|
75
|
+
@block_size
|
76
|
+
end
|
77
|
+
|
78
|
+
def aligned_suffix_length
|
79
|
+
@aligned_suffix_length
|
80
|
+
end
|
81
|
+
|
82
|
+
def suffix_blocks
|
83
|
+
@suffix_blocks
|
84
|
+
end
|
85
|
+
|
86
|
+
def real_suffix_length
|
87
|
+
@real_suffix_length
|
88
|
+
end
|
89
|
+
|
90
|
+
def each_block_position(&block)
|
91
|
+
1.upto(block_size,&block)
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
def attempt_match(hits, block_id, pos)
|
97
|
+
msg = @prefix_pad + (DUMMY * (block_size - pos))
|
98
|
+
relevant_bytes = block_size * (block_id.succ)
|
99
|
+
|
100
|
+
# build a dictionary for the current dummy + all hits
|
101
|
+
# resulting in entries with block_size -1 length
|
102
|
+
dict = assemble_dict(oracle,msg + hits,relevant_bytes)
|
103
|
+
result = @oracle.encipher(msg)[@prefix_bytes,relevant_bytes] # skip all prefix blocks
|
104
|
+
|
105
|
+
dict[result].tap do |match|
|
106
|
+
jot(match,debug: true,raw: true) unless match.nil?
|
107
|
+
if match.nil?
|
108
|
+
raise "Could not find dictonary entry for block #{block_id}, pos: #{pos}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# calculate the block size by detecting the growth
|
115
|
+
# of the resulting ciphertext by sending messages
|
116
|
+
# which length increases by one until a change occurs
|
117
|
+
def calculate_block_size
|
118
|
+
char_amount = 1
|
119
|
+
base_length = @oracle.encipher(DUMMY * char_amount).length
|
120
|
+
result = nil
|
121
|
+
(1..MAX_KNOWN_BLOCK_LENGTH).each do |length|
|
122
|
+
new_length = @oracle.encipher(DUMMY * char_amount).length
|
123
|
+
if new_length > base_length
|
124
|
+
result = new_length - base_length
|
125
|
+
break
|
126
|
+
end
|
127
|
+
char_amount += 1
|
128
|
+
end
|
129
|
+
result
|
130
|
+
end
|
131
|
+
|
132
|
+
# in case of a prefix some bytes of your 2 duplicate / redundant chars
|
133
|
+
# will be part of the first block, thus need to add enough extra chars
|
134
|
+
# to fill the first block containinig the random + unknown prefix with
|
135
|
+
# dummy chars to align it to the block length.
|
136
|
+
def calculate_prefix_length
|
137
|
+
duplications = 2
|
138
|
+
|
139
|
+
(0..(block_size() -1)).each do |pad_length|
|
140
|
+
# construct a message like this:
|
141
|
+
# 1 <unknown-prefix>|| prefix_pad * DUMMY
|
142
|
+
# 2 DUMMY * (block_size)
|
143
|
+
# 3 DUMMY * (block_size)
|
144
|
+
# 4 - (n-1) Target Message
|
145
|
+
# 5: target_end + pkcs#7 padding
|
146
|
+
malicious_msg = (PREFIX_PAD_DUMMY * pad_length) + (DUMMY * (block_size * duplications))
|
147
|
+
ciphertext = @oracle.encipher(malicious_msg)
|
148
|
+
|
149
|
+
return pad_length if block_is_left_aligned?(ciphertext,duplications)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Check whether we need to pad any oracle prefix.
|
154
|
+
# For example: if the oracle prepends 7 bytes to all messages
|
155
|
+
# we have to add block_size - 7 bytes to "left-align" our messages
|
156
|
+
def block_is_left_aligned?(ciphertext,redundant_test_blocks)
|
157
|
+
total_blocks = ciphertext.length / block_size
|
158
|
+
uniq_blocks = CryptBuffer(ciphertext).chunks_of(block_size).map(&:bytes).uniq.length
|
159
|
+
|
160
|
+
(total_blocks - uniq_blocks ) == (redundant_test_blocks -1)
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
def suffix_block_ids
|
165
|
+
0.upto(suffix_blocks.pred)
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
|
170
|
+
# prefix_pad can be empty || then prefix_blocks is 0
|
171
|
+
# this results in the simple case of no prefix and no substraction
|
172
|
+
def calculate_aligned_suffix_length
|
173
|
+
# substract the prefix block if given
|
174
|
+
@oracle.encipher(@prefix_pad + "").length() - (@prefix_blocks * block_size)
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
def calculate_real_suffix_length(oracle,block_size,minimum_length)
|
179
|
+
# map has a smell, that is does not abort and thus create unnecessary
|
180
|
+
# requests... while, count++; break; smells even worse
|
181
|
+
(0..block_size).each do |i|
|
182
|
+
total_length = oracle.encipher(@prefix_pad + (DUMMY * i) ).length
|
183
|
+
result = total_length - (@prefix_blocks * block_size )
|
184
|
+
if result > minimum_length
|
185
|
+
return minimum_length - (i-1)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def assemble_dict(oracle,prefix,relevant_bytes)
|
191
|
+
# we could also iterate over all bytes, but as long as we decrypt plain english we can
|
192
|
+
# reduce the number of iterations by just checking printable chars
|
193
|
+
# (0..255).map(&:chr).each_with_object({}) do |char,hsh|
|
194
|
+
|
195
|
+
# cache for performance
|
196
|
+
@chars ||= Analyzers::Utils::AsciiLanguageDetector.new.ascii_lingual_chars.map(&:chr)
|
197
|
+
|
198
|
+
@chars.each_with_object({}) do |char,hsh|
|
199
|
+
msg = prefix + char
|
200
|
+
ciphertext = oracle.encipher(msg)[@prefix_bytes,relevant_bytes].freeze
|
201
|
+
raise DuplicateDecryptionDictionaryEntry,"ciphertext #{ciphertext} is alredy in the dictionary" if hsh.has_key?(ciphertext)
|
202
|
+
hsh[ciphertext] = char # skip prefix blocks during [] access
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
@@ -1,64 +1,88 @@
|
|
1
1
|
module Ciphers
|
2
2
|
class Aes
|
3
3
|
|
4
|
-
def initialize(
|
5
|
-
@
|
4
|
+
def initialize(key_size: 128)
|
5
|
+
@key_size = key_size
|
6
|
+
@block_size_bits = 128
|
7
|
+
@block_size_bytes = 16
|
6
8
|
end
|
7
9
|
|
10
|
+
# NOTE convert ECB encryption to AES gem or both to openssl
|
8
11
|
def decipher_ecb(key,input)
|
9
|
-
@
|
10
|
-
@cipher.key = key
|
11
|
-
(@cipher.update(input) + @cipher.final)
|
12
|
+
decipher_ecb_blockwise(CryptBuffer(key),CryptBuffer(input).chunks_of(@block_size_bytes))
|
12
13
|
end
|
13
14
|
|
14
15
|
def encipher_ecb(key,input)
|
15
|
-
@
|
16
|
-
|
16
|
+
encipher_ecb_blockwise(CryptBuffer(key),CryptBuffer(input).chunks_of(@block_size_bytes))
|
17
|
+
end
|
18
|
+
|
19
|
+
def encipher_cbc(key_str,input_str,iv: nil)
|
20
|
+
unicipher_cbc(:encipher,key_str,input_str,iv)
|
21
|
+
end
|
22
|
+
|
23
|
+
def decipher_cbc(key_str,input_str,iv: nil)
|
24
|
+
unicipher_cbc(:decipher,key_str,input_str,iv)
|
25
|
+
end
|
26
|
+
|
17
27
|
|
18
|
-
|
28
|
+
private
|
29
|
+
|
30
|
+
def encipher_ecb_blockwise(key,blocks)
|
31
|
+
blocks.map{|block| encipher_ecb_block(key,block) }.join
|
32
|
+
end
|
33
|
+
|
34
|
+
def encipher_ecb_block(key,block)
|
35
|
+
need_padding = block.length < @block_size_bytes
|
36
|
+
_,out = AES.encrypt(block.str, key.hex, {:format => :plain,:padding => need_padding,:cipher => "AES-#{@key_size}-ECB"})
|
37
|
+
out
|
38
|
+
end
|
39
|
+
|
40
|
+
def decipher_ecb_blockwise(key,blocks)
|
41
|
+
blocks.map{|block| decipher_ecb_block(key,block) }.join
|
42
|
+
end
|
43
|
+
|
44
|
+
def decipher_ecb_block(key,block)
|
45
|
+
need_padding = block.length < @block_size_bytes
|
46
|
+
AES.decrypt(["",block.str], key.hex, {:format => :plain,:padding => need_padding,:cipher => "AES-#{@key_size}-ECB"})
|
19
47
|
end
|
20
48
|
|
21
|
-
|
22
|
-
|
49
|
+
# this method is used for encipher and decipher since most of the code is identical
|
50
|
+
# only the value of the previous block and the internal ecb method differs
|
51
|
+
def unicipher_cbc(direction,key_str,input_str,iv)
|
52
|
+
method="#{direction.to_s}_cbc_block"
|
53
|
+
blocks = CryptBuffer(input_str).chunks_of(@block_size_bytes)
|
23
54
|
iv ||= blocks.shift.str
|
24
|
-
|
25
|
-
|
55
|
+
key = CryptBuffer(key_str).hex
|
56
|
+
|
57
|
+
prev_block=iv.to_crypt_buffer
|
26
58
|
|
27
|
-
|
28
|
-
|
59
|
+
blocks.map do |block|
|
60
|
+
ctext_block = send(method,key,block,prev_block) #encipher_cbc_block(key,block,prev_block)
|
61
|
+
if direction == :encipher
|
62
|
+
prev_block = ctext_block
|
63
|
+
else
|
64
|
+
prev_block = block
|
65
|
+
end
|
29
66
|
|
30
|
-
|
31
|
-
|
32
|
-
xor_input = ecb_block
|
33
|
-
ecb_block.str
|
34
|
-
end.join
|
35
|
-
(data).to_crypt_buffer
|
67
|
+
ctext_block.str
|
68
|
+
end.join.to_crypt_buffer
|
36
69
|
end
|
37
70
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
k = CryptBuffer(key).hex
|
42
|
-
xor_input=iv.to_crypt_buffer
|
71
|
+
def encipher_cbc_block(key,block,prev_block)
|
72
|
+
xored = block ^ prev_block
|
73
|
+
need_padding = block.length != @block_size_bytes
|
43
74
|
|
44
|
-
|
45
|
-
|
46
|
-
ecb_block = CryptBuffer(out)
|
47
|
-
result = ecb_block ^ xor_input
|
48
|
-
xor_input = block
|
49
|
-
result.str
|
50
|
-
end.join
|
51
|
-
(data).to_crypt_buffer
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
def encipher_ecb_blockwise(crypter,blocks)
|
56
|
-
blocks.map{|block| encipher_ecb_block(block) }.join
|
75
|
+
_,out = AES.encrypt(xored.str, key, {:format => :plain,:padding => need_padding,:cipher => "AES-#{@key_size}-ECB",:iv => prev_block.str })
|
76
|
+
ecb_block = CryptBuffer(out)
|
57
77
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
78
|
+
def decipher_cbc_block(key,block,prev_block)
|
79
|
+
need_padding = block.length != @block_size_bytes
|
80
|
+
|
81
|
+
out = ::AES.decrypt([prev_block.str,block.str] , key, {:format => :plain,:padding => need_padding,:cipher => "AES-#{@key_size}-ECB",:iv => prev_block.str })
|
82
|
+
ecb_block = CryptBuffer(out)
|
83
|
+
ecb_block ^ prev_block
|
61
84
|
end
|
85
|
+
|
62
86
|
end
|
63
87
|
end
|
64
88
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
module Matasano
|
3
|
+
module Sets
|
4
|
+
module Set1
|
5
|
+
def solve1(input)
|
6
|
+
CryptBuffer.from_hex(input).base64
|
7
|
+
end
|
8
|
+
|
9
|
+
def solve2(c1,c2)
|
10
|
+
(CryptBuffer.from_hex(c1) ^ CryptBuffer.from_hex(c2)).hex.downcase
|
11
|
+
end
|
12
|
+
|
13
|
+
def solve3(input)
|
14
|
+
candidates = (1..256).map{ |guess| CryptBuffer.from_hex(input).xor_all_with(guess) }
|
15
|
+
detector = Analyzers::Utils::HumanLanguageDetector.new
|
16
|
+
|
17
|
+
detector.human_language_entries(candidates).first.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
# challange:
|
21
|
+
# One of the 60-character strings in this file has been encrypted by single-character XOR.
|
22
|
+
def solve4(hexstrings)
|
23
|
+
detector = Analyzers::Utils::HumanLanguageDetector.new
|
24
|
+
result = hexstrings.map{|h| CryptBuffer.from_hex(h)}.map.with_index do |c,i|
|
25
|
+
candidates = (1..256).map{ |guess| c.xor_all_with(guess) }
|
26
|
+
matches = detector.human_language_entries(candidates)
|
27
|
+
|
28
|
+
matches.empty? ? nil : matches
|
29
|
+
end
|
30
|
+
result.flatten.compact.map(&:str).first
|
31
|
+
end
|
32
|
+
|
33
|
+
def solve5(input,key)
|
34
|
+
CryptBuffer(input).xor(key,expand_input: true).hex
|
35
|
+
end
|
36
|
+
|
37
|
+
def solve6(input)
|
38
|
+
buffer = CryptBuffer.from_base64(input)
|
39
|
+
Analyzers::VigenereXor.new.analyze(buffer.hex,Analyzers::VigenereXor::HammingDistanceKeyLengthFinder.new)
|
40
|
+
end
|
41
|
+
|
42
|
+
def solve7(input,key)
|
43
|
+
data = CryptBuffer.from_base64(input).str
|
44
|
+
Ciphers::Aes.new.decipher_ecb(key,data)
|
45
|
+
end
|
46
|
+
|
47
|
+
def solve8(ciphers)
|
48
|
+
Utils::EcbDetector.new.detect(ciphers).first
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
|
2
|
+
module Matasano
|
3
|
+
module Sets
|
4
|
+
module Set2
|
5
|
+
def solve9(input)
|
6
|
+
CryptBuffer(input).pad(4).str
|
7
|
+
end
|
8
|
+
|
9
|
+
def solve10(key,input,iv)
|
10
|
+
data = CryptBuffer.from_base64(input).str
|
11
|
+
Ciphers::Aes.new.decipher_cbc(key,data,iv: iv).str
|
12
|
+
end
|
13
|
+
|
14
|
+
def solve11(oracle,plaintext)
|
15
|
+
puts "see tests"
|
16
|
+
end
|
17
|
+
|
18
|
+
def solve12(oracle)
|
19
|
+
Analyzers::EcbStringAppender.new(oracle).analyze
|
20
|
+
end
|
21
|
+
|
22
|
+
def solve13(key = SecureRandom.random_bytes(16) )
|
23
|
+
oracle = CryptoToolbox::Oracles::UserProfileEncryptionOracle.new(key)
|
24
|
+
|
25
|
+
# blocks:
|
26
|
+
# 1) email=[...]@xy.
|
27
|
+
# 2) com&uid=10&role=
|
28
|
+
# 3) (guest|admin)[...]
|
29
|
+
|
30
|
+
block_size = 16
|
31
|
+
prefix = "email="
|
32
|
+
infix = "&uid=10&role="
|
33
|
+
email_suffix = "@xy.com"
|
34
|
+
email_user_length = (2*block_size) - (prefix.length + infix.length + email_suffix.length)
|
35
|
+
email_user = ("a".."z").to_a.sample(email_user_length).join
|
36
|
+
email_address = email_user + email_suffix
|
37
|
+
ciphertext = oracle.encrypted_profile_for(email_address)
|
38
|
+
|
39
|
+
# take the first to blocks of the meassage to have a ciphertext of everything up until role=
|
40
|
+
forgery_blocks,_ = ciphertext.to_crypt_buffer[0,2*block_size]
|
41
|
+
|
42
|
+
# Construct a ciphertext containinig three blocks, where the last block
|
43
|
+
# only contains admin...... where . is a valid padding:
|
44
|
+
# 1: email=aaaaaaaaaa
|
45
|
+
# 2: admin........... . = 0xb
|
46
|
+
# 3: @whatever.com
|
47
|
+
target_role = "admin"
|
48
|
+
padding_length = (block_size - target_role.length())
|
49
|
+
malicious_username = "aaaaaaaaaa#{target_role}".to_crypt_buffer.pad(padding_length).str + email_suffix
|
50
|
+
ciphertext2 = oracle.encrypted_profile_for(malicious_username)
|
51
|
+
role_chunk = ciphertext2[block_size,block_size]
|
52
|
+
|
53
|
+
oracle.decrypt_profile(forgery_blocks.str + role_chunk)
|
54
|
+
end
|
55
|
+
|
56
|
+
def solve14(oracle)
|
57
|
+
Analyzers::EcbStringAppender.new(oracle).analyze
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module CryptoToolbox
|
2
|
+
module Oracles
|
3
|
+
class UserProfileEncryptionOracle
|
4
|
+
def initialize(key = SecureRandom.random_bytes(16) )
|
5
|
+
@key = key
|
6
|
+
end
|
7
|
+
|
8
|
+
def profile_for(email)
|
9
|
+
email.gsub!(/[&=]/,"") # sanitize meta chars
|
10
|
+
"email=#{email}&uid=10&role=guest"
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse_profile(string)
|
14
|
+
string.split("&").each_with_object({}){|pair,hsh| k,v = pair.split("="); hsh[k.to_sym] = v }
|
15
|
+
end
|
16
|
+
|
17
|
+
def encrypted_profile_for(email)
|
18
|
+
Ciphers::Aes.new.encipher_ecb(@key,profile_for(email))
|
19
|
+
end
|
20
|
+
|
21
|
+
def decrypt_profile(ciphertext)
|
22
|
+
plaintext = Ciphers::Aes.new.decipher_ecb(@key,ciphertext).to_crypt_buffer.strip_padding.str
|
23
|
+
parse_profile(plaintext)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,11 +1,27 @@
|
|
1
1
|
module Utils
|
2
2
|
class EcbDetector
|
3
|
+
|
4
|
+
def is_ecb?(ciphertext)
|
5
|
+
is_ecb_aligned?(CryptBuffer(ciphertext)) ||
|
6
|
+
is_ecb_unaligned?(CryptBuffer(ciphertext))
|
7
|
+
end
|
8
|
+
|
3
9
|
def detect(ciphers)
|
4
|
-
result = ciphers.map.with_index
|
10
|
+
result = ciphers.map.with_index do|c,i|
|
11
|
+
ecb_mode?(c) ? [i,c] : []
|
12
|
+
end
|
5
13
|
sanitize_result(result)
|
6
14
|
end
|
7
15
|
|
8
16
|
private
|
17
|
+
|
18
|
+
def is_ecb_aligned?(buffer)
|
19
|
+
ecb_mode?(buffer)
|
20
|
+
end
|
21
|
+
|
22
|
+
def is_ecb_unaligned?(buffer)
|
23
|
+
false
|
24
|
+
end
|
9
25
|
|
10
26
|
def sanitize_result(result)
|
11
27
|
result.reject(&:empty?)
|
@@ -16,14 +32,11 @@ module Utils
|
|
16
32
|
def duplicate_chunk?(chunks)
|
17
33
|
chunks.map(&:bytes).uniq.length < chunks.length
|
18
34
|
end
|
19
|
-
|
20
|
-
def
|
21
|
-
|
22
|
-
[index,ciphertext]
|
23
|
-
else
|
24
|
-
[]
|
25
|
-
end
|
35
|
+
|
36
|
+
def ecb_mode?(ciphertext)
|
37
|
+
duplicate_chunk?(ciphertext.chunks_of(16))
|
26
38
|
end
|
27
39
|
|
28
40
|
end
|
29
41
|
end
|
42
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Utils
|
2
|
+
class EcbOracle
|
3
|
+
attr_reader :mode,:prefix,:suffix
|
4
|
+
|
5
|
+
def initialize(static_key: nil,static_mode: nil,block_size: 128,static_prefix: nil,static_suffix: nil,append: false, prepend: false)
|
6
|
+
@key = CryptBuffer(static_key)
|
7
|
+
@mode = static_mode
|
8
|
+
@iv = nil
|
9
|
+
@c = nil
|
10
|
+
@block_size = block_size
|
11
|
+
|
12
|
+
@append = append
|
13
|
+
@prepend = prepend
|
14
|
+
@suffix = static_suffix
|
15
|
+
@prefix = static_prefix
|
16
|
+
end
|
17
|
+
|
18
|
+
def encipher(plaintext)
|
19
|
+
#support reproducable keys and mode
|
20
|
+
@key ||= CryptBuffer.random(16)
|
21
|
+
@mode ||= [:cbc,:ecb][SecureRandom.random_number(2)]
|
22
|
+
message = pad_message(plaintext)
|
23
|
+
method = "encipher_#{@mode}".to_sym
|
24
|
+
# we dispatch the method to avoid if-else dispatches
|
25
|
+
# due to the difference of IV usage
|
26
|
+
@c = send(method,message)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def pad_message(msg)
|
32
|
+
pad_range = (5..10).to_a
|
33
|
+
lpad_size = pad_range.sample
|
34
|
+
rpad_size = pad_range.sample
|
35
|
+
@prefix ||= SecureRandom.random_bytes(lpad_size)
|
36
|
+
@suffix ||= SecureRandom.random_bytes(rpad_size)
|
37
|
+
|
38
|
+
# NOTE PLEASE rewrite ME !!!
|
39
|
+
msg = @prefix + msg if prepend?
|
40
|
+
msg = msg + @suffix if append?
|
41
|
+
|
42
|
+
msg
|
43
|
+
end
|
44
|
+
|
45
|
+
def encipher_cbc(plaintext)
|
46
|
+
@iv = CryptBuffer.random(16)
|
47
|
+
crypter = Ciphers::Aes.new
|
48
|
+
crypter.send(:encipher_cbc,@key,plaintext,iv: @iv.str)
|
49
|
+
end
|
50
|
+
|
51
|
+
def encipher_ecb(plaintext)
|
52
|
+
crypter = Ciphers::Aes.new
|
53
|
+
crypter.send(:encipher_ecb,@key,plaintext)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def append?
|
58
|
+
@append == true
|
59
|
+
end
|
60
|
+
def prepend?
|
61
|
+
@prepend == true
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -3,14 +3,21 @@ module Utils
|
|
3
3
|
module Console
|
4
4
|
# Print to stdout with support of debug conditions
|
5
5
|
# This is especially helpfull if the analysis fails or is too slow
|
6
|
-
def jot(message, debug: false)
|
6
|
+
def jot(message, debug: false,raw: false)
|
7
7
|
if debug == false || ENV["DEBUG_ANALYSIS"]
|
8
|
-
|
8
|
+
raw ? print_raw(message) : print_nice(message)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
def print_delimiter_line
|
12
12
|
puts "====================================================================="
|
13
13
|
end
|
14
|
+
|
15
|
+
def print_raw(msg)
|
16
|
+
print msg
|
17
|
+
end
|
18
|
+
def print_nice(msg)
|
19
|
+
puts msg
|
20
|
+
end
|
14
21
|
end
|
15
22
|
end
|
16
23
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: crypto-toolbox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dennis Sivia
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aes
|
@@ -45,16 +45,19 @@ executables:
|
|
45
45
|
- break-vigenere-xor
|
46
46
|
- break-padding-oracle
|
47
47
|
- break-cbc-mac-variable-length
|
48
|
+
- break-ecb-string-appender
|
48
49
|
extensions: []
|
49
50
|
extra_rdoc_files: []
|
50
51
|
files:
|
51
52
|
- bin/break-cbc-mac-variable-length
|
53
|
+
- bin/break-ecb-string-appender
|
52
54
|
- bin/break-padding-oracle
|
53
55
|
- bin/break-vigenere-xor
|
54
56
|
- lib/crypto-toolbox.rb
|
55
57
|
- lib/crypto-toolbox/analyzers/cbc_mac.rb
|
56
58
|
- lib/crypto-toolbox/analyzers/cbc_mac/variable_length/analyzer.rb
|
57
59
|
- lib/crypto-toolbox/analyzers/cbc_mac/variable_length/oracles/tcp.rb
|
60
|
+
- lib/crypto-toolbox/analyzers/ecb_string_appender.rb
|
58
61
|
- lib/crypto-toolbox/analyzers/padding_oracle.rb
|
59
62
|
- lib/crypto-toolbox/analyzers/padding_oracle/analyzer.rb
|
60
63
|
- lib/crypto-toolbox/analyzers/padding_oracle/oracles/http_oracle.rb
|
@@ -80,9 +83,13 @@ files:
|
|
80
83
|
- lib/crypto-toolbox/crypt_buffer/concerns/random.rb
|
81
84
|
- lib/crypto-toolbox/crypt_buffer/concerns/xor.rb
|
82
85
|
- lib/crypto-toolbox/crypt_buffer_input_converter.rb
|
83
|
-
- lib/crypto-toolbox/crypto_challanges/solver.rb
|
84
86
|
- lib/crypto-toolbox/forgers/stream_ciphers/forge_generator.rb
|
87
|
+
- lib/crypto-toolbox/matasano/sets/set1.rb
|
88
|
+
- lib/crypto-toolbox/matasano/sets/set2.rb
|
89
|
+
- lib/crypto-toolbox/matasano/solver.rb
|
90
|
+
- lib/crypto-toolbox/oracles/user_profile_encryption_oracle.rb
|
85
91
|
- lib/crypto-toolbox/utils/ecb_detector.rb
|
92
|
+
- lib/crypto-toolbox/utils/ecb_oracle.rb
|
86
93
|
- lib/crypto-toolbox/utils/hamming_distance_filter.rb
|
87
94
|
- lib/crypto-toolbox/utils/reporting/console.rb
|
88
95
|
homepage: https://github.com/scepticulous/crypto-toolbox
|
@@ -110,3 +117,4 @@ signing_key:
|
|
110
117
|
specification_version: 4
|
111
118
|
summary: Toolbox for crypto analysis
|
112
119
|
test_files: []
|
120
|
+
has_rdoc:
|
@@ -1,59 +0,0 @@
|
|
1
|
-
|
2
|
-
module CryptoChallanges
|
3
|
-
class Solver
|
4
|
-
def solve1(input)
|
5
|
-
#CryptoChallanges::Set1::Challange1::Solver.run(input)
|
6
|
-
CryptBuffer.from_hex(input).base64
|
7
|
-
end
|
8
|
-
def solve2(c1,c2)
|
9
|
-
(CryptBuffer.from_hex(c1) ^ CryptBuffer.from_hex(c2)).hex.downcase
|
10
|
-
end
|
11
|
-
|
12
|
-
def solve3(input)
|
13
|
-
candidates = (1..256).map{ |guess| CryptBuffer.from_hex(input).xor_all_with(guess) }
|
14
|
-
detector = Analyzers::Utils::HumanLanguageDetector.new
|
15
|
-
|
16
|
-
detector.human_language_entries(candidates).first.to_s
|
17
|
-
end
|
18
|
-
|
19
|
-
# challange:
|
20
|
-
# One of the 60-character strings in this file has been encrypted by single-character XOR.
|
21
|
-
def solve4(hexstrings)
|
22
|
-
detector = Analyzers::Utils::HumanLanguageDetector.new
|
23
|
-
result = hexstrings.map{|h| CryptBuffer.from_hex(h)}.map.with_index do |c,i|
|
24
|
-
candidates = (1..256).map{ |guess| c.xor_all_with(guess) }
|
25
|
-
matches = detector.human_language_entries(candidates)
|
26
|
-
|
27
|
-
matches.empty? ? nil : matches
|
28
|
-
end
|
29
|
-
result.flatten.compact.map(&:str).first
|
30
|
-
end
|
31
|
-
|
32
|
-
def solve5(input,key)
|
33
|
-
CryptBuffer(input).xor(key,expand_input: true).hex
|
34
|
-
end
|
35
|
-
|
36
|
-
def solve6(input)
|
37
|
-
buffer = CryptBuffer.from_base64(input)
|
38
|
-
Analyzers::VigenereXor.new.analyze(buffer.hex,Analyzers::VigenereXor::HammingDistanceKeyLengthFinder.new)
|
39
|
-
end
|
40
|
-
|
41
|
-
def solve7(input,key)
|
42
|
-
data = CryptBuffer.from_base64(input).str
|
43
|
-
Ciphers::Aes.new(128,:ECB).decipher_ecb(key,data)
|
44
|
-
end
|
45
|
-
|
46
|
-
def solve8(ciphers)
|
47
|
-
Utils::EcbDetector.new.detect(ciphers).first
|
48
|
-
end
|
49
|
-
|
50
|
-
def solve9(input)
|
51
|
-
CryptBuffer(input).pad(4).str
|
52
|
-
end
|
53
|
-
|
54
|
-
def solve10(key,input,iv)
|
55
|
-
data = CryptBuffer.from_base64(input).str
|
56
|
-
Ciphers::Aes.new(128,:ECB).decipher_cbc(key,data,iv: iv).str
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|