crypto_toolchain 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +51 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +3 -0
  6. data/Guardfile +15 -0
  7. data/LICENSE +21 -0
  8. data/README.md +95 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/crypto_toolchain.gemspec +33 -0
  13. data/exe/crypto +7 -0
  14. data/lib/crypto_toolchain/black_boxes/aes_ctr_editor.rb +25 -0
  15. data/lib/crypto_toolchain/black_boxes/cbc_bitflip_target.rb +33 -0
  16. data/lib/crypto_toolchain/black_boxes/cbc_iv_equals_key_target.rb +35 -0
  17. data/lib/crypto_toolchain/black_boxes/cbc_padding_oracle.rb +44 -0
  18. data/lib/crypto_toolchain/black_boxes/ctr_bitflip_target.rb +32 -0
  19. data/lib/crypto_toolchain/black_boxes/dsa_keypair.rb +50 -0
  20. data/lib/crypto_toolchain/black_boxes/ecb_cut_and_paste_target.rb +50 -0
  21. data/lib/crypto_toolchain/black_boxes/ecb_interpolate_chosen_plaintext_oracle.rb +28 -0
  22. data/lib/crypto_toolchain/black_boxes/ecb_or_cbc_encryptor.rb +47 -0
  23. data/lib/crypto_toolchain/black_boxes/ecb_prepend_chosen_plaintext_oracle.rb +23 -0
  24. data/lib/crypto_toolchain/black_boxes/md4_mac.rb +20 -0
  25. data/lib/crypto_toolchain/black_boxes/mt_19937_stream_cipher.rb +47 -0
  26. data/lib/crypto_toolchain/black_boxes/netcat_cbc_padding_oracle.rb +33 -0
  27. data/lib/crypto_toolchain/black_boxes/rsa_keypair.rb +83 -0
  28. data/lib/crypto_toolchain/black_boxes/rsa_parity_oracle.rb +14 -0
  29. data/lib/crypto_toolchain/black_boxes/rsa_unpadded_message_recovery_oracle.rb +24 -0
  30. data/lib/crypto_toolchain/black_boxes/sha1_mac.rb +20 -0
  31. data/lib/crypto_toolchain/black_boxes.rb +22 -0
  32. data/lib/crypto_toolchain/diffie_hellman/messages.rb +53 -0
  33. data/lib/crypto_toolchain/diffie_hellman/mitm.rb +52 -0
  34. data/lib/crypto_toolchain/diffie_hellman/peer.rb +130 -0
  35. data/lib/crypto_toolchain/diffie_hellman/peer_info.rb +43 -0
  36. data/lib/crypto_toolchain/diffie_hellman/received_message.rb +17 -0
  37. data/lib/crypto_toolchain/diffie_hellman.rb +10 -0
  38. data/lib/crypto_toolchain/extensions/integer_extensions.rb +90 -0
  39. data/lib/crypto_toolchain/extensions/object_extensions.rb +24 -0
  40. data/lib/crypto_toolchain/extensions/string_extensions.rb +263 -0
  41. data/lib/crypto_toolchain/extensions.rb +8 -0
  42. data/lib/crypto_toolchain/srp/client.rb +51 -0
  43. data/lib/crypto_toolchain/srp/framework.rb +55 -0
  44. data/lib/crypto_toolchain/srp/server.rb +38 -0
  45. data/lib/crypto_toolchain/srp/simple_client.rb +32 -0
  46. data/lib/crypto_toolchain/srp/simple_server.rb +68 -0
  47. data/lib/crypto_toolchain/srp.rb +14 -0
  48. data/lib/crypto_toolchain/tools/aes_ctr_recoverer.rb +30 -0
  49. data/lib/crypto_toolchain/tools/cbc_bitflip_attack.rb +30 -0
  50. data/lib/crypto_toolchain/tools/cbc_iv_equals_key_attack.rb +30 -0
  51. data/lib/crypto_toolchain/tools/cbc_padding_oracle_attack.rb +51 -0
  52. data/lib/crypto_toolchain/tools/ctr_bitflip_attack.rb +24 -0
  53. data/lib/crypto_toolchain/tools/determine_blocksize.rb +20 -0
  54. data/lib/crypto_toolchain/tools/dsa_recover_nonce_from_signatures.rb +53 -0
  55. data/lib/crypto_toolchain/tools/dsa_recover_private_key_from_nonce.rb +39 -0
  56. data/lib/crypto_toolchain/tools/ecb_cut_and_paste_attack.rb +47 -0
  57. data/lib/crypto_toolchain/tools/ecb_interpolate_chosen_plaintext_attack.rb +72 -0
  58. data/lib/crypto_toolchain/tools/ecb_prepend_chosen_plaintext_attack.rb +42 -0
  59. data/lib/crypto_toolchain/tools/interactive_xor.rb +51 -0
  60. data/lib/crypto_toolchain/tools/low_exponent_rsa_signature_forgery.rb +27 -0
  61. data/lib/crypto_toolchain/tools/md4_length_extension_attack.rb +30 -0
  62. data/lib/crypto_toolchain/tools/mt_19937_seed_recoverer.rb +27 -0
  63. data/lib/crypto_toolchain/tools/mt_19937_stream_cipher_seed_recoverer.rb +40 -0
  64. data/lib/crypto_toolchain/tools/rsa_broadcast_attack.rb +21 -0
  65. data/lib/crypto_toolchain/tools/rsa_parity_oracle_attack.rb +33 -0
  66. data/lib/crypto_toolchain/tools/rsa_unpadded_message_recovery_attack.rb +49 -0
  67. data/lib/crypto_toolchain/tools/sha1_length_extension_attack.rb +30 -0
  68. data/lib/crypto_toolchain/tools.rb +31 -0
  69. data/lib/crypto_toolchain/utilities/hmac.rb +73 -0
  70. data/lib/crypto_toolchain/utilities/md4.rb +106 -0
  71. data/lib/crypto_toolchain/utilities/mt_19937.rb +218 -0
  72. data/lib/crypto_toolchain/utilities/sha1.rb +95 -0
  73. data/lib/crypto_toolchain/utilities.rb +9 -0
  74. data/lib/crypto_toolchain/version.rb +3 -0
  75. data/lib/crypto_toolchain.rb +34 -0
  76. 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,14 @@
1
+ module CryptoToolchain
2
+ module BlackBoxes
3
+ class RSAParityOracle
4
+ def initialize(keypair)
5
+ @keypair = keypair
6
+ end
7
+ attr_reader :keypair
8
+
9
+ def execute(ciphertext)
10
+ keypair.decrypt(ciphertext).to_number & 1
11
+ end
12
+ end
13
+ end
14
+ 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
+