coderrr-rtunnel 0.3.9 → 0.4.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.
- data/CHANGELOG +13 -0
- data/LICENSE +21 -0
- data/Manifest +48 -0
- data/README.markdown +40 -15
- data/Rakefile +31 -4
- data/bin/rtunnel_client +2 -1
- data/bin/rtunnel_server +2 -1
- data/lib/rtunnel.rb +20 -0
- data/lib/rtunnel/client.rb +308 -0
- data/lib/rtunnel/command_processor.rb +62 -0
- data/lib/rtunnel/command_protocol.rb +50 -0
- data/lib/rtunnel/commands.rb +233 -0
- data/lib/rtunnel/connection_id.rb +24 -0
- data/lib/rtunnel/core.rb +58 -0
- data/lib/rtunnel/crypto.rb +106 -0
- data/lib/rtunnel/frame_protocol.rb +34 -0
- data/lib/rtunnel/io_extensions.rb +54 -0
- data/lib/rtunnel/leak.rb +35 -0
- data/lib/rtunnel/rtunnel_client_cmd.rb +41 -0
- data/lib/rtunnel/rtunnel_server_cmd.rb +32 -0
- data/lib/rtunnel/server.rb +351 -0
- data/lib/rtunnel/socket_factory.rb +119 -0
- data/test/command_stubs.rb +77 -0
- data/test/protocol_mocks.rb +43 -0
- data/test/scenario_connection.rb +109 -0
- data/test/test_client.rb +48 -0
- data/test/test_command_protocol.rb +82 -0
- data/test/test_commands.rb +49 -0
- data/test/test_connection_id.rb +30 -0
- data/test/test_crypto.rb +127 -0
- data/test/test_frame_protocol.rb +109 -0
- data/test/test_io_extensions.rb +70 -0
- data/test/test_server.rb +70 -0
- data/test/test_socket_factory.rb +42 -0
- data/test/test_tunnel.rb +186 -0
- data/test_data/authorized_keys2 +4 -0
- data/test_data/known_hosts +4 -0
- data/test_data/random_rsa_key +27 -0
- data/test_data/ssh_host_dsa_key +12 -0
- data/test_data/ssh_host_rsa_key +27 -0
- data/tests/_ab_test.rb +16 -0
- data/tests/_stress_test.rb +96 -0
- data/tests/lo_http_server.rb +55 -0
- metadata +67 -27
- data/ab_test.rb +0 -23
- data/lib/client.rb +0 -150
- data/lib/cmds.rb +0 -166
- data/lib/core.rb +0 -58
- data/lib/rtunnel_client_cmd.rb +0 -23
- data/lib/rtunnel_server_cmd.rb +0 -18
- data/lib/server.rb +0 -197
- data/rtunnel.gemspec +0 -18
- data/rtunnel_client.rb +0 -3
- data/rtunnel_server.rb +0 -3
- data/stress_test.rb +0 -68
@@ -0,0 +1,62 @@
|
|
1
|
+
# The plumbing for processing RTunnel commands.
|
2
|
+
module RTunnel::CommandProcessor
|
3
|
+
# Called by CommandProtocol to process a command.
|
4
|
+
def receive_command(command)
|
5
|
+
case @last_command = command
|
6
|
+
when RTunnel::CloseConnectionCommand
|
7
|
+
process_close_connection command.connection_id
|
8
|
+
when RTunnel::CreateConnectionCommand
|
9
|
+
process_create_connection command.connection_id
|
10
|
+
when RTunnel::GenerateSessionKeyCommand
|
11
|
+
process_generate_session_key command.public_key_fp
|
12
|
+
when RTunnel::KeepAliveCommand
|
13
|
+
process_keep_alive
|
14
|
+
when RTunnel::RemoteListenCommand
|
15
|
+
process_remote_listen command.address
|
16
|
+
when RTunnel::SendDataCommand
|
17
|
+
process_send_data command.connection_id, command.data
|
18
|
+
when RTunnel::SetSessionKeyCommand
|
19
|
+
process_set_session_key command.encrypted_keys
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Override to process CloseConnectionCommand. Do NOT call super.
|
24
|
+
def process_close_connection(connection_id)
|
25
|
+
unexpected_command @last_command
|
26
|
+
end
|
27
|
+
|
28
|
+
# Override to process CreateConnectionCommand. Do NOT call super.
|
29
|
+
def process_create_connection(connection_id)
|
30
|
+
unexpected_command @last_command
|
31
|
+
end
|
32
|
+
|
33
|
+
# Override to process GenerateSessionKeyCommand. Do NOT call super.
|
34
|
+
def process_generate_session_key(public_key_fp)
|
35
|
+
unexpected_command @last_command
|
36
|
+
end
|
37
|
+
|
38
|
+
# Override to process KeepAliveCommand. Do NOT call super.
|
39
|
+
def process_keep_alive
|
40
|
+
unexpected_command @last_command
|
41
|
+
end
|
42
|
+
|
43
|
+
# Override to process RemoteListenCommand. Do NOT call super.
|
44
|
+
def process_remote_listen(address)
|
45
|
+
unexpected_command @last_command
|
46
|
+
end
|
47
|
+
|
48
|
+
# Override to process SendDataCommand. Do NOT call super.
|
49
|
+
def process_send_data(connection_id, data)
|
50
|
+
unexpected_command @last_command
|
51
|
+
end
|
52
|
+
|
53
|
+
# Override to process SetSessionKeyCommand. Do NOT call super.
|
54
|
+
def process_set_session_key(encrypted_keys)
|
55
|
+
unexpected_command @last_command
|
56
|
+
end
|
57
|
+
|
58
|
+
# Override to handle commands that haven't been overridden.
|
59
|
+
def unexpected_command(command)
|
60
|
+
W "Unexpected command: #{command.inspect}"
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RTunnel::CommandProtocol
|
2
|
+
include RTunnel::FrameProtocol
|
3
|
+
|
4
|
+
# Sends an encoded RTunnel command as a frame.
|
5
|
+
def send_command(command)
|
6
|
+
command_str = command.to_encoded_str
|
7
|
+
if @out_command_hasher
|
8
|
+
send_frame command_str + @out_command_hasher.hash(command_str)
|
9
|
+
else
|
10
|
+
send_frame command_str
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Decodes a frame into an RTunnel command.
|
15
|
+
def receive_frame(frame)
|
16
|
+
ioframe = StringIO.new frame
|
17
|
+
begin
|
18
|
+
command = RTunnel::Command.decode ioframe
|
19
|
+
rescue Exception => e
|
20
|
+
receive_bad_frame frame, e
|
21
|
+
return
|
22
|
+
end
|
23
|
+
if @in_command_hasher
|
24
|
+
signature = ioframe.read
|
25
|
+
if signature != @in_command_hasher.hash(frame[0...(-signature.length)])
|
26
|
+
receive_bad_frame frame, :bad_signature
|
27
|
+
return
|
28
|
+
end
|
29
|
+
end
|
30
|
+
receive_command command
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets a cryptographic hasher that will be used to sign outgoing commands.
|
34
|
+
# Once a hasher is set, all outgoing frames will be signed.
|
35
|
+
def outgoing_command_hasher=(hasher)
|
36
|
+
@out_command_hasher = hasher
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sets a cryptographic hasher that will be used to verify incoming commands.
|
40
|
+
# Once a hasher is set, all incoming frames without a matching signature will
|
41
|
+
# be ignored.
|
42
|
+
def incoming_command_hasher=(hasher)
|
43
|
+
@in_command_hasher = hasher
|
44
|
+
end
|
45
|
+
|
46
|
+
# Override to handle frames with corrupted or absent signatures.
|
47
|
+
def receive_bad_frame(frame, exception)
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
class RTunnel::Command
|
4
|
+
# Associates command codes with the classes implementing them.
|
5
|
+
class Registry
|
6
|
+
def initialize
|
7
|
+
@classes = {}
|
8
|
+
@codes = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def register(klass, command_code)
|
12
|
+
if @codes.has_key? command_code
|
13
|
+
raise "Command code #{command_code} already used for #{@codes[command_code].name}"
|
14
|
+
end
|
15
|
+
|
16
|
+
@codes[klass] = command_code
|
17
|
+
@classes[command_code] = klass
|
18
|
+
end
|
19
|
+
|
20
|
+
def class_for(command_code)
|
21
|
+
@classes[command_code]
|
22
|
+
end
|
23
|
+
|
24
|
+
def code_for(klass)
|
25
|
+
@codes[klass]
|
26
|
+
end
|
27
|
+
|
28
|
+
def codes_and_classes
|
29
|
+
ret_val = []
|
30
|
+
@codes.each { |klass, code| ret_val << [code, klass.name] }
|
31
|
+
ret_val.sort!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
@@registry = Registry.new
|
36
|
+
def self.registry
|
37
|
+
@@registry
|
38
|
+
end
|
39
|
+
|
40
|
+
# subclasses must call this to register and declare their command code
|
41
|
+
def self.command_code(code)
|
42
|
+
registry.register self, code
|
43
|
+
end
|
44
|
+
|
45
|
+
# subclasses should override this (and add to the result)
|
46
|
+
# to provide a debug string
|
47
|
+
def to_s
|
48
|
+
self.class.name
|
49
|
+
end
|
50
|
+
|
51
|
+
# subclasses should override this and call super
|
52
|
+
# before performing their own initialization
|
53
|
+
def initialize_from_io(io)
|
54
|
+
return self
|
55
|
+
end
|
56
|
+
|
57
|
+
# Encode this command to a IO / IOString.
|
58
|
+
def encode(io)
|
59
|
+
io.write RTunnel::Command.registry.code_for(self.class)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Produce a string with an encoding of this command.
|
63
|
+
def to_encoded_str
|
64
|
+
string_io = StringIO.new
|
65
|
+
self.encode string_io
|
66
|
+
string_io.string
|
67
|
+
end
|
68
|
+
|
69
|
+
# Decode a Command instance from a IO / IOString.
|
70
|
+
def self.decode(io)
|
71
|
+
return nil unless code = io.getc
|
72
|
+
klass = registry.class_for code.chr
|
73
|
+
return nil unless klass
|
74
|
+
|
75
|
+
command = klass.new
|
76
|
+
command.initialize_from_io io
|
77
|
+
end
|
78
|
+
|
79
|
+
# Printable string containing all the codes and their classes.
|
80
|
+
def self.printable_codes
|
81
|
+
printable = ''
|
82
|
+
registry.codes_and_classes.each do |code_and_class|
|
83
|
+
printable << "#{code_and_class.first}: #{code_and_class.last}\n"
|
84
|
+
end
|
85
|
+
return printable
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class RTunnel::ConnectionCommand < RTunnel::Command
|
90
|
+
attr_reader :connection_id
|
91
|
+
|
92
|
+
def initialize(connection_id = nil)
|
93
|
+
super()
|
94
|
+
@connection_id = connection_id
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_s
|
98
|
+
super + "/id=#{connection_id}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize_from_io(io)
|
102
|
+
super
|
103
|
+
@connection_id = io.read_varstring
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
def encode(io)
|
108
|
+
super
|
109
|
+
io.write_varstring @connection_id
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class RTunnel::CreateConnectionCommand < RTunnel::ConnectionCommand
|
114
|
+
command_code 'C'
|
115
|
+
end
|
116
|
+
|
117
|
+
class RTunnel::CloseConnectionCommand < RTunnel::ConnectionCommand
|
118
|
+
command_code 'X'
|
119
|
+
end
|
120
|
+
|
121
|
+
class RTunnel::SendDataCommand < RTunnel::ConnectionCommand
|
122
|
+
command_code 'D'
|
123
|
+
|
124
|
+
attr_reader :data
|
125
|
+
|
126
|
+
def initialize(connection_id = nil, data = nil)
|
127
|
+
super(connection_id)
|
128
|
+
@data = data
|
129
|
+
end
|
130
|
+
|
131
|
+
def initialize_from_io(io)
|
132
|
+
super
|
133
|
+
@data = io.read_varstring
|
134
|
+
self
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_s
|
138
|
+
super + "/data=#{data}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def encode(io)
|
142
|
+
super
|
143
|
+
io.write_varstring @data
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class RTunnel::RemoteListenCommand < RTunnel::Command
|
148
|
+
command_code 'L'
|
149
|
+
|
150
|
+
attr_reader :address
|
151
|
+
|
152
|
+
def initialize(address = nil)
|
153
|
+
super()
|
154
|
+
@address = address
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_s
|
158
|
+
super + "/address=#{address}"
|
159
|
+
end
|
160
|
+
|
161
|
+
def initialize_from_io(io)
|
162
|
+
super
|
163
|
+
@address = io.read_varstring
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
def encode(io)
|
168
|
+
super
|
169
|
+
io.write_varstring address
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class RTunnel::KeepAliveCommand < RTunnel::Command
|
174
|
+
command_code 'A'
|
175
|
+
|
176
|
+
def initialize_from_io(io)
|
177
|
+
super
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class RTunnel::GenerateSessionKeyCommand < RTunnel::Command
|
182
|
+
command_code 'S'
|
183
|
+
|
184
|
+
attr_reader :public_key_fp
|
185
|
+
|
186
|
+
def initialize(public_key_fp = nil)
|
187
|
+
super()
|
188
|
+
@public_key_fp = public_key_fp
|
189
|
+
end
|
190
|
+
|
191
|
+
def to_s
|
192
|
+
super + "/pubkey_fp=#{@public_key_fp.inspect}"
|
193
|
+
end
|
194
|
+
|
195
|
+
def initialize_from_io(io)
|
196
|
+
super
|
197
|
+
@public_key_fp = io.read_varstring
|
198
|
+
self
|
199
|
+
end
|
200
|
+
|
201
|
+
def encode(io)
|
202
|
+
super
|
203
|
+
io.write_varstring @public_key_fp
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
class RTunnel::SetSessionKeyCommand < RTunnel::Command
|
208
|
+
command_code 'K'
|
209
|
+
|
210
|
+
attr_reader :encrypted_keys
|
211
|
+
|
212
|
+
def initialize(encrypted_keys = nil)
|
213
|
+
super()
|
214
|
+
@encrypted_keys = encrypted_keys
|
215
|
+
end
|
216
|
+
|
217
|
+
def to_s
|
218
|
+
super + "/enc_keys=#{@encrypted_key.inspect}"
|
219
|
+
end
|
220
|
+
|
221
|
+
def initialize_from_io(io)
|
222
|
+
super
|
223
|
+
@encrypted_keys = io.read_varstring
|
224
|
+
self
|
225
|
+
end
|
226
|
+
|
227
|
+
def encode(io)
|
228
|
+
super
|
229
|
+
io.write_varstring @encrypted_keys
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# TODO(not_me): this file (and its tests) cry for a DSL
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
# Unique ID generation functionality.
|
5
|
+
module RTunnel::ConnectionId
|
6
|
+
def self.new_cipher
|
7
|
+
cipher = OpenSSL::Cipher::Cipher.new 'aes-128-ecb'
|
8
|
+
cipher.encrypt
|
9
|
+
cipher.key, cipher.iv = cipher.random_key, cipher.random_iv
|
10
|
+
cipher
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.new_counter
|
14
|
+
'0' * 16
|
15
|
+
end
|
16
|
+
|
17
|
+
def new_connection_id
|
18
|
+
@session_id_cipher ||= RTunnel::ConnectionId.new_cipher
|
19
|
+
@session_id_counter ||= RTunnel::ConnectionId.new_counter
|
20
|
+
connection_id = @session_id_cipher.update @session_id_counter
|
21
|
+
@session_id_counter.succ!
|
22
|
+
Base64.encode64(connection_id).strip
|
23
|
+
end
|
24
|
+
end
|
data/lib/rtunnel/core.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module RTunnel
|
4
|
+
DEFAULT_CONTROL_PORT = 19050
|
5
|
+
TUNNEL_TIMEOUT = 10
|
6
|
+
KEEP_ALIVE_INTERVAL = 2
|
7
|
+
|
8
|
+
class AbortProgramException < Exception
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module RTunnel::Logging
|
14
|
+
def init_log(options = {})
|
15
|
+
# TODO(costan): parse logging options
|
16
|
+
if options[:to]
|
17
|
+
@log = options[:to].instance_variable_get(:@log).dup
|
18
|
+
else
|
19
|
+
@log = Logger.new(STDERR)
|
20
|
+
@log.level = Logger::ERROR
|
21
|
+
end
|
22
|
+
if options[:level]
|
23
|
+
@log.level = Logger::const_get(options[:level].upcase.to_sym)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def D(message)
|
28
|
+
@log.debug message
|
29
|
+
end
|
30
|
+
|
31
|
+
def W(message)
|
32
|
+
@log.warn message
|
33
|
+
end
|
34
|
+
|
35
|
+
def I(message)
|
36
|
+
@log.info message
|
37
|
+
end
|
38
|
+
|
39
|
+
def E(message)
|
40
|
+
@log.error message
|
41
|
+
end
|
42
|
+
|
43
|
+
def F(message)
|
44
|
+
@log.fatal message
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module RTunnel
|
49
|
+
# Resolve the given address to an IP.
|
50
|
+
# The address can have the following formats: host; host:port; ip; ip:port;
|
51
|
+
def self.resolve_address(address, timeout_sec = 5)
|
52
|
+
host, rest = address.split(':', 2)
|
53
|
+
ip = timeout(timeout_sec) { Resolv.getaddress(host) }
|
54
|
+
rest ? "#{ip}:#{rest}" : ip
|
55
|
+
rescue Exception
|
56
|
+
raise AbortProgramException, "Error resolving #{host}"
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
require 'openssl'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'net/ssh'
|
7
|
+
|
8
|
+
module RTunnel::Crypto
|
9
|
+
# Reads all the keys from an openssh known_hosts or authorized_keys2 file.
|
10
|
+
def self.read_authorized_keys(file_name)
|
11
|
+
keys = []
|
12
|
+
File.read(file_name).each_line do |line|
|
13
|
+
pubkey_match = /ssh-\w*\s*(\S*)/.match line
|
14
|
+
next unless pubkey_match
|
15
|
+
pubkey_blob = pubkey_match[1].unpack('m*').first
|
16
|
+
keys << Net::SSH::Buffer.new(pubkey_blob).read_key
|
17
|
+
end
|
18
|
+
keys
|
19
|
+
end
|
20
|
+
|
21
|
+
# Loads a private key from an openssh key file.
|
22
|
+
def self.read_private_key(file_name)
|
23
|
+
Net::SSH::KeyFactory.load_private_key file_name
|
24
|
+
end
|
25
|
+
|
26
|
+
# Computes a string that represents the key. Different keys should
|
27
|
+
# map out to different fingerprints.
|
28
|
+
def self.key_fingerprint(key)
|
29
|
+
key.public_key.to_der
|
30
|
+
end
|
31
|
+
|
32
|
+
# Encrypts some data with a public key. The matching private key will be
|
33
|
+
# required to decrypt the data.
|
34
|
+
def self.encrypt_with_key(key, data)
|
35
|
+
if key.kind_of? OpenSSL::PKey::RSA
|
36
|
+
key.public_encrypt data, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
37
|
+
elsif key.kind_of? OpenSSL::PKey::DSA
|
38
|
+
key.public_encrypt encrypted_data
|
39
|
+
else
|
40
|
+
raise 'Unsupported key type'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Decrypts data that was previously encrypted with encrypt_with_key.
|
45
|
+
def self.decrypt_with_key(key, encrypted_data)
|
46
|
+
if key.kind_of? OpenSSL::PKey::RSA
|
47
|
+
key.private_decrypt encrypted_data, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
48
|
+
elsif key.kind_of? OpenSSL::PKey::DSA
|
49
|
+
key.private_decrypt encrypted_data
|
50
|
+
else
|
51
|
+
raise 'Unsupported key type'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Loads public keys to be used by a server.
|
56
|
+
def self.load_public_keys(file_name)
|
57
|
+
key_list = read_authorized_keys file_name
|
58
|
+
RTunnel::Crypto::KeySet.new key_list
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# A set of keys used by a server to authenticate clients.
|
63
|
+
class RTunnel::Crypto::KeySet
|
64
|
+
def initialize(key_list)
|
65
|
+
@keys_by_fp = {}
|
66
|
+
key_list.each { |k| @keys_by_fp[RTunnel::Crypto.key_fingerprint(k)] = k }
|
67
|
+
end
|
68
|
+
|
69
|
+
def [](key_fp)
|
70
|
+
@keys_by_fp[key_fp]
|
71
|
+
end
|
72
|
+
|
73
|
+
def length
|
74
|
+
@keys_by_fp.length
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# A cryptographically secure hasher. Instances will hash the data
|
79
|
+
class RTunnel::Crypto::Hasher
|
80
|
+
attr_reader :key
|
81
|
+
|
82
|
+
def initialize(key = nil)
|
83
|
+
@key = key || RTunnel::Crypto::Hasher.random_key
|
84
|
+
@cipher = OpenSSL::Cipher::Cipher.new 'aes-128-cbc'
|
85
|
+
@cipher.encrypt
|
86
|
+
iokey = StringIO.new @key
|
87
|
+
@cipher.key = iokey.read_varstring
|
88
|
+
@cipher.iv = iokey.read_varstring
|
89
|
+
end
|
90
|
+
|
91
|
+
# Creates a hash for the given data. Warning: this method is not idempotent.
|
92
|
+
# The intent is that the same hash can be produced by another hasher that is
|
93
|
+
# initialized with the same key and has been fed the same data.
|
94
|
+
def hash(data)
|
95
|
+
@cipher.update Digest::SHA2.digest(data)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Produces a random key for the hasher.
|
99
|
+
def self.random_key
|
100
|
+
cipher = OpenSSL::Cipher::Cipher.new 'aes-128-cbc'
|
101
|
+
iokey = StringIO.new
|
102
|
+
iokey.write_varstring cipher.random_key
|
103
|
+
iokey.write_varstring cipher.random_iv
|
104
|
+
iokey.string
|
105
|
+
end
|
106
|
+
end
|