net-ssh 5.2.0 → 6.0.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 (69) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +7 -4
  6. data/.rubocop_todo.yml +392 -379
  7. data/.travis.yml +16 -17
  8. data/CHANGES.txt +11 -0
  9. data/Manifest +0 -1
  10. data/README.md +286 -0
  11. data/Rakefile +1 -2
  12. data/appveyor.yml +4 -2
  13. data/lib/net/ssh.rb +7 -2
  14. data/lib/net/ssh/authentication/certificate.rb +10 -1
  15. data/lib/net/ssh/authentication/ed25519.rb +2 -1
  16. data/lib/net/ssh/authentication/ed25519_loader.rb +1 -1
  17. data/lib/net/ssh/authentication/key_manager.rb +34 -5
  18. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +3 -1
  19. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +0 -1
  20. data/lib/net/ssh/authentication/session.rb +9 -6
  21. data/lib/net/ssh/buffer.rb +1 -10
  22. data/lib/net/ssh/buffered_io.rb +0 -1
  23. data/lib/net/ssh/config.rb +51 -32
  24. data/lib/net/ssh/connection/channel.rb +17 -5
  25. data/lib/net/ssh/connection/event_loop.rb +0 -1
  26. data/lib/net/ssh/connection/session.rb +7 -4
  27. data/lib/net/ssh/key_factory.rb +6 -8
  28. data/lib/net/ssh/known_hosts.rb +27 -29
  29. data/lib/net/ssh/loggable.rb +2 -2
  30. data/lib/net/ssh/proxy/command.rb +0 -1
  31. data/lib/net/ssh/proxy/socks5.rb +0 -1
  32. data/lib/net/ssh/service/forward.rb +2 -1
  33. data/lib/net/ssh/test.rb +3 -2
  34. data/lib/net/ssh/transport/algorithms.rb +67 -42
  35. data/lib/net/ssh/transport/cipher_factory.rb +11 -27
  36. data/lib/net/ssh/transport/constants.rb +10 -6
  37. data/lib/net/ssh/transport/ctr.rb +1 -7
  38. data/lib/net/ssh/transport/hmac.rb +15 -13
  39. data/lib/net/ssh/transport/hmac/abstract.rb +16 -0
  40. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  41. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  42. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  43. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  44. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  45. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  46. data/lib/net/ssh/transport/kex.rb +14 -11
  47. data/lib/net/ssh/transport/kex/abstract.rb +123 -0
  48. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  49. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +38 -0
  50. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  51. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +1 -15
  52. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +9 -118
  53. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +0 -6
  54. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  55. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +18 -79
  56. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +5 -4
  57. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +5 -4
  58. data/lib/net/ssh/transport/openssl.rb +104 -107
  59. data/lib/net/ssh/transport/packet_stream.rb +44 -11
  60. data/lib/net/ssh/transport/state.rb +1 -1
  61. data/lib/net/ssh/version.rb +2 -2
  62. data/net-ssh-public_cert.pem +8 -8
  63. data/net-ssh.gemspec +9 -7
  64. metadata +46 -29
  65. metadata.gz.sig +2 -3
  66. data/Gemfile.noed25519.lock +0 -41
  67. data/README.rdoc +0 -194
  68. data/lib/net/ssh/ruby_compat.rb +0 -13
  69. data/support/arcfour_check.rb +0 -20
@@ -41,14 +41,11 @@ module Net
41
41
  # This is used internally by Net::SSH, and will never need to be used directly
42
42
  # by consumers of the library.
43
43
  class KnownHosts
44
- if defined?(OpenSSL::PKey::EC)
45
- SUPPORTED_TYPE = %w[ssh-rsa ssh-dss
46
- ecdsa-sha2-nistp256
47
- ecdsa-sha2-nistp384
48
- ecdsa-sha2-nistp521]
49
- else
50
- SUPPORTED_TYPE = %w[ssh-rsa ssh-dss]
51
- end
44
+ SUPPORTED_TYPE = %w[ssh-rsa ssh-dss
45
+ ecdsa-sha2-nistp256
46
+ ecdsa-sha2-nistp384
47
+ ecdsa-sha2-nistp521]
48
+
52
49
  SUPPORTED_TYPE.push('ssh-ed25519') if Net::SSH::Authentication::ED25519Loader::LOADED
53
50
 
54
51
  class <<self
@@ -78,7 +75,9 @@ module Net
78
75
 
79
76
  files += Array(options[:user_known_hosts_file] || %w[~/.ssh/known_hosts ~/.ssh/known_hosts2]) if which == :all || which == :user
80
77
 
81
- files += Array(options[:global_known_hosts_file] || %w[/etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2]) if which == :all || which == :global
78
+ if which == :all || which == :global
79
+ files += Array(options[:global_known_hosts_file] || %w[/etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2])
80
+ end
82
81
 
83
82
  return files
84
83
  end
@@ -130,27 +129,22 @@ module Net
130
129
  host_ip = entries[1]
131
130
 
132
131
  File.open(source) do |file|
133
- scanner = StringScanner.new("")
134
132
  file.each_line do |line|
135
- scanner.string = line
133
+ hosts, type, key_content = line.split(' ')
134
+ # Skip empty line or one that is commented
135
+ next if hosts.nil? || hosts.start_with?('#')
136
136
 
137
- scanner.skip(/\s*/)
138
- next if scanner.match?(/$|#/)
137
+ hostlist = hosts.split(',')
138
+
139
+ next unless SUPPORTED_TYPE.include?(type)
139
140
 
140
- hostlist = scanner.scan(/\S+/).split(/,/)
141
141
  found = hostlist.any? { |pattern| match(host_name, pattern) } || known_host_hash?(hostlist, entries)
142
142
  next unless found
143
143
 
144
144
  found = hostlist.include?(host_ip) if options[:check_host_ip] && entries.size > 1 && hostlist.size > 1
145
145
  next unless found
146
146
 
147
- scanner.skip(/\s*/)
148
- type = scanner.scan(/\S+/)
149
-
150
- next unless SUPPORTED_TYPE.include?(type)
151
-
152
- scanner.skip(/\s*/)
153
- blob = scanner.rest.unpack("m*").first
147
+ blob = key_content.unpack("m*").first
154
148
  keys << Net::SSH::Buffer.new(blob).read_key
155
149
  end
156
150
  end
@@ -159,14 +153,18 @@ module Net
159
153
  end
160
154
 
161
155
  def match(host, pattern)
162
- # see man 8 sshd for pattern details
163
- pattern_regexp = pattern.split('*').map do |x|
164
- x.split('?').map do |y|
165
- Regexp.escape(y)
166
- end.join('.')
167
- end.join('[^.]*')
168
-
169
- host =~ Regexp.new("\\A#{pattern_regexp}\\z")
156
+ if pattern.include?('*') || pattern.include?('?')
157
+ # see man 8 sshd for pattern details
158
+ pattern_regexp = pattern.split('*').map do |x|
159
+ x.split('?').map do |y|
160
+ Regexp.escape(y)
161
+ end.join('.')
162
+ end.join('[^.]*')
163
+
164
+ host =~ Regexp.new("\\A#{pattern_regexp}\\z")
165
+ else
166
+ host == pattern
167
+ end
170
168
  end
171
169
 
172
170
  # Indicates whether one of the entries matches an hostname that has been
@@ -56,8 +56,8 @@ module Net
56
56
  # originates. It defaults to the name of class with the object_id
57
57
  # appended.
58
58
  def facility
59
- @facility ||= self.class.name.gsub(/::/, ".").gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase + "[%x]" % object_id
59
+ @facility ||= self.class.to_s.gsub(/::/, ".").gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase + "[%x]" % object_id
60
60
  end
61
61
  end
62
62
  end
63
- end
63
+ end
@@ -1,7 +1,6 @@
1
1
  require 'socket'
2
2
  require 'rubygems'
3
3
  require 'net/ssh/proxy/errors'
4
- require 'net/ssh/ruby_compat'
5
4
 
6
5
  module Net
7
6
  module SSH
@@ -1,5 +1,4 @@
1
1
  require 'socket'
2
- require 'net/ssh/ruby_compat'
3
2
  require 'net/ssh/proxy/errors'
4
3
 
5
4
  module Net
@@ -85,7 +85,8 @@ module Net
85
85
  client = server.accept
86
86
  debug { "received connection on #{socket}" }
87
87
 
88
- channel = session.open_channel("direct-tcpip", :string, remote_host, :long, remote_port, :string, bind_address, local_port_type, local_port) do |achannel|
88
+ channel = session.open_channel("direct-tcpip", :string, remote_host, :long,
89
+ remote_port, :string, bind_address, local_port_type, local_port) do |achannel|
89
90
  achannel.info { "direct channel established" }
90
91
  end
91
92
 
@@ -74,7 +74,7 @@ module Net
74
74
  def transport(options={})
75
75
  @transport ||= Net::SSH::Transport::Session.new(
76
76
  options[:host] || "localhost",
77
- options.merge(kex: "test", host_key: "ssh-rsa", verify_host_key: :never, proxy: socket(options))
77
+ options.merge(kex: "test", host_key: "ssh-rsa", append_all_supported_algorithms: true, verify_host_key: :never, proxy: socket(options))
78
78
  )
79
79
  end
80
80
 
@@ -86,7 +86,8 @@ module Net
86
86
  def assert_scripted
87
87
  raise "there is no script to be processed" if socket.script.events.empty?
88
88
  Net::SSH::Test::Extensions::IO.with_test_extension { yield }
89
- assert socket.script.events.empty?, "there should not be any remaining scripted events, but there are still #{socket.script.events.length} pending"
89
+ assert socket.script.events.empty?, "there should not be any remaining scripted events, but there are still" \
90
+ "#{socket.script.events.length} pending"
90
91
  end
91
92
  end
92
93
 
@@ -5,13 +5,13 @@ require 'net/ssh/transport/cipher_factory'
5
5
  require 'net/ssh/transport/constants'
6
6
  require 'net/ssh/transport/hmac'
7
7
  require 'net/ssh/transport/kex'
8
+ require 'net/ssh/transport/kex/curve25519_sha256_loader'
8
9
  require 'net/ssh/transport/server_version'
9
10
  require 'net/ssh/authentication/ed25519_loader'
10
11
 
11
12
  module Net
12
13
  module SSH
13
14
  module Transport
14
-
15
15
  # Implements the higher-level logic behind an SSH key-exchange. It handles
16
16
  # both the initial exchange, as well as subsequent re-exchanges (as needed).
17
17
  # It also encapsulates the negotiation of the algorithms, and provides a
@@ -23,56 +23,72 @@ module Net
23
23
  include Loggable
24
24
  include Constants
25
25
 
26
- # Define the default algorithms, in order of preference, supported by
27
- # Net::SSH.
28
- ALGORITHMS = {
29
- host_key: %w[ssh-rsa-cert-v01@openssh.com
26
+ # Define the default algorithms, in order of preference, supported by Net::SSH.
27
+ DEFAULT_ALGORITHMS = {
28
+ host_key: %w[ecdsa-sha2-nistp521-cert-v01@openssh.com
29
+ ecdsa-sha2-nistp384-cert-v01@openssh.com
30
+ ecdsa-sha2-nistp256-cert-v01@openssh.com
31
+ ecdsa-sha2-nistp521
32
+ ecdsa-sha2-nistp384
33
+ ecdsa-sha2-nistp256
34
+ ssh-rsa-cert-v01@openssh.com
30
35
  ssh-rsa-cert-v00@openssh.com
31
- ssh-rsa ssh-dss],
32
- kex: %w[diffie-hellman-group-exchange-sha256
33
- diffie-hellman-group-exchange-sha1
34
- diffie-hellman-group14-sha1
36
+ ssh-rsa],
37
+
38
+ kex: %w[ecdh-sha2-nistp521
39
+ ecdh-sha2-nistp384
40
+ ecdh-sha2-nistp256
41
+ diffie-hellman-group-exchange-sha256
42
+ diffie-hellman-group14-sha1],
43
+
44
+ encryption: %w[aes256-ctr aes192-ctr aes128-ctr],
45
+
46
+ hmac: %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com
47
+ hmac-sha2-512 hmac-sha2-256
48
+ hmac-sha1]
49
+ }.freeze
50
+
51
+ if Net::SSH::Authentication::ED25519Loader::LOADED
52
+ DEFAULT_ALGORITHMS[:host_key].unshift(
53
+ 'ssh-ed25519-cert-v01@openssh.com',
54
+ 'ssh-ed25519'
55
+ )
56
+ end
57
+
58
+ if Net::SSH::Transport::Kex::Curve25519Sha256Loader::LOADED
59
+ DEFAULT_ALGORITHMS[:kex].unshift(
60
+ 'curve25519-sha256',
61
+ 'curve25519-sha256@libssh.org'
62
+ )
63
+ end
64
+
65
+ # Define all algorithms, with the deprecated, supported by Net::SSH.
66
+ ALGORITHMS = {
67
+ host_key: DEFAULT_ALGORITHMS[:host_key] + %w[ssh-dss],
68
+
69
+ kex: DEFAULT_ALGORITHMS[:kex] +
70
+ %w[diffie-hellman-group-exchange-sha1
35
71
  diffie-hellman-group1-sha1],
36
- encryption: %w[aes256-ctr aes192-ctr aes128-ctr
37
- aes256-cbc aes192-cbc aes128-cbc
72
+
73
+ encryption: DEFAULT_ALGORITHMS[:encryption] +
74
+ %w[aes256-cbc aes192-cbc aes128-cbc
38
75
  rijndael-cbc@lysator.liu.se
39
76
  blowfish-ctr blowfish-cbc
40
77
  cast128-ctr cast128-cbc
41
78
  3des-ctr 3des-cbc
42
- idea-cbc arcfour256 arcfour128 arcfour
79
+ idea-cbc
43
80
  none],
44
81
 
45
- hmac: %w[hmac-sha2-512 hmac-sha2-256
46
- hmac-sha2-512-96 hmac-sha2-256-96
47
- hmac-sha1 hmac-sha1-96
82
+ hmac: DEFAULT_ALGORITHMS[:hmac] +
83
+ %w[hmac-sha2-512-96 hmac-sha2-256-96
84
+ hmac-sha1-96
48
85
  hmac-ripemd160 hmac-ripemd160@openssh.com
49
86
  hmac-md5 hmac-md5-96
50
87
  none],
51
88
 
52
89
  compression: %w[none zlib@openssh.com zlib],
53
90
  language: %w[]
54
- }
55
- if defined?(OpenSSL::PKey::EC)
56
- ALGORITHMS[:host_key].unshift(
57
- "ecdsa-sha2-nistp521-cert-v01@openssh.com",
58
- "ecdsa-sha2-nistp384-cert-v01@openssh.com",
59
- "ecdsa-sha2-nistp256-cert-v01@openssh.com",
60
- "ecdsa-sha2-nistp521",
61
- "ecdsa-sha2-nistp384",
62
- "ecdsa-sha2-nistp256"
63
- )
64
- if Net::SSH::Authentication::ED25519Loader::LOADED
65
- ALGORITHMS[:host_key].unshift(
66
- "ssh-ed25519-cert-v01@openssh.com",
67
- "ssh-ed25519"
68
- )
69
- end
70
- ALGORITHMS[:kex].unshift(
71
- "ecdh-sha2-nistp521",
72
- "ecdh-sha2-nistp384",
73
- "ecdh-sha2-nistp256"
74
- )
75
- end
91
+ }.freeze
76
92
 
77
93
  # The underlying transport layer session that supports this object
78
94
  attr_reader :session
@@ -140,6 +156,7 @@ module Net
140
156
  # Start the algorithm negotation
141
157
  def start
142
158
  raise ArgumentError, "Cannot call start if it's negotiation started or done" if @pending || @initialized
159
+
143
160
  send_kexinit
144
161
  end
145
162
 
@@ -197,8 +214,8 @@ module Net
197
214
 
198
215
  def host_key_format
199
216
  case host_key
200
- when "ssh-rsa-cert-v01@openssh.com", "ssh-rsa-cert-v00@openssh.com"
201
- "ssh-rsa"
217
+ when /^([a-z0-9-]+)-cert-v\d{2}@openssh.com$/
218
+ Regexp.last_match[1]
202
219
  else
203
220
  host_key
204
221
  end
@@ -239,7 +256,10 @@ module Net
239
256
  options[:compression] = %w[zlib@openssh.com zlib] if options[:compression] == true
240
257
 
241
258
  ALGORITHMS.each do |algorithm, supported|
242
- algorithms[algorithm] = compose_algorithm_list(supported, options[algorithm], options[:append_all_supported_algorithms])
259
+ algorithms[algorithm] = compose_algorithm_list(
260
+ supported, options[algorithm] || DEFAULT_ALGORITHMS[algorithm],
261
+ options[:append_all_supported_algorithms]
262
+ )
243
263
  end
244
264
 
245
265
  # for convention, make sure our list has the same keys as the server
@@ -356,7 +376,8 @@ module Net
356
376
 
357
377
  debug do
358
378
  "negotiated:\n" +
359
- %i[kex host_key encryption_server encryption_client hmac_client hmac_server compression_client compression_server language_client language_server].map do |key|
379
+ %i[kex host_key encryption_server encryption_client hmac_client hmac_server
380
+ compression_client compression_server language_client language_server].map do |key|
360
381
  "* #{key}: #{instance_variable_get("@#{key}")}"
361
382
  end.join("\n")
362
383
  end
@@ -368,7 +389,11 @@ module Net
368
389
  def negotiate(algorithm)
369
390
  match = self[algorithm].find { |item| @server_data[algorithm].include?(item) }
370
391
 
371
- raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm" if match.nil?
392
+ if match.nil?
393
+ raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm\n"\
394
+ "Server #{algorithm} preferences: #{@server_data[algorithm].join(',')}\n"\
395
+ "Client #{algorithm} preferences: #{self[algorithm].join(',')}"
396
+ end
372
397
 
373
398
  return match
374
399
  end
@@ -19,30 +19,17 @@ module Net
19
19
  "idea-cbc" => "idea-cbc",
20
20
  "cast128-cbc" => "cast-cbc",
21
21
  "rijndael-cbc@lysator.liu.se" => "aes-256-cbc",
22
- "arcfour128" => "rc4",
23
- "arcfour256" => "rc4",
24
- "arcfour512" => "rc4",
25
- "arcfour" => "rc4",
26
-
27
22
  "3des-ctr" => "des-ede3",
28
23
  "blowfish-ctr" => "bf-ecb",
29
-
30
- "aes256-ctr" => ::OpenSSL::Cipher.ciphers.include?("aes-256-ctr") ? "aes-256-ctr" : "aes-256-ecb",
31
- "aes192-ctr" => ::OpenSSL::Cipher.ciphers.include?("aes-192-ctr") ? "aes-192-ctr" : "aes-192-ecb",
32
- "aes128-ctr" => ::OpenSSL::Cipher.ciphers.include?("aes-128-ctr") ? "aes-128-ctr" : "aes-128-ecb",
33
- "cast128-ctr" => "cast5-ecb",
34
-
35
- "none" => "none"
36
- }
37
-
38
- # Ruby's OpenSSL bindings always return a key length of 16 for RC4 ciphers
39
- # resulting in the error: OpenSSL::CipherError: key length too short.
40
- # The following ciphers will override this key length.
41
- KEY_LEN_OVERRIDE = {
42
- "arcfour256" => 32,
43
- "arcfour512" => 64
24
+
25
+ 'aes256-ctr' => 'aes-256-ctr',
26
+ 'aes192-ctr' => 'aes-192-ctr',
27
+ 'aes128-ctr' => 'aes-128-ctr',
28
+ 'cast128-ctr' => 'cast5-ecb',
29
+
30
+ 'none' => 'none'
44
31
  }
45
-
32
+
46
33
  # Returns true if the underlying OpenSSL library supports the given cipher,
47
34
  # and false otherwise.
48
35
  def self.supported?(name)
@@ -72,12 +59,11 @@ module Net
72
59
  cipher = Net::SSH::Transport::OpenSSLAESCTR.new(cipher)
73
60
  end
74
61
  end
75
- cipher.iv = Net::SSH::Transport::KeyExpander.expand_key(cipher.iv_len, options[:iv], options) if ossl_name != "rc4"
62
+ cipher.iv = Net::SSH::Transport::KeyExpander.expand_key(cipher.iv_len, options[:iv], options)
76
63
 
77
- key_len = KEY_LEN_OVERRIDE[name] || cipher.key_len
64
+ key_len = cipher.key_len
78
65
  cipher.key_len = key_len
79
66
  cipher.key = Net::SSH::Transport::KeyExpander.expand_key(key_len, options[:key], options)
80
- cipher.update(" " * 1536) if (ossl_name == "rc4" && name != "arcfour")
81
67
 
82
68
  return cipher
83
69
  end
@@ -94,13 +80,11 @@ module Net
94
80
  result << 0 if options[:iv_len]
95
81
  else
96
82
  cipher = OpenSSL::Cipher.new(ossl_name)
97
- key_len = KEY_LEN_OVERRIDE[name] || cipher.key_len
83
+ key_len = cipher.key_len
98
84
  cipher.key_len = key_len
99
85
 
100
86
  block_size =
101
87
  case ossl_name
102
- when "rc4"
103
- 8
104
88
  when /\-ctr/
105
89
  Net::SSH::Transport::OpenSSLAESCTR.block_size
106
90
  else
@@ -2,11 +2,10 @@ module Net
2
2
  module SSH
3
3
  module Transport
4
4
  module Constants
5
-
6
5
  #--
7
6
  # Transport layer generic messages
8
7
  #++
9
-
8
+
10
9
  DISCONNECT = 1
11
10
  IGNORE = 2
12
11
  UNIMPLEMENTED = 3
@@ -17,19 +16,24 @@ module Net
17
16
  #--
18
17
  # Algorithm negotiation messages
19
18
  #++
20
-
19
+
21
20
  KEXINIT = 20
22
21
  NEWKEYS = 21
23
-
22
+
24
23
  #--
25
24
  # Key exchange method specific messages
26
25
  #++
27
-
26
+
28
27
  KEXDH_INIT = 30
29
28
  KEXDH_REPLY = 31
30
-
29
+
31
30
  KEXECDH_INIT = 30
32
31
  KEXECDH_REPLY = 31
32
+
33
+ KEXDH_GEX_GROUP = 31
34
+ KEXDH_GEX_INIT = 32
35
+ KEXDH_GEX_REPLY = 33
36
+ KEXDH_GEX_REQUEST = 34
33
37
  end
34
38
  end
35
39
  end
@@ -88,14 +88,8 @@ module Net::SSH::Transport
88
88
  end
89
89
 
90
90
  def final
91
- unless @remaining.empty?
92
- s = xor!(@remaining, _update(@counter))
93
- else
94
- s = ""
95
- end
96
-
91
+ s = @remaining.empty? ? '' : xor!(@remaining, _update(@counter))
97
92
  @remaining = ""
98
-
99
93
  s
100
94
  end
101
95