crypto_toolchain 0.1.0
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 +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
|