crypto_toolchain 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +51 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +3 -0
- data/Guardfile +15 -0
- data/LICENSE +21 -0
- data/README.md +95 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/crypto_toolchain.gemspec +33 -0
- data/exe/crypto +7 -0
- data/lib/crypto_toolchain/black_boxes/aes_ctr_editor.rb +25 -0
- data/lib/crypto_toolchain/black_boxes/cbc_bitflip_target.rb +33 -0
- data/lib/crypto_toolchain/black_boxes/cbc_iv_equals_key_target.rb +35 -0
- data/lib/crypto_toolchain/black_boxes/cbc_padding_oracle.rb +44 -0
- data/lib/crypto_toolchain/black_boxes/ctr_bitflip_target.rb +32 -0
- data/lib/crypto_toolchain/black_boxes/dsa_keypair.rb +50 -0
- data/lib/crypto_toolchain/black_boxes/ecb_cut_and_paste_target.rb +50 -0
- data/lib/crypto_toolchain/black_boxes/ecb_interpolate_chosen_plaintext_oracle.rb +28 -0
- data/lib/crypto_toolchain/black_boxes/ecb_or_cbc_encryptor.rb +47 -0
- data/lib/crypto_toolchain/black_boxes/ecb_prepend_chosen_plaintext_oracle.rb +23 -0
- data/lib/crypto_toolchain/black_boxes/md4_mac.rb +20 -0
- data/lib/crypto_toolchain/black_boxes/mt_19937_stream_cipher.rb +47 -0
- data/lib/crypto_toolchain/black_boxes/netcat_cbc_padding_oracle.rb +33 -0
- data/lib/crypto_toolchain/black_boxes/rsa_keypair.rb +83 -0
- data/lib/crypto_toolchain/black_boxes/rsa_parity_oracle.rb +14 -0
- data/lib/crypto_toolchain/black_boxes/rsa_unpadded_message_recovery_oracle.rb +24 -0
- data/lib/crypto_toolchain/black_boxes/sha1_mac.rb +20 -0
- data/lib/crypto_toolchain/black_boxes.rb +22 -0
- data/lib/crypto_toolchain/diffie_hellman/messages.rb +53 -0
- data/lib/crypto_toolchain/diffie_hellman/mitm.rb +52 -0
- data/lib/crypto_toolchain/diffie_hellman/peer.rb +130 -0
- data/lib/crypto_toolchain/diffie_hellman/peer_info.rb +43 -0
- data/lib/crypto_toolchain/diffie_hellman/received_message.rb +17 -0
- data/lib/crypto_toolchain/diffie_hellman.rb +10 -0
- data/lib/crypto_toolchain/extensions/integer_extensions.rb +90 -0
- data/lib/crypto_toolchain/extensions/object_extensions.rb +24 -0
- data/lib/crypto_toolchain/extensions/string_extensions.rb +263 -0
- data/lib/crypto_toolchain/extensions.rb +8 -0
- data/lib/crypto_toolchain/srp/client.rb +51 -0
- data/lib/crypto_toolchain/srp/framework.rb +55 -0
- data/lib/crypto_toolchain/srp/server.rb +38 -0
- data/lib/crypto_toolchain/srp/simple_client.rb +32 -0
- data/lib/crypto_toolchain/srp/simple_server.rb +68 -0
- data/lib/crypto_toolchain/srp.rb +14 -0
- data/lib/crypto_toolchain/tools/aes_ctr_recoverer.rb +30 -0
- data/lib/crypto_toolchain/tools/cbc_bitflip_attack.rb +30 -0
- data/lib/crypto_toolchain/tools/cbc_iv_equals_key_attack.rb +30 -0
- data/lib/crypto_toolchain/tools/cbc_padding_oracle_attack.rb +51 -0
- data/lib/crypto_toolchain/tools/ctr_bitflip_attack.rb +24 -0
- data/lib/crypto_toolchain/tools/determine_blocksize.rb +20 -0
- data/lib/crypto_toolchain/tools/dsa_recover_nonce_from_signatures.rb +53 -0
- data/lib/crypto_toolchain/tools/dsa_recover_private_key_from_nonce.rb +39 -0
- data/lib/crypto_toolchain/tools/ecb_cut_and_paste_attack.rb +47 -0
- data/lib/crypto_toolchain/tools/ecb_interpolate_chosen_plaintext_attack.rb +72 -0
- data/lib/crypto_toolchain/tools/ecb_prepend_chosen_plaintext_attack.rb +42 -0
- data/lib/crypto_toolchain/tools/interactive_xor.rb +51 -0
- data/lib/crypto_toolchain/tools/low_exponent_rsa_signature_forgery.rb +27 -0
- data/lib/crypto_toolchain/tools/md4_length_extension_attack.rb +30 -0
- data/lib/crypto_toolchain/tools/mt_19937_seed_recoverer.rb +27 -0
- data/lib/crypto_toolchain/tools/mt_19937_stream_cipher_seed_recoverer.rb +40 -0
- data/lib/crypto_toolchain/tools/rsa_broadcast_attack.rb +21 -0
- data/lib/crypto_toolchain/tools/rsa_parity_oracle_attack.rb +33 -0
- data/lib/crypto_toolchain/tools/rsa_unpadded_message_recovery_attack.rb +49 -0
- data/lib/crypto_toolchain/tools/sha1_length_extension_attack.rb +30 -0
- data/lib/crypto_toolchain/tools.rb +31 -0
- data/lib/crypto_toolchain/utilities/hmac.rb +73 -0
- data/lib/crypto_toolchain/utilities/md4.rb +106 -0
- data/lib/crypto_toolchain/utilities/mt_19937.rb +218 -0
- data/lib/crypto_toolchain/utilities/sha1.rb +95 -0
- data/lib/crypto_toolchain/utilities.rb +9 -0
- data/lib/crypto_toolchain/version.rb +3 -0
- data/lib/crypto_toolchain.rb +34 -0
- metadata +232 -0
@@ -0,0 +1,263 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
class String
|
3
|
+
# Not cryptographically secure
|
4
|
+
def self.random_bytes(n)
|
5
|
+
n.times.with_object("") do |_, memo|
|
6
|
+
memo << random_byte
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Obviously not cryptographically secure
|
11
|
+
def self.random_byte
|
12
|
+
(0..255).to_a.sample.chr
|
13
|
+
end
|
14
|
+
|
15
|
+
def from_hex
|
16
|
+
raise StandardError.new("Not hex") unless hex?
|
17
|
+
[self].pack("H*")
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_hex
|
21
|
+
unpack("H*").first
|
22
|
+
end
|
23
|
+
|
24
|
+
def hex?
|
25
|
+
self !~ /[^0-9a-f]/i
|
26
|
+
end
|
27
|
+
|
28
|
+
def swap_endian
|
29
|
+
raise ArgumentError.new("Bytesize must be multiple of 4") unless bytesize % 4 == 0
|
30
|
+
unpack("L<*").pack("L>*")
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :swap_endianness, :swap_endian
|
34
|
+
|
35
|
+
def to_base64(strict: true)
|
36
|
+
if strict
|
37
|
+
Base64.strict_encode64(self)
|
38
|
+
else
|
39
|
+
Base64.encode64(self)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def from_base64(strict: true)
|
44
|
+
if strict
|
45
|
+
begin
|
46
|
+
Base64.strict_decode64(self)
|
47
|
+
rescue ArgumentError
|
48
|
+
Base64.decode64(self)
|
49
|
+
end
|
50
|
+
else
|
51
|
+
Base64.decode64(self)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def ^(other)
|
56
|
+
if length != other.length
|
57
|
+
raise ArgumentError.new("Must be same lengths, self: #{self.bytesize}, other: #{other.bytesize}")
|
58
|
+
end
|
59
|
+
each_byte.with_index.with_object("") do |(byte, i), ret|
|
60
|
+
ret << (byte.ord ^ other[i].ord)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def score
|
65
|
+
scan(/[etaoin shrdlu]/i).size
|
66
|
+
end
|
67
|
+
|
68
|
+
def in_blocks(blocksize = CryptoToolchain::AES_BLOCK_SIZE)
|
69
|
+
bytes.map(&:chr).each_slice(blocksize).map(&:join) || [""]
|
70
|
+
end
|
71
|
+
|
72
|
+
def repeat_to(len)
|
73
|
+
ljust(len, self)
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_number
|
77
|
+
to_hex.to_i(16)
|
78
|
+
end
|
79
|
+
|
80
|
+
def hamming_distance(other)
|
81
|
+
(self ^ other).to_bits.count("1")
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_bits
|
85
|
+
self.unpack("B*").first
|
86
|
+
end
|
87
|
+
alias_method :bitstring, :to_bits
|
88
|
+
alias_method :bits, :to_bits
|
89
|
+
|
90
|
+
def potential_repeating_xor_keysizes(take: 3, min: 2, max: 40)
|
91
|
+
(min..max).sort_by do |size|
|
92
|
+
normalized_hamming_distance(self.in_blocks(size)) / size.to_f
|
93
|
+
end.take(take)
|
94
|
+
end
|
95
|
+
|
96
|
+
def potential_repeating_xor_keys(potential_keysizes: self.potential_repeating_xor_keysizes)
|
97
|
+
potential_keysizes.map do |keysize|
|
98
|
+
arr = self.in_blocks(keysize)
|
99
|
+
transposed = (0...keysize).each_with_object([]) do |i, memo|
|
100
|
+
memo << arr.map { |row| row[i] }.join
|
101
|
+
end
|
102
|
+
key = transposed.each_with_object("") do |str, memo|
|
103
|
+
memo << CryptoToolchain::Tools.detect_single_character_xor(str)
|
104
|
+
end
|
105
|
+
key
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# unique blocks. block size is in _bytes_
|
110
|
+
def unique_blocks(blocksize = CryptoToolchain::AES_BLOCK_SIZE)
|
111
|
+
in_blocks(blocksize).each_with_object({}) do |block, found|
|
112
|
+
found[block] ||= true
|
113
|
+
end.keys
|
114
|
+
end
|
115
|
+
|
116
|
+
def pad_pkcs7(blocksize = CryptoToolchain::AES_BLOCK_SIZE)
|
117
|
+
_blocks = in_blocks(blocksize)
|
118
|
+
pad_num = blocksize - _blocks.last.bytesize
|
119
|
+
if pad_num == 0
|
120
|
+
"#{self}#{blocksize.chr * blocksize}"
|
121
|
+
else
|
122
|
+
"#{self}#{pad_num.chr * pad_num}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def is_pkcs7_padded?(blocksize = CryptoToolchain::AES_BLOCK_SIZE)
|
127
|
+
# if self.size != blocksize
|
128
|
+
return in_blocks(blocksize).last.is_block_pkcs7_padded?(blocksize)
|
129
|
+
# end
|
130
|
+
end
|
131
|
+
|
132
|
+
def without_pkcs7_padding(blocksize = CryptoToolchain::AES_BLOCK_SIZE, raise_error: false)
|
133
|
+
if !is_pkcs7_padded?(blocksize)
|
134
|
+
raise ArgumentError.new("Not PKCS7 padded") unless is_pkcs7_padded?(blocksize) if raise_error
|
135
|
+
return self
|
136
|
+
end
|
137
|
+
self[0..(bytesize - (1 + bytes.last))]
|
138
|
+
end
|
139
|
+
|
140
|
+
def decrypt_ecb(key: , blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128')
|
141
|
+
in_blocks(blocksize).each_with_object("") do |block, memo|
|
142
|
+
dec = OpenSSL::Cipher.new("#{cipher}-ECB")
|
143
|
+
dec.decrypt
|
144
|
+
dec.key = key
|
145
|
+
dec.padding = 0
|
146
|
+
plain = dec.update(block) + dec.final
|
147
|
+
memo << plain
|
148
|
+
end.without_pkcs7_padding(blocksize)
|
149
|
+
end
|
150
|
+
|
151
|
+
def encrypt_ecb(key: , blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128')
|
152
|
+
self.pad_pkcs7(blocksize).in_blocks(blocksize).each_with_object("").with_index do |(block, memo), i|
|
153
|
+
|
154
|
+
enc = OpenSSL::Cipher.new("#{cipher}-ECB")
|
155
|
+
enc.encrypt
|
156
|
+
enc.key = key
|
157
|
+
enc.padding = 0
|
158
|
+
plain = enc.update(block) + enc.final
|
159
|
+
|
160
|
+
memo << plain
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def decrypt_cbc(key: , iv: , blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128', strip_padding: true)
|
165
|
+
_blocks = in_blocks(blocksize)
|
166
|
+
decrypted = _blocks.each_with_object("").with_index do |(block, memo), i|
|
167
|
+
dec = OpenSSL::Cipher.new("#{cipher}-ECB")
|
168
|
+
dec.decrypt
|
169
|
+
dec.key = key
|
170
|
+
dec.padding = 0
|
171
|
+
unciphered = dec.update(block) + dec.final
|
172
|
+
chain_block = i == 0 ? iv : _blocks[i - 1]
|
173
|
+
memo << (unciphered ^ chain_block)
|
174
|
+
end
|
175
|
+
if strip_padding
|
176
|
+
decrypted.without_pkcs7_padding(blocksize)
|
177
|
+
else
|
178
|
+
decrypted
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def encrypt_cbc(key: , iv: , blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128')
|
183
|
+
_blocks = pad_pkcs7(blocksize).in_blocks(blocksize)
|
184
|
+
_blocks.each_with_object("").with_index do |(block, memo), i|
|
185
|
+
chain_block = i == 0 ? iv : memo[(blocksize * -1)..-1]
|
186
|
+
intermediate = block ^ chain_block
|
187
|
+
enc = OpenSSL::Cipher.new("#{cipher}-ECB")
|
188
|
+
enc.encrypt
|
189
|
+
enc.key = key
|
190
|
+
enc.padding = 0
|
191
|
+
crypted = enc.update(intermediate) + enc.final
|
192
|
+
memo << crypted
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Bitstring is indexed as a normal string, ie:
|
197
|
+
#
|
198
|
+
# 'd' = 0x64 = 01100100
|
199
|
+
# 01234567
|
200
|
+
# 'd'.bitflip(7) => 'e'
|
201
|
+
def bitflip(bit_index, byte_index: 0)
|
202
|
+
byte_offset, bit_offset = bit_index.divmod(8)
|
203
|
+
byte_offset += byte_index
|
204
|
+
target = self.dup
|
205
|
+
target[byte_offset] = (target[byte_offset].ord ^ (1 << (7-bit_offset))).chr
|
206
|
+
target
|
207
|
+
end
|
208
|
+
alias_method :bit_flip, :bitflip
|
209
|
+
alias_method :flipbit, :bitflip
|
210
|
+
alias_method :flip_bit, :bitflip
|
211
|
+
alias_method :flip, :bitflip
|
212
|
+
|
213
|
+
def encrypt_ctr(key: , nonce: , cipher: 'AES-128', start_counter: 0)
|
214
|
+
each_byte.with_index(start_counter).with_object("") do |(byte, i), memo|
|
215
|
+
ctr = i / 16
|
216
|
+
ctr_params = [nonce, ctr].pack("Q<Q<")
|
217
|
+
enc = OpenSSL::Cipher.new("#{cipher}-ECB")
|
218
|
+
enc.encrypt
|
219
|
+
enc.key = key
|
220
|
+
enc.padding = 0
|
221
|
+
keystream = enc.update(ctr_params) + enc.final
|
222
|
+
memo << (byte.chr ^ keystream[i % 16])
|
223
|
+
end
|
224
|
+
end
|
225
|
+
alias_method :decrypt_ctr, :encrypt_ctr
|
226
|
+
|
227
|
+
def contains_duplicate_blocks?(blocksize = CryptoToolchain::AES_BLOCK_SIZE)
|
228
|
+
_blocks = in_blocks(blocksize)
|
229
|
+
_blocks.length > _blocks.uniq.length
|
230
|
+
end
|
231
|
+
alias_method :is_ecb_encrypted?, :contains_duplicate_blocks?
|
232
|
+
|
233
|
+
# Thanks Ruby Facets!
|
234
|
+
def snakecase
|
235
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
236
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
237
|
+
tr('-', '_').
|
238
|
+
gsub(/\s/, '_').
|
239
|
+
gsub(/__+/, '_').
|
240
|
+
downcase
|
241
|
+
end
|
242
|
+
alias_method :snake_case, :snakecase
|
243
|
+
alias_method :underscore, :snakecase
|
244
|
+
|
245
|
+
protected
|
246
|
+
|
247
|
+
def is_block_pkcs7_padded?(blocksize = CryptoToolchain::AES_BLOCK_SIZE)
|
248
|
+
return true if self == blocksize.chr * blocksize
|
249
|
+
(1...blocksize).each do |padding|
|
250
|
+
return true if self[(blocksize - padding)..-1] == padding.chr * padding
|
251
|
+
end
|
252
|
+
false
|
253
|
+
end
|
254
|
+
|
255
|
+
def normalized_hamming_distance(blocks)
|
256
|
+
raise ArgumentError.new("arg should be an array") unless blocks.is_a?(Array)
|
257
|
+
(
|
258
|
+
blocks[0].hamming_distance(blocks[1]) +
|
259
|
+
blocks[0].hamming_distance(blocks[2]) +
|
260
|
+
blocks[0].hamming_distance(blocks[3])
|
261
|
+
) / 3.0
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module SRP
|
3
|
+
class Client
|
4
|
+
include SRP::Framework
|
5
|
+
|
6
|
+
attr_reader :server_pubkey, :authenticated
|
7
|
+
alias_method :authenticated?, :authenticated
|
8
|
+
|
9
|
+
def initialize(**kargs)
|
10
|
+
provided_pubkey = kargs.delete(:pubkey)
|
11
|
+
super(**kargs)
|
12
|
+
@pubkey = provided_pubkey || g.modpow(privkey, n)
|
13
|
+
end
|
14
|
+
|
15
|
+
def send_hello
|
16
|
+
write_message("hello", email, pubkey)
|
17
|
+
end
|
18
|
+
|
19
|
+
def send_verify
|
20
|
+
hmac = OpenSSL::HMAC.hexdigest("SHA256", key.to_s, salt.to_s)
|
21
|
+
write_message("verify", hmac)
|
22
|
+
end
|
23
|
+
|
24
|
+
def hello_received(_salt, _server_pubkey)
|
25
|
+
@salt = _salt.to_i
|
26
|
+
@server_pubkey = _server_pubkey.to_i
|
27
|
+
secret = calculate_secret
|
28
|
+
puts "Client generated secret #{secret}" if DEBUG
|
29
|
+
@key = Digest::SHA256.hexdigest(secret.to_s)
|
30
|
+
send_verify
|
31
|
+
end
|
32
|
+
|
33
|
+
def authentication_success_received
|
34
|
+
@authenticated = true
|
35
|
+
write_message("shutdown")
|
36
|
+
raise ShutdownSignal
|
37
|
+
end
|
38
|
+
|
39
|
+
def calculate_secret
|
40
|
+
return 0 if [0, n, n**2, n**3].include?(pubkey)
|
41
|
+
|
42
|
+
xH = Digest::SHA256.hexdigest("#{salt}#{password}")
|
43
|
+
x = xH.to_i(16)
|
44
|
+
uH = Digest::SHA256.hexdigest("#{pubkey}#{server_pubkey}")
|
45
|
+
u = uH.to_i(16)
|
46
|
+
# S = (B - k * g**x)**(a + u * x) % N
|
47
|
+
(server_pubkey - k * g.modpow(x, n)).modpow(privkey + u * x, n)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module SRP
|
3
|
+
module Framework
|
4
|
+
|
5
|
+
attr_reader :n, :g, :k, :email, :password, :socket, :privkey, :pubkey, :salt, :key
|
6
|
+
|
7
|
+
def initialize(n: CryptoToolchain::NIST_P, g: CryptoToolchain::NIST_G,
|
8
|
+
k: 3, email: "charles@goodog.com", password: "i<3porkchops",
|
9
|
+
socket: )
|
10
|
+
@n = n
|
11
|
+
@g = g
|
12
|
+
@k = k
|
13
|
+
@email = email
|
14
|
+
@password = password
|
15
|
+
@socket = socket
|
16
|
+
@privkey = rand(1..0xffffffff) % n
|
17
|
+
end
|
18
|
+
|
19
|
+
EVENT_WHITELIST = %w( hello verify shutdown error authentication_success ).freeze
|
20
|
+
def go!
|
21
|
+
event_loop do |event_string|
|
22
|
+
event_type, *data = event_string.split(DELIMITER)
|
23
|
+
puts "Received #{event_type} #{data}" if DEBUG
|
24
|
+
if !EVENT_WHITELIST.include?(event_type)
|
25
|
+
socket.puts("error|event #{event_type} unknown") and next
|
26
|
+
end
|
27
|
+
send("#{event_type}_received", *data)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def event_loop
|
32
|
+
begin
|
33
|
+
loop do
|
34
|
+
yield socket.readline.strip
|
35
|
+
end
|
36
|
+
rescue ShutdownSignal
|
37
|
+
# Nothing
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def shutdown_received(*args)
|
42
|
+
raise ShutdownSignal
|
43
|
+
end
|
44
|
+
|
45
|
+
def write_message(*args)
|
46
|
+
socket.puts args.join(DELIMITER)
|
47
|
+
end
|
48
|
+
|
49
|
+
def error_received(*args)
|
50
|
+
raise StandardError.new(args.join(" "))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module SRP
|
3
|
+
class Server
|
4
|
+
include SRP::Framework
|
5
|
+
|
6
|
+
attr_reader :v, :client_pubkey
|
7
|
+
|
8
|
+
def initialize(**kargs)
|
9
|
+
super(**kargs)
|
10
|
+
@salt = rand(1..0xffffffff)
|
11
|
+
xH = Digest::SHA256.hexdigest("#{salt}#{password}")
|
12
|
+
x = xH.to_i(16)
|
13
|
+
@v = g.modpow(x, n)
|
14
|
+
@pubkey = k*v + g.modpow(privkey, n)
|
15
|
+
end
|
16
|
+
|
17
|
+
def hello_received(email, _client_pubkey)
|
18
|
+
@client_pubkey = _client_pubkey.to_i
|
19
|
+
write_message("hello", salt, pubkey)
|
20
|
+
uH = Digest::SHA256.hexdigest("#{client_pubkey}#{pubkey}")
|
21
|
+
u = uH.to_i(16)
|
22
|
+
# S = (A * v**u) ** b % N
|
23
|
+
secret = (client_pubkey * v.modpow(u, n)).modpow(privkey, n)
|
24
|
+
puts "Server generated secret #{secret}" if DEBUG
|
25
|
+
@key = Digest::SHA256.hexdigest(secret.to_s)
|
26
|
+
end
|
27
|
+
|
28
|
+
def verify_received(hmac)
|
29
|
+
valid_hmac = OpenSSL::HMAC.hexdigest("SHA256", key.to_s, salt.to_s)
|
30
|
+
if hmac == valid_hmac
|
31
|
+
write_message("authentication_success")
|
32
|
+
else
|
33
|
+
write_message("error", "invalid_hmac")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module SRP
|
3
|
+
class SimpleClient < Client
|
4
|
+
include SRP::Framework
|
5
|
+
|
6
|
+
attr_reader :u
|
7
|
+
|
8
|
+
def initialize(**kargs)
|
9
|
+
provided_pubkey = kargs.delete(:pubkey)
|
10
|
+
super(**kargs)
|
11
|
+
@pubkey = provided_pubkey || g.modpow(privkey, n)
|
12
|
+
end
|
13
|
+
|
14
|
+
def hello_received(_salt, _server_pubkey, _u)
|
15
|
+
@salt = _salt.to_i
|
16
|
+
@server_pubkey = _server_pubkey.to_i
|
17
|
+
@u = _u.to_i
|
18
|
+
secret = calculate_secret
|
19
|
+
puts "SimpleClient generated secret #{secret}" if DEBUG
|
20
|
+
@key = Digest::SHA256.hexdigest(secret.to_s)
|
21
|
+
send_verify
|
22
|
+
end
|
23
|
+
|
24
|
+
def calculate_secret
|
25
|
+
xH = Digest::SHA256.hexdigest("#{salt}#{password}")
|
26
|
+
x = xH.to_i(16)
|
27
|
+
# S = B**(a + ux) % n
|
28
|
+
server_pubkey.modpow(privkey + (u * x), n)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module SRP
|
3
|
+
class SimpleServer < Server
|
4
|
+
include SRP::Framework
|
5
|
+
|
6
|
+
def initialize(n: CryptoToolchain::NIST_P, g: CryptoToolchain::NIST_G,
|
7
|
+
k: 3, email: "charles@goodog.com", password: "i<3porkchops",
|
8
|
+
privkey: nil, pubkey: nil, u: (rand(1..0x0000ffff)), malicious: false,
|
9
|
+
salt: rand(1..0xffffffff), socket: )
|
10
|
+
@n = n
|
11
|
+
@g = g
|
12
|
+
@k = k
|
13
|
+
@email = email,
|
14
|
+
@password = password
|
15
|
+
@socket = socket
|
16
|
+
@privkey = privkey || rand(1..0xffffffff) % n
|
17
|
+
@pubkey = pubkey || g.modpow(@privkey, n)
|
18
|
+
@u = u
|
19
|
+
@salt = salt
|
20
|
+
xH = Digest::SHA256.hexdigest("#{salt}#{password}")
|
21
|
+
x = xH.to_i(16)
|
22
|
+
@v = g.modpow(x, n)
|
23
|
+
@malicious = malicious
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :salt, :u, :malicious, :recovered_password
|
27
|
+
alias_method :malicious?, :malicious
|
28
|
+
|
29
|
+
def hello_received(email, _client_pubkey)
|
30
|
+
@client_pubkey = _client_pubkey.to_i
|
31
|
+
write_message("hello", salt, pubkey, u)
|
32
|
+
# S = (A * v**u) ** b % N
|
33
|
+
secret = (client_pubkey * v.modpow(u, n)).modpow(privkey, n)
|
34
|
+
puts "SimpleServer generated secret #{secret}" if DEBUG
|
35
|
+
@key = Digest::SHA256.hexdigest(secret.to_s)
|
36
|
+
end
|
37
|
+
|
38
|
+
def wordlist
|
39
|
+
return @wordlist if defined? @wordlist
|
40
|
+
_words = File.readlines("/usr/share/dict/words").
|
41
|
+
shuffle[0...100].
|
42
|
+
map(&:strip)
|
43
|
+
_words << "i<3porkchops"
|
44
|
+
@wordlist = _words.shuffle
|
45
|
+
end
|
46
|
+
|
47
|
+
def crack(hmac)
|
48
|
+
wordlist.each_with_index do |word, i|
|
49
|
+
_x = Digest::SHA256.hexdigest("#{salt}#{word}").to_i(16)
|
50
|
+
_v = g.modpow(_x, n)
|
51
|
+
_secret = (client_pubkey * _v.modpow(u, n)).modpow(privkey, n)
|
52
|
+
_key = Digest::SHA256.hexdigest(_secret.to_s)
|
53
|
+
word_hmac = OpenSSL::HMAC.hexdigest("SHA256", _key, salt.to_s)
|
54
|
+
return word if word_hmac == hmac
|
55
|
+
end
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def verify_received(hmac)
|
60
|
+
if malicious?
|
61
|
+
@recovered_password = crack(hmac)
|
62
|
+
puts "Recovered #{@recovered_password}" if DEBUG
|
63
|
+
end
|
64
|
+
super(hmac)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "crypto_toolchain/srp/framework"
|
2
|
+
require "crypto_toolchain/srp/client"
|
3
|
+
require "crypto_toolchain/srp/server"
|
4
|
+
require "crypto_toolchain/srp/simple_client"
|
5
|
+
require "crypto_toolchain/srp/simple_server"
|
6
|
+
|
7
|
+
module CryptoToolchain
|
8
|
+
module SRP
|
9
|
+
ShutdownSignal = Class.new(RuntimeError)
|
10
|
+
|
11
|
+
DELIMITER = "|"
|
12
|
+
DEBUG = false
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module Tools
|
3
|
+
class AesCtrRecoverer
|
4
|
+
|
5
|
+
attr_reader :ciphertext, :editor
|
6
|
+
|
7
|
+
def initialize(editor)
|
8
|
+
@editor = editor
|
9
|
+
@ciphertext = editor.ciphertext
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute
|
13
|
+
(0...(ciphertext.length)).each_with_object("") do |i, memo|
|
14
|
+
memo << get_character(i)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_character(i)
|
19
|
+
(0..255).each do |byte|
|
20
|
+
chr = byte.chr
|
21
|
+
if editor.edit(offset: i, with: chr) == ciphertext
|
22
|
+
return chr
|
23
|
+
end
|
24
|
+
end
|
25
|
+
raise RuntimeError, "Could not recover character"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding; ASCII-8BIT
|
2
|
+
module CryptoToolchain
|
3
|
+
module Tools
|
4
|
+
class CbcBitflipAttack
|
5
|
+
def initialize(target: CryptoToolchain::BlackBoxes::CbcBitflipTarget.new)
|
6
|
+
@target = target
|
7
|
+
end
|
8
|
+
|
9
|
+
def flip(block, byte, bit)
|
10
|
+
new_byte = ((1 << (bit - 8)) ^ (block[byte].ord)).chr
|
11
|
+
block[0...byte] + new_byte + block[(byte+1)..-1]
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute
|
15
|
+
easy = ":admin<true:" #only need to flip the last bit of bytes 1, 7, 12
|
16
|
+
blocks = target.encrypt(easy).in_blocks(16)
|
17
|
+
first = flip(blocks[1], 0, 8)
|
18
|
+
equals = flip(first, 6, 8)
|
19
|
+
last = flip(equals, 11, 8)
|
20
|
+
blocks[1] = last
|
21
|
+
blocks.join
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :target
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding; ASCII-8BIT
|
2
|
+
module CryptoToolchain
|
3
|
+
module Tools
|
4
|
+
class CbcIvEqualsKeyAttack
|
5
|
+
attr
|
6
|
+
def initialize(target: CryptoToolchain::BlackBoxes::CbcIvEqualsKeyTarget.new,
|
7
|
+
message_prefix: "Invalid byte in ")
|
8
|
+
@target = target
|
9
|
+
@message_prefix = message_prefix
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute
|
13
|
+
initial = ("A" * CryptoToolchain::AES_BLOCK_SIZE * 3)
|
14
|
+
blocks = target.encrypt(initial).in_blocks(CryptoToolchain::AES_BLOCK_SIZE)
|
15
|
+
mal = blocks[0] + (0.chr * 16) + blocks[0]
|
16
|
+
begin
|
17
|
+
target.is_admin?(mal)
|
18
|
+
rescue RuntimeError => e
|
19
|
+
blocks = e.message[(message_prefix.length)..-1].in_blocks(CryptoToolchain::AES_BLOCK_SIZE)
|
20
|
+
blocks[0] ^ blocks[2]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :target, :message_prefix
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
module CryptoToolchain
|
3
|
+
module Tools
|
4
|
+
class CbcPaddingOracleAttack
|
5
|
+
def initialize(oracle: , blocksize: 16)
|
6
|
+
@oracle = oracle
|
7
|
+
@blocksize = blocksize
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute
|
11
|
+
_blocks = ciphertext.in_blocks(blocksize)
|
12
|
+
_blocks[1..-1].map.with_index(1) do |block, i|
|
13
|
+
preceding = _blocks[i - 1]
|
14
|
+
intermediate = intermediate_block(preceding: preceding, target: block)
|
15
|
+
intermediate ^ preceding
|
16
|
+
end.
|
17
|
+
join.
|
18
|
+
without_pkcs7_padding(blocksize)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :oracle, :blocksize
|
24
|
+
|
25
|
+
def ciphertext
|
26
|
+
@ciphertext ||= oracle.ciphertext
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def intermediate_block(preceding: , target: )
|
32
|
+
range = Array(0...blocksize).reverse
|
33
|
+
range.each_with_object(0x00.chr * blocksize) do |index, memo|
|
34
|
+
pad = blocksize - index
|
35
|
+
padding = (pad.chr * pad).rjust(blocksize, 0x00.chr)
|
36
|
+
candidate = padding ^ memo ^ preceding
|
37
|
+
(0..255).each do |guess|
|
38
|
+
next if (preceding.bytes[index] == guess && index == blocksize - 1)
|
39
|
+
candidate = padding ^ memo
|
40
|
+
candidate[index] = guess.chr
|
41
|
+
attempt = candidate + target
|
42
|
+
if oracle.execute(attempt)
|
43
|
+
memo[index] = (guess ^ pad).chr
|
44
|
+
break
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|