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