net-ssh 5.2.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
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