crypto-toolbox 0.2.6 → 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|