shadowsocks_ruby 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 (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/.yardopts +5 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +10 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +160 -0
  10. data/Rakefile +14 -0
  11. data/bin/aruba +17 -0
  12. data/bin/bundler +17 -0
  13. data/bin/console +14 -0
  14. data/bin/cucumber +17 -0
  15. data/bin/einhorn +17 -0
  16. data/bin/einhornsh +17 -0
  17. data/bin/htmldiff +17 -0
  18. data/bin/ldiff +17 -0
  19. data/bin/lrucache_server +16 -0
  20. data/bin/rake +17 -0
  21. data/bin/rspec +17 -0
  22. data/bin/setup +8 -0
  23. data/bin/yard +17 -0
  24. data/bin/yardoc +17 -0
  25. data/bin/yri +17 -0
  26. data/config.example.json +6 -0
  27. data/exe/sslocal-ruby +4 -0
  28. data/exe/ssserver-ruby +4 -0
  29. data/lib/shadowsocks_ruby/app.rb +236 -0
  30. data/lib/shadowsocks_ruby/cipher/cipher.rb +112 -0
  31. data/lib/shadowsocks_ruby/cipher/openssl.rb +81 -0
  32. data/lib/shadowsocks_ruby/cipher/rbnacl.rb +64 -0
  33. data/lib/shadowsocks_ruby/cipher/rc4_md5.rb +54 -0
  34. data/lib/shadowsocks_ruby/cipher/table.rb +65 -0
  35. data/lib/shadowsocks_ruby/cli/sslocal_runner.rb +125 -0
  36. data/lib/shadowsocks_ruby/cli/ssserver_runner.rb +115 -0
  37. data/lib/shadowsocks_ruby/connections/backend_connection.rb +50 -0
  38. data/lib/shadowsocks_ruby/connections/connection.rb +195 -0
  39. data/lib/shadowsocks_ruby/connections/server_connection.rb +63 -0
  40. data/lib/shadowsocks_ruby/connections/tcp/client_connection.rb +43 -0
  41. data/lib/shadowsocks_ruby/connections/tcp/destination_connection.rb +19 -0
  42. data/lib/shadowsocks_ruby/connections/tcp/localbackend_connection.rb +29 -0
  43. data/lib/shadowsocks_ruby/connections/tcp/remoteserver_connection.rb +18 -0
  44. data/lib/shadowsocks_ruby/connections/udp/client_connection.rb +43 -0
  45. data/lib/shadowsocks_ruby/connections/udp/destination_connection.rb +18 -0
  46. data/lib/shadowsocks_ruby/connections/udp/localbackend_connection.rb +29 -0
  47. data/lib/shadowsocks_ruby/connections/udp/remoteserver_connection.rb +18 -0
  48. data/lib/shadowsocks_ruby/protocols/cipher/iv_cipher.rb +99 -0
  49. data/lib/shadowsocks_ruby/protocols/cipher/no_iv_cipher.rb +53 -0
  50. data/lib/shadowsocks_ruby/protocols/cipher/verify_sha1.rb +138 -0
  51. data/lib/shadowsocks_ruby/protocols/obfs/http_simple.rb +189 -0
  52. data/lib/shadowsocks_ruby/protocols/obfs/tls_ticket.rb +342 -0
  53. data/lib/shadowsocks_ruby/protocols/packet/plain.rb +51 -0
  54. data/lib/shadowsocks_ruby/protocols/packet/shadowsocks.rb +88 -0
  55. data/lib/shadowsocks_ruby/protocols/packet/socks5.rb +162 -0
  56. data/lib/shadowsocks_ruby/protocols/protocol.rb +164 -0
  57. data/lib/shadowsocks_ruby/protocols/protocol_stack.rb +117 -0
  58. data/lib/shadowsocks_ruby/util.rb +52 -0
  59. data/lib/shadowsocks_ruby/version.rb +3 -0
  60. data/lib/shadowsocks_ruby.rb +86 -0
  61. data/shadowsocks_ruby.gemspec +48 -0
  62. metadata +290 -0
@@ -0,0 +1,236 @@
1
+ require 'fileutils'
2
+ require 'singleton'
3
+ require 'socket'
4
+ require 'json'
5
+ require 'einhorn'
6
+
7
+ module ShadowsocksRuby
8
+ # App is a singleton object which provide either Shadowsocks Client functionality
9
+ # or Shadowsocks Server functionality. One App startup one EventMachine event loop
10
+ # on one Native Thread / CPU core.
11
+ #
12
+ # Because Ruby MRI has a GIL, it is unable to utilize execution parallelism on
13
+ # multi-core CPU.
14
+ # However, one can use a shared socket manager to spin off a few Apps that can be
15
+ # executed parallelly and let the Kernel to do the load balance things.
16
+ #
17
+ # A few things noticeable when using a shared socket manager:
18
+ # * At present, only {https://github.com/stripe/einhorn Einhorn socket manager} are supported.
19
+ # systemd shared socket manager support is in the plan.
20
+ #
21
+ # * At present, using socket manager could cause TLS1.2 obsfucation protocol's
22
+ # replay attact detection malfunctions, because every process have it's own
23
+ # copy of LRUCache.
24
+ #
25
+ # * Shared socket manager does not work on Windows
26
+ #
27
+ class App
28
+ include Singleton
29
+
30
+ MAX_FAST_SHUTDOWN_SECONDS = 10
31
+
32
+ attr_reader :options
33
+ attr_reader :logger
34
+
35
+ @@options = {}
36
+
37
+ def self.options= options
38
+ @@options = options
39
+ end
40
+
41
+ def initialize
42
+ @options = @@options
43
+
44
+ @logger = Logger.new(STDOUT).tap do |log|
45
+
46
+ if options[:__server]
47
+ log.progname = "ssserver-ruby"
48
+ elsif options[:__client]
49
+ log.progname = "sslocal-ruby"
50
+ end
51
+
52
+ if options[:verbose]
53
+ log.level = Logger::DEBUG
54
+ elsif options[:quiet]
55
+ log.level = Logger::WARN
56
+ else
57
+ log.level = Logger::INFO
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ def run!
64
+ if options[:__server]
65
+ start_server
66
+ elsif options[:__client]
67
+ start_client
68
+ end
69
+ #rescue Exception => e
70
+ # logger.fatal { e.message + "\n" + e.backtrace.join("\n")}
71
+ end
72
+
73
+ def trap_signals
74
+ STDOUT.sync = true
75
+ STDERR.sync = true
76
+ trap('QUIT') do
77
+ self.fast_shutdown('QUIT')
78
+ end
79
+ trap('TERM') do
80
+ self.fast_shutdown('TERM')
81
+ end
82
+ trap('INT') do
83
+ self.fast_shutdown('INT')
84
+ end
85
+ end
86
+
87
+ # TODO: next_tick can't be called from trap context
88
+ def graceful_shutdown(signal)
89
+ EventMachine.stop_server(@server) if @server
90
+ Thread.new{ logger.info "Received #{signal} signal. No longer accepting new connections." }
91
+ Thread.new{ logger.info "Waiting for #{EventMachine.connection_count} connections to finish." }
92
+ @server = nil
93
+ graceful_shutdown_check
94
+ end
95
+
96
+ # TODO: next_tick can't be called from trap context
97
+ def graceful_shutdown_check
98
+ EventMachine.next_tick do
99
+ count = EventMachine.connection_count
100
+ if count == 0
101
+ EventMachine.stop_event_loop
102
+ else
103
+ @wait_count ||= count
104
+ Thread.new{ logger.info "Waiting for #{EventMachine.connection_count} connections to finish." if @wait_count != count }
105
+ EventMachine.next_tick self.method(:graceful_shutdown_check)
106
+ end
107
+ end
108
+ end
109
+
110
+ # TODO: where does EventMachine.connection_count come from?
111
+ def fast_shutdown(signal)
112
+ EventMachine.stop_server(@server) if @server
113
+ Thread.new{ logger.info "Received #{signal} signal. No longer accepting new connections." }
114
+ Thread.new{ logger.info "Maximum time to wait for connections is #{MAX_FAST_SHUTDOWN_SECONDS} seconds." }
115
+ Thread.new{ logger.info "Waiting for #{EventMachine.connection_count} connections to finish." }
116
+ @server = nil
117
+ EventMachine.stop_event_loop
118
+ #EventMachine.stop_event_loop if EventMachine.connection_count == 0
119
+ #Thread.new do
120
+ # sleep MAX_FAST_SHUTDOWN_SECONDS
121
+ # $kernel.exit!
122
+ #end
123
+ end
124
+
125
+
126
+ def start_server
127
+ stack3 = Protocols::ProtocolStack.new([
128
+ get_packet_protocol,
129
+ get_cipher_protocol,
130
+ get_obfs_protocol
131
+ ].compact, options[:cipher_name], options[:password])
132
+
133
+ stack4 = Protocols::ProtocolStack.new([
134
+ ["plain", {}]
135
+ ], options[:cipher_name], options[:password])
136
+
137
+ server_args = [
138
+ stack3,
139
+ {},
140
+ stack4,
141
+ {}
142
+ ]
143
+
144
+ start_em options[:server], options[:port], Connections::TCP::LocalBackendConnection, server_args
145
+ end
146
+
147
+ def start_client
148
+ stack1 = Protocols::ProtocolStack.new([
149
+ ["socks5", {}]
150
+ ], options[:cipher_name], options[:password])
151
+
152
+ stack2 = Protocols::ProtocolStack.new([
153
+ get_packet_protocol,
154
+ get_cipher_protocol,
155
+ get_obfs_protocol
156
+ ].compact, options[:cipher_name], options[:password])
157
+
158
+ local_args = [
159
+ stack1,
160
+ {:host => options[:server], :port => options[:port]},
161
+ stack2,
162
+ {}
163
+ ]
164
+
165
+ start_em options[:local_addr], options[:local_port], Connections::TCP::ClientConnection, local_args
166
+ end
167
+
168
+ def start_em host, port, klass_server, server_args
169
+ EventMachine.epoll
170
+
171
+ EventMachine.run do
172
+ if options[:einhorn] != true
173
+ @server = EventMachine.start_server(host, port, klass_server, *server_args)
174
+ else
175
+ fd_num = Einhorn::Worker.socket!
176
+ socket = Socket.for_fd(fd_num)
177
+
178
+ @server = EventMachine.attach_server(socket, klass_server, *server_args)
179
+ end
180
+ logger.info "server started"
181
+ logger.info "Listening on #{host}:#{port}"
182
+ logger.info "Send QUIT to quit after waiting for all connections to finish."
183
+ logger.info "Send TERM or INT to quit after waiting for up to #{MAX_FAST_SHUTDOWN_SECONDS} seconds for connections to finish."
184
+
185
+ trap_signals
186
+ end
187
+ end
188
+
189
+ def get_packet_protocol
190
+ case options[:packet_name]
191
+ when "origin", "verify_sha1", "verify_sha1_strict"
192
+ ["shadowsocks", {}]
193
+ else
194
+ raise AppError, "no such protocol: #{options[:packet_name]}"
195
+ end
196
+ end
197
+
198
+ def get_cipher_protocol
199
+ if options[:cipher_name] == nil || options[:cipher_name] == "none"
200
+ return nil
201
+ end
202
+ case options[:cipher_name]
203
+ when "table"
204
+ ["no_iv_cipher", {}]
205
+ else
206
+ case options[:packet_name]
207
+ when "origin"
208
+ ["iv_cipher", {}]
209
+ when "verify_sha1"
210
+ ["verify_sha1", {}]
211
+ when "verify_sha1_strict"
212
+ ["verify_sha1", {:compatible => false}]
213
+ end
214
+ end
215
+ end
216
+
217
+ def get_obfs_protocol
218
+ if options[:obfs_name] == nil
219
+ return nil
220
+ end
221
+ case options[:obfs_name]
222
+ when "http_simple"
223
+ ["http_simple", {:host => options[:server], :port => options[:port], :obfs_param => options[:obfs_param]}]
224
+ when "http_simple_strict"
225
+ ["http_simple", {:host => options[:server], :port => options[:port], :obfs_param => options[:obfs_param], :compatible => false}]
226
+ when "tls_ticket"
227
+ ["tls_ticket", {:host => options[:server], :obfs_param => options[:obfs_param]}]
228
+ when "tls_ticket_strict"
229
+ ["tls_ticket", {:host => options[:server], :obfs_param => options[:obfs_param], :compatible => false}]
230
+ else
231
+ raise AppError, "no such protocol: #{options[:obfs_name]}"
232
+ end
233
+ end
234
+
235
+ end
236
+ end
@@ -0,0 +1,112 @@
1
+ require 'openssl'
2
+ module ShadowsocksRuby
3
+
4
+ # This module provide classes to encapsulate different underlying crypto library,
5
+ # to utilize them with an unique interface.
6
+ #
7
+ # It also provide some useful utility functions like
8
+ # {.hmac_sha1_digest} and {.bytes_to_key}.
9
+ #
10
+ # @example
11
+ # # Demonstrate how to build a cipher object and it's typical use case.
12
+ # cipher = ShadowsocksRuby::Cipher.build('aes-256-cfb', 'secret123')
13
+ # iv = cipher.random_id
14
+ # encrypted_text = cipher.encrypt("hello world!", iv)
15
+ # puts cipher.decrypt(encrypted_text, iv) # hello world!
16
+ # puts cipher.key # ...... # in case key need to be used in some Digest algorithm.
17
+
18
+ module Cipher
19
+ extend self
20
+
21
+ # Builder for cipher object
22
+ #
23
+ # Supported methods are:
24
+ # * table
25
+ # * rc4-md5
26
+ # * chacha20, chacha2-ietf, salsa20 which are provided by RbNaCl
27
+ # * all cipher methods supported by ruby gems OpenSSL, use
28
+ #
29
+ # ruby -e "require 'openssl'; puts OpenSSL::Cipher.ciphers"
30
+ #
31
+ # to get a full list.
32
+ # @param [String] method Cipher methods
33
+ # @param [String] password Password
34
+ # @return [OpenSSL, Table, RC4_MD5, RbNaCl] A duck type cipher object
35
+ #
36
+ def build method, password
37
+ case method
38
+ when 'table'
39
+ ShadowsocksRuby::Cipher::Table.new password
40
+ when 'rc4-md5'
41
+ ShadowsocksRuby::Cipher::RC4_MD5.new password
42
+ when 'chacha20','chacha20-ietf','salsa20'
43
+ ShadowsocksRuby::Cipher::RbNaCl.new method, password
44
+ else
45
+ ShadowsocksRuby::Cipher::OpenSSL.new method, password
46
+ end
47
+ end
48
+
49
+ # Generate <b>first 10 bytes</b> of HMAC using sha1 Digest
50
+ #
51
+ # @param [String] key Key, use {#bytes_to_key} to convert a password to key if you need
52
+ # @param [String] message Message to digest
53
+ # @return [String] Digest, <b> only first 10 bytes</b>
54
+ def hmac_sha1_digest(key, message)
55
+ @digest ||= ::OpenSSL::Digest.new('sha1')
56
+ ::OpenSSL::HMAC.digest(@digest, key, message)[0,10]
57
+ end
58
+
59
+ # Equivalent to OpenSSL's EVP_BytesToKey() with count = 1
60
+ #
61
+ # @param [String] Password Password bytes
62
+ # @param [Integer] key_len Key length, the length of key bytes to generate
63
+ # @param [Integer] iv_len IV length, needed by internal algorithm
64
+ # @return [String] Key bytes, of *key_len* length
65
+ def bytes_to_key(password, key_len, iv_len)
66
+ bytes_to_key1(nil, password, 1, key_len, iv_len)[0]
67
+ end
68
+
69
+ private
70
+
71
+ def bytes_to_key0(md_buf, salt, data, count)
72
+ src = md_buf.empty? ? '' : md_buf
73
+ src << data
74
+ src << salt if salt
75
+
76
+ dst = ::OpenSSL::Digest::MD5.digest(src)
77
+
78
+ (count - 1).times do
79
+ dst = ::OpenSSL::Digest::MD5.digest(dst)
80
+ end
81
+
82
+ return dst
83
+ end
84
+
85
+ # Equivalent to OpenSSL's EVP_BytesToKey()
86
+ # Taken from http://d.hatena.ne.jp/winebarrel/20081208/p1
87
+ # Fixed by Zhenkyle
88
+ def bytes_to_key1(salt, data, count, nkey, niv)
89
+ key = ''
90
+ iv = ''
91
+ md_buf = ''
92
+
93
+ loop do
94
+ md_buf = bytes_to_key0(md_buf, salt, data, count)
95
+
96
+ if nkey.nonzero?
97
+ key << (nkey > md_buf.length ? md_buf : md_buf[0, nkey])
98
+ nkey -= nkey > md_buf.length ? md_buf.length : nkey
99
+ elsif niv.nonzero?
100
+ iv << (niv > md_buf.length ? md_buf : md_buf[0, niv])
101
+ niv -= niv > md_buf.length ? md_buf.length : niv
102
+ end
103
+
104
+ if nkey.zero? and niv.zero?
105
+ break
106
+ end
107
+ end
108
+
109
+ return [key, iv]
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,81 @@
1
+ require 'openssl'
2
+
3
+ module ShadowsocksRuby
4
+ module Cipher
5
+
6
+ # Encapsulate RubyGems version of OpenSSL, the gems version is newer than
7
+ # the version in Ruby Standand Library.
8
+ #
9
+ # Cipher methods provided by Ruby OpenSSL library is dicided by
10
+ # the OpenSSL library comes with ruby on your system.
11
+ # To work with specific version of OpenSSL library other than the version
12
+ # comes with ruby, you may need to specify the path where OpenSSL is installed.
13
+ #
14
+ # gem install openssl -- --with-openssl-dir=/opt/openssl
15
+ #
16
+ # Use this command to get a full list of cipher methods supported on your system.
17
+ # ruby -e "require 'openssl'; puts OpenSSL::Cipher.ciphers"
18
+ #
19
+ #
20
+ # See https://github.com/ruby/openssl for more detail.
21
+ #
22
+ # Normally you should use {ShadowsocksRuby::Cipher#build} to get an
23
+ # instance of this class.
24
+ class OpenSSL
25
+
26
+ # Return the key, which length is decided by the cipher method.
27
+ # @return [String] key
28
+ attr_reader :key
29
+
30
+ # @param [String] method Cipher methods
31
+ # @param [String] password Password
32
+ def initialize method, password
33
+ @cipher_encrypt = ::OpenSSL::Cipher.new(method).encrypt
34
+ @cipher_decrypt = ::OpenSSL::Cipher.new(method).decrypt
35
+ key_len = @cipher_encrypt.key_len
36
+ iv_len = @cipher_encrypt.iv_len
37
+ @key = Cipher.bytes_to_key(password, key_len, iv_len)
38
+ @cipher_encrypt.key = @key
39
+ @cipher_decrypt.key = @key
40
+ @encrypt_iv = nil
41
+ @decrypt_iv = nil
42
+ end
43
+
44
+ # Generate a random IV for the cipher method
45
+ # @return [String] random IV of the length of the cipher method
46
+ def random_iv
47
+ @encrypt_iv = @cipher_encrypt.random_iv
48
+ end
49
+
50
+ # Encrypt message by provided IV
51
+ # @param [String] message
52
+ # @param [String] iv
53
+ # @return [String] Encrypted Message
54
+ def encrypt(message, iv)
55
+ if @encrypt_iv != iv
56
+ @encrypt_iv = iv
57
+ @cipher_encrypt.iv = iv
58
+ end
59
+ @cipher_encrypt.update(message) << @cipher_encrypt.final
60
+ end
61
+
62
+ # Decrypt message by provided IV
63
+ # @param [String] message
64
+ # @param [String] iv
65
+ # @return [String] Decrypted Message
66
+ def decrypt(message, iv)
67
+ if @decrypt_iv != iv
68
+ @decrypt_iv = iv
69
+ @cipher_decrypt.iv = iv
70
+ end
71
+ @cipher_decrypt.update(message) << @cipher_decrypt.final
72
+ end
73
+
74
+ # Get the cipher object's IV length
75
+ # @return [Integer]
76
+ def iv_len
77
+ @cipher_encrypt.iv_len
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,64 @@
1
+ require 'rbnacl'
2
+
3
+ module ShadowsocksRuby
4
+ module Cipher
5
+
6
+ # Encapsulate RbNaCl ruby library, cipher methods provided by this Class are:
7
+ # * chacha20 -- ChaCha20Poly1305Legacy without ad
8
+ # * chacha2-ietf -- ChaCha20Poly1305IETF without ad
9
+ # * salsa20 -- XSalsa20Poly1305 without ad
10
+ #
11
+ # Normally you should use {ShadowsocksRuby::Cipher#build} to get an
12
+ # instance of this class.
13
+
14
+ class RbNaCl
15
+
16
+ attr_reader :key
17
+ # (see OpenSSL#initialize)
18
+ def initialize method, password
19
+ klass = case method
20
+ when 'chacha20'
21
+ ::RbNaCl::AEAD::ChaCha20Poly1305Legacy
22
+ when 'chacha20-ietf'
23
+ ::RbNaCl::AEAD::ChaCha20Poly1305IETF
24
+ when 'salsa20'
25
+ ::RbNaCl::SecretBoxes::XSalsa20Poly1305
26
+ else
27
+ raise CipherError, "unsupported method: " + method
28
+ end
29
+ key_len = klass.key_bytes
30
+ iv_len = klass.nonce_bytes
31
+ @key = ShadowsocksRuby::Cipher.bytes_to_key(password, key_len, iv_len)
32
+ @cipher = klass.new(@key)
33
+ end
34
+
35
+ # (see OpenSSL#random_iv)
36
+ def random_iv
37
+ ::RbNaCl::Random.random_bytes(@cipher.nonce_bytes)
38
+ end
39
+
40
+ # (see OpenSSL#encrypt)
41
+ def encrypt(message, iv)
42
+ if @cipher.class == ::RbNaCl::SecretBoxes::XSalsa20Poly1305
43
+ @cipher.encrypt(iv, message)
44
+ else
45
+ @cipher.encrypt(iv, message, nil)
46
+ end
47
+ end
48
+
49
+ # (see OpenSSL#decrypt)
50
+ def decrypt(message, iv)
51
+ if @cipher.class == ::RbNaCl::SecretBoxes::XSalsa20Poly1305
52
+ @cipher.decrypt(iv, message)
53
+ else
54
+ @cipher.decrypt(iv, message, nil)
55
+ end
56
+ end
57
+
58
+ # (see OpenSSL#iv_len)
59
+ def iv_len
60
+ @cipher.iv_bytes
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,54 @@
1
+ require 'openssl'
2
+
3
+ module ShadowsocksRuby
4
+ module Cipher
5
+
6
+ # Implementation of the RC4_MD5 cipher method.
7
+ #
8
+ # Normally you should use {ShadowsocksRuby::Cipher#build} to get an
9
+ # instance of this class.
10
+
11
+ class RC4_MD5
12
+ attr_reader :key
13
+
14
+ # (see OpenSSL#initialize)
15
+ def initialize password
16
+ @key = ShadowsocksRuby::Cipher.bytes_to_key(password, 16, 16)
17
+ @cipher_encrypt = ::OpenSSL::Cipher.new('rc4').encrypt
18
+ @cipher_decrypt = ::OpenSSL::Cipher.new('rc4').decrypt
19
+ @encrypt_iv = nil
20
+ @decrypt_iv = nil
21
+ end
22
+
23
+ # (see OpenSSL#random_iv)
24
+ def random_iv
25
+ Random.new.bytes(16)
26
+ end
27
+
28
+ # (see OpenSSL#encrypt)
29
+ def encrypt(message, iv)
30
+ if @encrypt_iv != iv
31
+ @encrypt_iv = iv
32
+ key = ::OpenSSL::Digest::MD5.digest(@key + iv)
33
+ @cipher_encrypt.key = key
34
+ end
35
+ @cipher_encrypt.update(message) << @cipher_encrypt.final
36
+ end
37
+
38
+ # (see OpenSSL#decrypt)
39
+ def decrypt(message, iv)
40
+ if @decrypt_iv != iv
41
+ @decrypt_iv = iv
42
+ key = ::OpenSSL::Digest::MD5.digest(@key + iv)
43
+ @cipher_decrypt.key = key
44
+ end
45
+ @cipher_decrypt.update(message) << @cipher_decrypt.final
46
+ end
47
+
48
+ # (see OpenSSL#iv_len)
49
+ def iv_len
50
+ 16
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,65 @@
1
+ require 'openssl'
2
+
3
+ module ShadowsocksRuby
4
+ module Cipher
5
+
6
+ # Implementation of the Table cipher method.
7
+ #
8
+ # Note: this cipher method have neither IV or key, so may be
9
+ # incompatible with protocols which needs IV or key.
10
+ #
11
+ # Normally you should use {ShadowsocksRuby::Cipher#build} to get an
12
+ # instance of this class.
13
+ class Table
14
+ # (see OpenSSL#initialize)
15
+ def initialize password
16
+ @encrypt_table, @decrypt_table = get_table(password)
17
+ end
18
+
19
+ # (see OpenSSL#encrypt)
20
+ def encrypt(message)
21
+ translate @encrypt_table, message
22
+ end
23
+
24
+ # (see OpenSSL#decrypt)
25
+ def decrypt(message)
26
+ translate @decrypt_table, message
27
+ end
28
+
29
+ # (see OpenSSL#iv_len)
30
+ #
31
+ # returns 0 for Table
32
+ def iv_len
33
+ 0
34
+ end
35
+
36
+ # (see OpenSSL#key)
37
+ #
38
+ # returns nil for Table
39
+ def key
40
+ nil
41
+ end
42
+
43
+ private
44
+
45
+ def get_table(key)
46
+ table = [*0..255]
47
+ a = ::OpenSSL::Digest::MD5.digest(key).unpack('Q<')[0]
48
+
49
+ (1...1024).each do |i|
50
+ table.sort! { |x, y| a % (x + i) - a % (y + i) }
51
+ end
52
+
53
+ decrypt_table = Array.new(256)
54
+ table.each_with_index {|x, i| decrypt_table[x] = i}
55
+
56
+ [table, decrypt_table]
57
+ end
58
+
59
+ def translate(table, buf)
60
+ buf.bytes.map!{|x| table[x]}.pack("C*")
61
+ end
62
+
63
+ end
64
+ end
65
+ end