【问题标题】:Peculiar encoding issues in Ruby: ASCII != UTF-8 but UTF-8 == ASCIIRuby 中的特殊编码问题:ASCII != UTF-8 但 UTF-8 == ASCII
【发布时间】:2014-06-19 02:28:02
【问题描述】:

下面代码返回的值是"\x88\x90r\"\x9EN\xFFR"

MyApp::XVP::xvp_password_encrypt_vnc("L1UkDr]c")
# => "\x88\x90r\"\x9EN\xFFR"

当我们在测试中使用它时:

should "correctly encrypt a vnc password" do
  assert MyApp::XVP::xvp_password_encrypt_vnc("L1UkDr]c") == "\x88\x90r\"\x9EN\xFFR"
end
# => false

这是一个编码问题,我们可以通过以下操作看到:

MyApp::XVP::xvp_password_encrypt_vnc("L1UkDr]c").encoding
# => #<Encoding:ASCII-8BIT>

"\x88\x90r\"\x9EN\xFFR".encoding
# => #<Encoding:UTF-8>

因此,比较失败是有道理的,修复它的方法是在 xvp_password_encrypt_vnc 方法的末尾强制编码为 UTF,如下所示:

def xvp_password_encrypt_vnc(hex)
  des = OpenSSL::Cipher::Cipher.new("des-ecb")
  ... etc 
  des.update(hex).force_encoding('UTF-8')
end

现在,我们失败的测试通过了:

should "correctly encrypt a vnc password" do
  assert MyApp::XVP::xvp_password_encrypt_vnc("L1UkDr]c").force_encoding("UTF-8") == "\x88\x90r\"\x9EN\xFFR"
end
# => true

但事情似乎反过来不一样:

# This should fail
should "correctly encrypt a vnc password" do
  MyApp::XVP::xvp_password_decrypt_vnc("\x88\x90r\"\x9EN\xFFR") == "L1UkDr]c"
end
# => true

上述方法失败的原因是我们再次将 ASCII-8bit 与 UTF-8 进行比较(之前失败了):

MyApp::XVP::xvp_password_decrypt_vnc("\x88\x90r\"\x9EN\xFFR").encoding
# => #<Encoding:ASCII-8BIT>

"L1UkDr]c".encoding
# => #<Encoding:UTF-8>

为什么会失败:

something encoded in ASCII 8-bit != same thing encoded in UTF-8

但是当我们走另一条路时它不会失败:

something encoding in UTF-8 == same thing encoded in ASCII 8-bit

【问题讨论】:

  • "\x88\x90r\"\x9EN\xFFR".valid_encoding? 应该有助于回答您的问题。您的问题是字符串不是“以 UTF-8 编码的相同内容”,这些字节甚至不是有效的 UTF-8。但是,纯文本密码的 ASCII 和 UTF-8 编码相当于字节和字符。
  • 你有什么问题?
  • @sawa,请阅读最后我问的部分:“那么为什么它会失败......但是当我们走另一条路时它不会失败”。
  • @NeilSlater 感谢它解释了很多。

标签: ruby encoding utf-8 character-encoding ascii-8bit


【解决方案1】:

请记住,编码用于人机交互,密码用于计算机与计算机交互。构建密码时,实际上是创建了一个比特流,它没有固有的编码。

为了弥补 Ruby 使用编码解释字符串的倾向,您可以将值转换为 Base64,如下所示:

require 'base64'

module MyApp::XVP
  def xvp_password_encrypt_vnc64(hex)
    Base64.strict_encode64 xvp_password_encrypt_vnc(hex)
  end

  def xvp_password_decrypt_vnc64(hex)
    xvp_password_decrypt_vnc Base64.strict_decode64(hex)
  end
end

并对这些方法的输出执行测试。

另一种可能性是将您的规格数据转换为Encoding::BINARY(这是Encoding::ASCII_8BIT 的别名):

context 'decoding password'
  let(:encoded) { "\x88\x90r\"\x9EN\xFFR".force_encoding('BINARY') }
  let(:decoded) { "L1UkDr]c" }

  subject { MyApp::XVP::xvp_password_decrypt_vnc(encoded) }
  it { should eq decoded }
end

【讨论】:

  • 感谢 DMKE,这让我对我应该做什么有了一个好主意。
  • 很好的解释.. +1
【解决方案2】:

这两种情况的区别不在于您进行比较的“方式”,而是要比较的字符串的性质。文档对此并不清楚,但是当比较两个字符串并且它们具有不同的编码时,Ruby 会检查它们是否具有可比性。

特别是,如果字符串具有ASCII-8BIT 编码并且仅包含小于x80 的字节(即仅在 ASCII 范围内),则可以将其与 ASCII 兼容编码(例如 UTF-8)中的字符串进行比较。如果它包含超出 ASCII 范围的字节(大于x7f,则无法与其他编码的字符串进行比较。

在您的第一种情况下,字符串是 "\x88\x90r\"\x9EN\xFFR",其中包含非 ascii 字节,因此它与标记为 UTF-8 的字符串进行比较,即使 UTF-8 字符串实际上包含相同的字节(注意在这种情况下,这不是一个有效的 UTF-8 字符串)。换句话说,以下两个比较都返回false

u = "\x88\x90r\"\x9EN\xFFR" # default utf-8 encoding
b = "\x88\x90r\"\x9EN\xFFR".force_encoding('ASCII-8BIT') 

# utf-8 == ascii 8bit
puts u == b

# ascii 8bit == utf-8
puts b == u

第二个字符串是"L1UkDr]c",它仅包含 ASCII 范围内的字节(小于 0x80),因此可以与 UTF-8 字符串进行比较。对于这两种情况,这段代码都会生成true

u = "L1UkDr]c" # default utf-8 encoding
b = "L1UkDr]c".force_encoding('ASCII-8BIT') 

# utf-8 == ascii 8bit
puts u == b

# ascii 8bit == utf-8
puts b == u

在组合不同编码的字符串时使用相同(或至少相似)的规则。例如,在第一种情况下(字符串中包含非 ascii 字节)尝试执行u + b 将导致Encoding::CompatibilityError,在第二种情况下,您只会得到字符串"L1UkDr]cL1UkDr]c"

【讨论】:

    猜你喜欢
    • 2011-08-23
    • 1970-01-01
    • 2011-06-22
    • 1970-01-01
    • 2011-06-26
    • 2011-02-03
    • 1970-01-01
    • 2014-02-13
    • 1970-01-01
    相关资源
    最近更新 更多