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 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