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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1123ac6ed9d5331c0c2201ba341b68a9841cbe92
4
- data.tar.gz: 10885b70c101833936c9b687150737738e9e0d33
3
+ metadata.gz: 4b760901ea5c2ae1dea432a632e4d9d48e3bb58f
4
+ data.tar.gz: 866e4c2c574a4ad9f8a43891574087e74022d55f
5
5
  SHA512:
6
- metadata.gz: 49add99b4bbba46bb5c7f25830a4eeeb52f2b9ab784edf1b8e2e9f572cba52b7085476494a2071b086ea14f4bcb95d55ed5a3cdd3f74f26f4d38304cd8aeab20
7
- data.tar.gz: ad20f731508e0a281dc088775d05ca0973f80dcc61b2d1a9ab96b4195b5a055f14c1e2c494cdfb573567511bfd935fd02e0ab29e76b1c678081296446103d869
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
+
@@ -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/crypto_challanges/solver.rb'
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(keysize,mode)
5
- @cipher = OpenSSL::Cipher::AES.new(keysize,mode)
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
- @cipher.decrypt
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
- @cipher.encrypt
16
- @cipher.key = key
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
- encipher_ecb_blockwise(@cipher,CryptBuffer(input).chunks_of(16)) + @cipher.final
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
- def encipher_cbc(key,input,iv: nil)
22
- blocks = CryptBuffer(input).chunks_of(16)
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
- k = CryptBuffer(key).hex
25
- xor_input=iv.to_crypt_buffer
55
+ key = CryptBuffer(key_str).hex
56
+
57
+ prev_block=iv.to_crypt_buffer
26
58
 
27
- data = blocks.map do |block|
28
- xored = xor_input ^ block
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
- _,out = AES.encrypt(xored.str, k, {:format => :plain,:padding => false,:cipher => "AES-128-ECB",:iv => xor_input.str })
31
- ecb_block = CryptBuffer(out)
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 decipher_cbc(key,input,iv: nil)
39
- blocks = CryptBuffer(input).chunks_of(16)
40
- iv ||= blocks.shift.str
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
- data = blocks.map do |block|
45
- out = ::AES.decrypt([xor_input.str,block.str] , k, {:format => :plain,:padding => false,:cipher => "AES-128-ECB",:iv => xor_input.str })
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
- def encipher_ecb_block(crypter,block)
60
- crypter.update(block.str)
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
 
@@ -28,6 +28,9 @@ module CryptBufferConcern
28
28
  strict ? Base64.strict_encode64(str) : Base64.encode64(str)
29
29
  end
30
30
 
31
+ def to_crypt_buffer
32
+ self
33
+ end
31
34
  private
32
35
  def bytes2hex(bytes)
33
36
  bytes.map{|b| b.to_s(16)}.map{|hs| hs.length == 1 ? "0#{hs}" : hs }.join
@@ -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,9 @@
1
+ require 'crypto-toolbox/matasano/sets/set1.rb'
2
+ require 'crypto-toolbox/matasano/sets/set2.rb'
3
+
4
+ module Matasano
5
+ class Solver
6
+ include Matasano::Sets::Set1
7
+ include Matasano::Sets::Set2
8
+ end
9
+ 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{|c,i| detect_ecb_entry(c,i) }
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 detect_ecb_entry(ciphertext,index)
21
- if duplicate_chunk?(ciphertext.chunks_of(16))
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
- puts message
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.6
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-20 00:00:00.000000000 Z
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