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,47 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module BlackBoxes
|
3
|
+
class MT19937StreamCipher
|
4
|
+
MAX_SEED = 0x0000ffff
|
5
|
+
class << self
|
6
|
+
def max_seed
|
7
|
+
@max_seed ||= MAX_SEED
|
8
|
+
end
|
9
|
+
|
10
|
+
def max_seed=(val)
|
11
|
+
@max_seed = val
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.generate_token(length: 32, seed: Time.now.to_i)
|
16
|
+
new("A" * length, seed: seed).keystream.to_base64
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(plaintext, seed: rand(0..(self.class.max_seed)))
|
20
|
+
@seed = seed & self.class.max_seed
|
21
|
+
@prng = CryptoToolchain::Utilities::MT19937.new(@seed)
|
22
|
+
@plaintext = plaintext
|
23
|
+
end
|
24
|
+
|
25
|
+
def encrypt(str = plaintext)
|
26
|
+
str ^ keystream
|
27
|
+
end
|
28
|
+
|
29
|
+
def decrypt(str)
|
30
|
+
str ^ keystream
|
31
|
+
end
|
32
|
+
|
33
|
+
def keystream
|
34
|
+
return @keystream if defined? @keystream
|
35
|
+
_keystream = (0..(plaintext.bytesize / 4)).each_with_object("") do |_, memo|
|
36
|
+
memo << [prng.extract].pack("L")
|
37
|
+
end
|
38
|
+
@keystream = _keystream[0...(plaintext.bytesize)]
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :plaintext, :prng, :seed
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module BlackBoxes
|
3
|
+
class NetcatCbcPaddingOracle
|
4
|
+
|
5
|
+
attr_reader :ciphertext
|
6
|
+
|
7
|
+
def initialize(key: Random.new.bytes(16), iv: Random.new.bytes(16))
|
8
|
+
@key = key
|
9
|
+
@iv = iv
|
10
|
+
@ciphertext = Base64.strict_decode64('SNXIDUFQW0Ul6GXI4NyU/LMHl+vRlVIYp4pvFstfpP1n1C9Xhbl/bNip6mK5l7TMPS+vw247XTYK3LKIGT4AZVh6zUB97fN3fOamkLvzpmA=')
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute(str)
|
14
|
+
handle = IO.popen(["nc", "bufferoverflow.disappointedmama.com", '6767'], "r+")
|
15
|
+
handle.puts(Base64.strict_encode64(str))
|
16
|
+
resp = handle.readpartial(1024).strip
|
17
|
+
handle.close
|
18
|
+
case resp
|
19
|
+
when "Failed to decrypt the message"
|
20
|
+
false
|
21
|
+
when "Successfully received and decrypted the message"
|
22
|
+
true
|
23
|
+
else
|
24
|
+
raise StandardError.new, "Unknown response `#{res}`"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :key, :iv
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
module CryptoToolchain
|
3
|
+
module BlackBoxes
|
4
|
+
class RSAKeypair
|
5
|
+
PrivateKey = Struct.new(:d, :n)
|
6
|
+
PublicKey = Struct.new(:e, :n)
|
7
|
+
|
8
|
+
def initialize(bits: 1024)
|
9
|
+
@bits = bits
|
10
|
+
@p = OpenSSL::BN::generate_prime(bits/2).to_i
|
11
|
+
@q = OpenSSL::BN::generate_prime(bits/2).to_i
|
12
|
+
@n = @p * @q
|
13
|
+
et = (@p-1) * (@q-1)
|
14
|
+
@e = 3
|
15
|
+
@d = @e.invmod(et)
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :e, :bits
|
19
|
+
|
20
|
+
def encrypt(m, to: )
|
21
|
+
raise ArgumentError.new("Message should be a string") unless m.is_a?(String)
|
22
|
+
m.
|
23
|
+
to_number.
|
24
|
+
modpow(to.e, to.n).
|
25
|
+
to_bin_string
|
26
|
+
end
|
27
|
+
|
28
|
+
def decrypt(m)
|
29
|
+
raise ArgumentError.new("Message should be a string") unless m.is_a?(String)
|
30
|
+
m.
|
31
|
+
to_number.
|
32
|
+
modpow(private_key.d, private_key.n).
|
33
|
+
to_bin_string
|
34
|
+
end
|
35
|
+
|
36
|
+
def sign(plaintext)
|
37
|
+
blocksize = bits / 8
|
38
|
+
digest = CryptoToolchain::Utilities::SHA1.digest(plaintext)
|
39
|
+
asn = asn1(:sha1)
|
40
|
+
# the 4 is the mandatory 0x00 0x01 0xff 0x00
|
41
|
+
pad_num = blocksize - ( digest.bytesize + asn.bytesize + 4)
|
42
|
+
block = "\x00\x01\xff" + (0xff.chr * pad_num) + "\x00" + asn + digest
|
43
|
+
decrypt(block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def verify(message, signature: , lazy: true)
|
47
|
+
raise("I can't not be lazy") unless lazy
|
48
|
+
enc = encrypt(signature, to: pubkey)
|
49
|
+
asn = ASN1.fetch(:sha1)
|
50
|
+
regex = /(?<padding>\x01\xff+\x00)(?<asn>.{#{asn.bytesize}})(?<hash>.{20})/m
|
51
|
+
begin
|
52
|
+
_padding, potential_asn, hash = enc.match(regex).captures
|
53
|
+
rescue
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
potential_asn == asn && hash == CryptoToolchain::Utilities::SHA1.digest(message)
|
57
|
+
end
|
58
|
+
|
59
|
+
def public_key
|
60
|
+
@public_key ||= PublicKey.new(@e, @n)
|
61
|
+
end
|
62
|
+
alias_method :pubkey, :public_key
|
63
|
+
|
64
|
+
def private_key
|
65
|
+
@private_key ||= PrivateKey.new(@d, @n)
|
66
|
+
end
|
67
|
+
alias_method :privkey, :private_key
|
68
|
+
|
69
|
+
# Values from
|
70
|
+
# https://stackoverflow.com/questions/3713774/c-sharp-how-to-calculate-asn-1-der-encoding-of-a-particular-hash-algorithm
|
71
|
+
def asn1(hash_type)
|
72
|
+
raise ArgumentError.new("Only sha1 is supported") unless hash_type == :sha1
|
73
|
+
{
|
74
|
+
md5: "0 0\f\x06\b*\x86H\x86\xF7\r\x02\x05\x05\x00\x04\x10",
|
75
|
+
sha1: "0!0\t\x06\x05+\x0E\x03\x02\x1A\x05\x00\x04\x14",
|
76
|
+
sha256: "010\r\x06\t`\x86H\x01e\x03\x04\x02\x01\x05\x00\x04 ",
|
77
|
+
sha384: "0A0\r\x06\t`\x86H\x01e\x03\x04\x02\x02\x05\x00\x040",
|
78
|
+
sha512: "0Q0\r\x06\t`\x86H\x01e\x03\x04\x02\x03\x05\x00\x04@"
|
79
|
+
}.fetch(hash_type)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module BlackBoxes
|
3
|
+
class RSAUnpaddedMessageRecoveryOracle
|
4
|
+
|
5
|
+
attr_reader :keypair
|
6
|
+
|
7
|
+
def initialize(keypair: CryptoToolchain::BlackBoxes::RSAKeypair.new)
|
8
|
+
@keypair = keypair
|
9
|
+
@seen = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute(ciphertext)
|
13
|
+
hsh = Digest::SHA256.hexdigest(ciphertext)
|
14
|
+
raise ArgumentError.new("Already decrypted") if @seen.include?(hsh)
|
15
|
+
@seen << hsh
|
16
|
+
keypair.decrypt(ciphertext)
|
17
|
+
end
|
18
|
+
|
19
|
+
def encrypt(ciphertext)
|
20
|
+
keypair.encrypt(ciphertext, to: keypair.public_key)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module BlackBoxes
|
3
|
+
class SHA1Mac
|
4
|
+
attr_reader :key
|
5
|
+
|
6
|
+
def initialize(key: Random.new.bytes(16))
|
7
|
+
@key = key
|
8
|
+
end
|
9
|
+
|
10
|
+
def mac(str)
|
11
|
+
concat = key + str
|
12
|
+
CryptoToolchain::Utilities::SHA1.hexdigest(concat)
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid?(message: , mac: )
|
16
|
+
self.mac(message) == mac
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "crypto_toolchain/black_boxes/ecb_or_cbc_encryptor"
|
2
|
+
require "crypto_toolchain/black_boxes/ecb_prepend_chosen_plaintext_oracle"
|
3
|
+
require "crypto_toolchain/black_boxes/ecb_interpolate_chosen_plaintext_oracle"
|
4
|
+
require "crypto_toolchain/black_boxes/ecb_cut_and_paste_target"
|
5
|
+
require "crypto_toolchain/black_boxes/cbc_bitflip_target"
|
6
|
+
require "crypto_toolchain/black_boxes/cbc_padding_oracle"
|
7
|
+
require "crypto_toolchain/black_boxes/netcat_cbc_padding_oracle"
|
8
|
+
require "crypto_toolchain/black_boxes/mt_19937_stream_cipher"
|
9
|
+
require "crypto_toolchain/black_boxes/aes_ctr_editor"
|
10
|
+
require "crypto_toolchain/black_boxes/ctr_bitflip_target"
|
11
|
+
require "crypto_toolchain/black_boxes/cbc_iv_equals_key_target"
|
12
|
+
require "crypto_toolchain/black_boxes/sha1_mac"
|
13
|
+
require "crypto_toolchain/black_boxes/md4_mac"
|
14
|
+
require "crypto_toolchain/black_boxes/rsa_keypair"
|
15
|
+
require "crypto_toolchain/black_boxes/rsa_unpadded_message_recovery_oracle"
|
16
|
+
require "crypto_toolchain/black_boxes/rsa_parity_oracle"
|
17
|
+
require "crypto_toolchain/black_boxes/dsa_keypair"
|
18
|
+
|
19
|
+
module CryptoToolchain
|
20
|
+
module BlackBoxes
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module DiffieHellman
|
3
|
+
module Messages
|
4
|
+
class Die ; end
|
5
|
+
|
6
|
+
class PeerAddress
|
7
|
+
def initialize(peer: , channel: , initial: false)
|
8
|
+
@peer = peer
|
9
|
+
@channel = channel
|
10
|
+
@initial = initial
|
11
|
+
end
|
12
|
+
attr_reader :peer, :channel, :initial
|
13
|
+
alias_method :initial?, :initial
|
14
|
+
attr_accessor :pubkey, :shared_secret
|
15
|
+
end
|
16
|
+
|
17
|
+
class KeyExchange
|
18
|
+
def initialize(peer: , pubkey: , p: nil, g: nil, initial: false)
|
19
|
+
if initial && (p.nil? || g.nil?)
|
20
|
+
raise ArgumentError.new("Initial message must provide p and g")
|
21
|
+
end
|
22
|
+
@p = p
|
23
|
+
@g = g
|
24
|
+
@pubkey = pubkey
|
25
|
+
@peer = peer
|
26
|
+
@initial = initial
|
27
|
+
end
|
28
|
+
attr_reader :p, :g, :peer, :pubkey, :initial
|
29
|
+
alias_method :initial?, :initial
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
"PEER: #{peer.name} P: #{p} G: #{g} PUBKEY: #{pubkey % 1000}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Datum
|
37
|
+
def initialize(peer: , contents: , initial: false)
|
38
|
+
@peer = peer
|
39
|
+
@contents = contents
|
40
|
+
@initial = initial
|
41
|
+
end
|
42
|
+
|
43
|
+
def decrypt(key: )
|
44
|
+
iv = contents[0..15]
|
45
|
+
contents[16..-1].decrypt_cbc(key: key, iv: iv)
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :peer, :contents, :initial
|
49
|
+
alias_method :initial?, :initial
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
Thread.abort_on_exception = true
|
2
|
+
module CryptoToolchain
|
3
|
+
module DiffieHellman
|
4
|
+
class MITM < Peer
|
5
|
+
def initialize(debug: false, name: "MITM", p: NIST_P, g: NIST_G, peer_a: , peer_b: , pubkey: nil)
|
6
|
+
@peer_a = peer_a
|
7
|
+
@peer_b = peer_b
|
8
|
+
@pubkey = pubkey
|
9
|
+
super(debug: debug, name: name, p: p, g: g)
|
10
|
+
[peer_a, peer_b].each do |peer|
|
11
|
+
puts "Adding #{peer.name} to #{name} at startup" if debug
|
12
|
+
add_address(peer)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def peer_address_response(msg)
|
17
|
+
send_msg other_peer(msg.peer), my_address_message(initial: msg.initial)
|
18
|
+
end
|
19
|
+
|
20
|
+
def do_key_exchange
|
21
|
+
msg = Messages::KeyExchange.new(peer: self, pubkey: pubkey, p: p, g: g, initial: true)
|
22
|
+
[peer_a, peer_b].each do |peer|
|
23
|
+
info_for(peer).update(p: p, g: g)
|
24
|
+
send_msg(peer, msg)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def key_exchange_response(msg)
|
29
|
+
info = info_for(msg.peer)
|
30
|
+
info.update(pubkey: msg.pubkey)
|
31
|
+
secret_override = invalid_pubkey? ? 0 : nil
|
32
|
+
info.set_shared_secret(privkey, override: secret_override)
|
33
|
+
puts "#{name} generated secret #{info.shared_secret} for #{msg.peer.name}" if debug
|
34
|
+
end
|
35
|
+
|
36
|
+
def datum_response(msg)
|
37
|
+
data = msg.decrypt(key: info_for(msg.peer).session_key)
|
38
|
+
puts "#{name} got message containing #{data} from #{msg.peer.name}" if debug
|
39
|
+
other = other_peer(msg.peer)
|
40
|
+
encrypted = encrypted_message_for(other, message: data, initial: msg.initial)
|
41
|
+
send_msg(other, encrypted)
|
42
|
+
@received_messages << ReceivedMessage.new(from: msg.peer.name, contents: data)
|
43
|
+
end
|
44
|
+
|
45
|
+
def other_peer(peer)
|
46
|
+
peer == peer_a ? peer_b : peer_a
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader :peer_a, :peer_b
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module DiffieHellman
|
3
|
+
class Peer
|
4
|
+
def initialize(debug: false, name: SecureRandom.uuid, p: NIST_P, g: NIST_G)
|
5
|
+
@addresses = {}
|
6
|
+
@channel = Queue.new
|
7
|
+
@name = name
|
8
|
+
@debug = debug
|
9
|
+
@p = p
|
10
|
+
@g = g
|
11
|
+
@received_messages = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def process!
|
15
|
+
when_ready do
|
16
|
+
msg = channel.pop
|
17
|
+
message_type = msg.class.to_s.split(':').last
|
18
|
+
if msg.respond_to?(:peer)
|
19
|
+
puts "#{name} got #{message_type} from #{msg.peer.name}" if debug
|
20
|
+
end
|
21
|
+
method = "#{message_type}_response".snakecase
|
22
|
+
unless self.respond_to?(method)
|
23
|
+
raise ArgumentError.new("Don't know how to process method :#{method}")
|
24
|
+
end
|
25
|
+
begin
|
26
|
+
send(method, msg)
|
27
|
+
rescue ReceivedDie
|
28
|
+
break
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
alias_method :process, :process!
|
33
|
+
|
34
|
+
def die_response(msg)
|
35
|
+
raise ReceivedDie
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_address(peer)
|
39
|
+
@addresses[peer.name] ||= PeerInfo.new(peer: peer, channel: peer.channel )
|
40
|
+
end
|
41
|
+
|
42
|
+
def peer_address_response(msg)
|
43
|
+
add_address(msg.peer)
|
44
|
+
if msg.initial?
|
45
|
+
send_msg msg.peer, my_address_message
|
46
|
+
end
|
47
|
+
puts "#{name} added #{msg.peer.name}" if debug
|
48
|
+
end
|
49
|
+
|
50
|
+
def key_exchange_response(msg)
|
51
|
+
info = info_for(msg.peer)
|
52
|
+
if msg.initial?
|
53
|
+
@p = msg.p
|
54
|
+
@g = msg.g
|
55
|
+
end
|
56
|
+
info.update(p: p, g: g, pubkey: msg.pubkey)
|
57
|
+
info.set_shared_secret(privkey)
|
58
|
+
if debug
|
59
|
+
puts "#{name} will use p = #{p}"
|
60
|
+
puts "#{name} will use g = #{g}"
|
61
|
+
puts "#{name} thinks #{msg.peer.name} has pubkey #{msg.pubkey}"
|
62
|
+
puts "#{name} generated secret #{info.shared_secret} for #{msg.peer.name}"
|
63
|
+
end
|
64
|
+
my_pubkey_msg = Messages::KeyExchange.new(peer: self, pubkey: pubkey, initial: false)
|
65
|
+
send_msg msg.peer, my_pubkey_msg if msg.initial?
|
66
|
+
end
|
67
|
+
|
68
|
+
def datum_response(msg)
|
69
|
+
data = msg.decrypt(key: info_for(msg.peer).session_key)
|
70
|
+
puts "#{name} got message containing #{data} from #{msg.peer.name}" if debug
|
71
|
+
if msg.initial?
|
72
|
+
encrypted = encrypted_message_for(msg.peer, message: data, initial: false)
|
73
|
+
send_msg(msg.peer, encrypted)
|
74
|
+
end
|
75
|
+
@received_messages << ReceivedMessage.new(from: msg.peer.name, contents: data)
|
76
|
+
end
|
77
|
+
|
78
|
+
def when_ready
|
79
|
+
loop do
|
80
|
+
while(channel.empty?)
|
81
|
+
sleep 0.001
|
82
|
+
end
|
83
|
+
yield
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def send_msg(peer, message)
|
88
|
+
puts "#{name} sends #{message.class.to_s.split(':').last} to #{peer.name}" if debug
|
89
|
+
peer.channel.enq(message)
|
90
|
+
end
|
91
|
+
|
92
|
+
def info_for(peer)
|
93
|
+
addresses.fetch(peer.name)
|
94
|
+
end
|
95
|
+
|
96
|
+
def pubkey
|
97
|
+
raise RuntimeError.new("Can't generate public key until p has been set") if p.nil?
|
98
|
+
raise RuntimeError.new("Can't generate public key until g has been set") if g.nil?
|
99
|
+
@pubkey ||= g.modexp(privkey, p)
|
100
|
+
end
|
101
|
+
|
102
|
+
def privkey
|
103
|
+
raise RuntimeError.new("Can't generate private key until p has been set") if p.nil?
|
104
|
+
@privkey ||= rand(1..0xffffffff) % p
|
105
|
+
end
|
106
|
+
|
107
|
+
def my_address_message(initial: false)
|
108
|
+
Messages::PeerAddress.new(peer: self, channel: self.channel, initial: initial)
|
109
|
+
end
|
110
|
+
|
111
|
+
def encrypted_message_for(peer, message: , initial: false)
|
112
|
+
key = info_for(peer).session_key
|
113
|
+
iv = Random.new.bytes(16)
|
114
|
+
encrypted = (iv + message.encrypt_cbc(key: key, iv: iv))
|
115
|
+
Messages::Datum.new(peer: self, contents: encrypted, initial: initial)
|
116
|
+
end
|
117
|
+
|
118
|
+
def valid_pubkey?
|
119
|
+
pubkey < p
|
120
|
+
end
|
121
|
+
|
122
|
+
def invalid_pubkey?
|
123
|
+
!valid_pubkey?
|
124
|
+
end
|
125
|
+
|
126
|
+
attr_reader :addresses, :channel, :name, :debug, :p, :g, :received_messages
|
127
|
+
alias_method :debug?, :debug
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module DiffieHellman
|
3
|
+
class PeerInfo
|
4
|
+
def initialize(peer: , channel: , pubkey: nil, p: nil, g: nil)
|
5
|
+
@peer = peer
|
6
|
+
@channel = channel
|
7
|
+
@pubkey = pubkey
|
8
|
+
@p = p
|
9
|
+
@g = g
|
10
|
+
end
|
11
|
+
attr_reader :peer, :channel, :shared_secret
|
12
|
+
attr_accessor :p, :g, :pubkey
|
13
|
+
|
14
|
+
def to_h
|
15
|
+
{
|
16
|
+
name: peer.name,
|
17
|
+
p: p,
|
18
|
+
g: g,
|
19
|
+
pubkey: pubkey,
|
20
|
+
secret: shared_secret
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_shared_secret(privkey, override: nil)
|
25
|
+
@shared_secret = override ? override : pubkey.modexp(privkey, p)
|
26
|
+
end
|
27
|
+
|
28
|
+
def session_key
|
29
|
+
if shared_secret.nil?
|
30
|
+
raise ArgumentError.new("Session key requires a shared secret")
|
31
|
+
end
|
32
|
+
@session_key ||= CryptoToolchain::Utilities::SHA1.bindigest(shared_secret.to_s)[0..15]
|
33
|
+
end
|
34
|
+
|
35
|
+
def update(hsh)
|
36
|
+
hsh.each do |k, v|
|
37
|
+
self.send("#{k}=", v) unless v.nil?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CryptoToolchain
|
2
|
+
module DiffieHellman
|
3
|
+
class ReceivedMessage
|
4
|
+
def initialize(from: , contents: )
|
5
|
+
@from = from
|
6
|
+
@contents = contents
|
7
|
+
end
|
8
|
+
|
9
|
+
def ==(other)
|
10
|
+
unless other.is_a?(CryptoToolchain::DiffieHellman::ReceivedMessage)
|
11
|
+
raise ArgumentError.new("Cannot coerce #{other.clas} to ReceivedMessage")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
attr_reader :from, :contents
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require "crypto_toolchain/diffie_hellman/messages"
|
2
|
+
require "crypto_toolchain/diffie_hellman/peer_info"
|
3
|
+
require "crypto_toolchain/diffie_hellman/peer"
|
4
|
+
require "crypto_toolchain/diffie_hellman/mitm"
|
5
|
+
require "crypto_toolchain/diffie_hellman/received_message"
|
6
|
+
module CryptoToolchain
|
7
|
+
module DiffieHellman
|
8
|
+
ReceivedDie = Class.new(RuntimeError)
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
class Integer
|
3
|
+
def to_hex_string
|
4
|
+
ret = to_s(16)
|
5
|
+
if ret.length.odd?
|
6
|
+
ret = "0#{ret}"
|
7
|
+
end
|
8
|
+
ret
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_bin_string
|
12
|
+
to_hex_string.from_hex
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_bits(pack_arg = "L>")
|
16
|
+
[self].pack(pack_arg).to_bits
|
17
|
+
end
|
18
|
+
|
19
|
+
def lrot(num)
|
20
|
+
((self << num) & 0xffffffff) |
|
21
|
+
((self & 0xffffffff) >> (32 - num))
|
22
|
+
end
|
23
|
+
|
24
|
+
def rrot(num)
|
25
|
+
((self & 0xffffffff) >> num) |
|
26
|
+
((self << (32 - num)) & 0xffffffff)
|
27
|
+
end
|
28
|
+
|
29
|
+
# From Wikipedia:
|
30
|
+
# https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Pseudocode
|
31
|
+
def invmod(n)
|
32
|
+
a = self
|
33
|
+
t = 0
|
34
|
+
new_t = 1
|
35
|
+
r = n
|
36
|
+
new_r = a
|
37
|
+
while new_r != 0
|
38
|
+
quotient = r / new_r
|
39
|
+
t, new_t = new_t, (t - quotient * new_t)
|
40
|
+
r, new_r = new_r, (r - quotient * new_r)
|
41
|
+
end
|
42
|
+
raise ArgumentError.new("#{self} is not invertible") if r > 1
|
43
|
+
t += n if t < 0
|
44
|
+
t
|
45
|
+
end
|
46
|
+
alias_method :mod_inverse, :invmod
|
47
|
+
alias_method :modinv, :invmod
|
48
|
+
|
49
|
+
# https://rosettacode.org/wiki/Nth_root#Ruby
|
50
|
+
# (with modifications)
|
51
|
+
ROUNDING = %i(up down none)
|
52
|
+
def root(n, round: :down)
|
53
|
+
raise ArgumentError.new("round must be in [#{ROUNDING.join(', ')}]") unless ROUNDING.include?(round)
|
54
|
+
raise ArgumentError.new("Can't be called on 0") if self == 0
|
55
|
+
x = self
|
56
|
+
loop do
|
57
|
+
prev = x
|
58
|
+
x = ((n - 1) * prev) + (self / (prev ** (n - 1)))
|
59
|
+
x /= n
|
60
|
+
break if (prev - x) <= 0
|
61
|
+
end
|
62
|
+
if x**n == self
|
63
|
+
x
|
64
|
+
else
|
65
|
+
case round
|
66
|
+
when :up
|
67
|
+
x+1
|
68
|
+
when :down
|
69
|
+
x
|
70
|
+
when :none
|
71
|
+
raise ArgumentError.new("#{self} has no #{n}th root")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def modexp(exponent, mod)
|
77
|
+
raise ArgumentError.new("Exponent must be non-negative") if exponent < 0
|
78
|
+
product = 1
|
79
|
+
base = self % mod
|
80
|
+
while exponent > 0
|
81
|
+
if exponent & 0x01 == 1
|
82
|
+
product = (product * base) % mod
|
83
|
+
end
|
84
|
+
exponent = exponent >> 1
|
85
|
+
base = (base**2) % mod
|
86
|
+
end
|
87
|
+
product
|
88
|
+
end
|
89
|
+
alias_method :modpow, :modexp
|
90
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
class Object
|
3
|
+
# Transcribed from http://cryptopals.com/sets/5/challenges/40
|
4
|
+
def chinese_remainder(residues, mods)
|
5
|
+
mod_product = ->(without) { mods.inject(:*) / without }
|
6
|
+
sum = 0
|
7
|
+
residues.zip(mods) do |(residue, mod)|
|
8
|
+
mp = mod_product.call(mod)
|
9
|
+
sum += residue * mp * mp.invmod(mod)
|
10
|
+
end
|
11
|
+
sum % mods.inject(:*)
|
12
|
+
end
|
13
|
+
|
14
|
+
def numberize(n)
|
15
|
+
if n.respond_to?(:to_number)
|
16
|
+
n.to_number
|
17
|
+
elsif n.is_a?(Numeric)
|
18
|
+
n
|
19
|
+
else
|
20
|
+
raise ArgumentError, "#{n} cannot be numberized"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|