Ruby 加密导致非字母数字字符
Ruby Cyphering Leads to non Alphanumeric Characters
我正在尝试制作一个基本密码。
def caesar_crypto_encode(text, shift)
(text.nil? or text.strip.empty? ) ? "" : text.gsub(/[a-zA-Z]/){ |cstr|
((cstr.ord)+shift).chr }
end
但是当位移太高时,我会得到这些字符:
Test.assert_equals(caesar_crypto_encode("Hello world!", 127), "eBIIL TLOIA!")
Expected: "eBIIL TLOIA!", instead got: "\xC7\xE4\xEB\xEB\xEE \xF6\xEE\xF1\xEB\xE3!"
这是什么格式?
I'm still curious about that format though...
这些字符表示得到每个字母的序数(ord
)加上127
(即(cstr.ord)+shift).chr
)后对应的ASCII编码
为什么?从文档中检查 Integer#chr:
Returns a string containing the character represented by the int's
value according to encoding.
所以,例如,拿你的第一个字母 "H":
char_ord = "H".ord
#=> 72
new_char_ord = char_ord + 127
#=> 199
new_char_ord.chr
#=> "\xC7"
所以,199
对应"\xC7"
。继续更改 "Hello world"
中的所有字符,您将得到 "\xC7\xE4\xEB\xEB\xEE \xF6\xEE\xF1\xEB\xE3"
.
为避免这种情况,您只需使用代表字母的 ord
值进行循环(在 可能重复 link 中回答)。
您得到详细输出的原因是因为 Ruby 是 运行 UTF-8 编码,而您的转换刚刚产生了乱码字符(UTF-8 编码下的无效字符序列)。
ASCII字符A-Z
表示为十进制数(序数)65-90,a-z
为97-122。当您添加 127 时,您会将所有字符推入 8 位 space,这使得它们无法识别正确的 UTF-8 编码。
这就是 Ruby inspect
以引号形式输出编码字符串的原因,它将每个字符显示为其十六进制数 "\xC7..."
。
如果你想从中得到一些相似的字符,你可以将乱码重新编码为支持 8 位字符的 ISO8859-1。
这样做会得到以下结果:
s = "\xC7\xE4\xEB\xEB\xEE \xF6\xEE\xF1\xEB\xE3!"
>> s.encoding
=> #<Encoding:UTF-8>
# Re-encode as ISO8859-1.
# Your terminal (and Ruby) is using UTF-8, so Ruby will refuse to print these yet.
>> s.force_encoding('iso8859-1')
=> "\xC7\xE4\xEB\xEB\xEE \xF6\xEE\xF1\xEB\xE3!"
# In order to be able to print ISO8859-1 on an UTF-8 terminal, you have to
# convert them back to UTF-8 by re-encoding. This way your terminal (and Ruby)
# can display the ISO8859-1 8-bit characters using UTF-8 encoding:
>> s.encode('UTF-8')
=> "Çäëëî öîñëã!"
# Another way is just to repack the bytes into UTF-8:
>> s.bytes.pack('U*')
=> "Çäëëî öîñëã!"
当然,正确的做法是在任何情况下都不要让数字溢出到 8 位 space。你的加密算法有bug,需要保证输出在7位ASCII范围内
更好的解决方案
就像@tadman 建议的那样,您可以改用 tr
:
AZ_SEQUENCE = *'A'..'Z' + *'a'..'z'
"Hello world!".tr(AZ_SEQUENCE.join, AZ_SEQUENCE.rotate(127).join)
=> "eBIIL tLOIA!
我正在尝试制作一个基本密码。
def caesar_crypto_encode(text, shift)
(text.nil? or text.strip.empty? ) ? "" : text.gsub(/[a-zA-Z]/){ |cstr|
((cstr.ord)+shift).chr }
end
但是当位移太高时,我会得到这些字符:
Test.assert_equals(caesar_crypto_encode("Hello world!", 127), "eBIIL TLOIA!")
Expected: "eBIIL TLOIA!", instead got: "\xC7\xE4\xEB\xEB\xEE \xF6\xEE\xF1\xEB\xE3!"
这是什么格式?
I'm still curious about that format though...
这些字符表示得到每个字母的序数(ord
)加上127
(即(cstr.ord)+shift).chr
)后对应的ASCII编码
为什么?从文档中检查 Integer#chr:
Returns a string containing the character represented by the int's value according to encoding.
所以,例如,拿你的第一个字母 "H":
char_ord = "H".ord
#=> 72
new_char_ord = char_ord + 127
#=> 199
new_char_ord.chr
#=> "\xC7"
所以,199
对应"\xC7"
。继续更改 "Hello world"
中的所有字符,您将得到 "\xC7\xE4\xEB\xEB\xEE \xF6\xEE\xF1\xEB\xE3"
.
为避免这种情况,您只需使用代表字母的 ord
值进行循环(在 可能重复 link 中回答)。
您得到详细输出的原因是因为 Ruby 是 运行 UTF-8 编码,而您的转换刚刚产生了乱码字符(UTF-8 编码下的无效字符序列)。
ASCII字符A-Z
表示为十进制数(序数)65-90,a-z
为97-122。当您添加 127 时,您会将所有字符推入 8 位 space,这使得它们无法识别正确的 UTF-8 编码。
这就是 Ruby inspect
以引号形式输出编码字符串的原因,它将每个字符显示为其十六进制数 "\xC7..."
。
如果你想从中得到一些相似的字符,你可以将乱码重新编码为支持 8 位字符的 ISO8859-1。
这样做会得到以下结果:
s = "\xC7\xE4\xEB\xEB\xEE \xF6\xEE\xF1\xEB\xE3!"
>> s.encoding
=> #<Encoding:UTF-8>
# Re-encode as ISO8859-1.
# Your terminal (and Ruby) is using UTF-8, so Ruby will refuse to print these yet.
>> s.force_encoding('iso8859-1')
=> "\xC7\xE4\xEB\xEB\xEE \xF6\xEE\xF1\xEB\xE3!"
# In order to be able to print ISO8859-1 on an UTF-8 terminal, you have to
# convert them back to UTF-8 by re-encoding. This way your terminal (and Ruby)
# can display the ISO8859-1 8-bit characters using UTF-8 encoding:
>> s.encode('UTF-8')
=> "Çäëëî öîñëã!"
# Another way is just to repack the bytes into UTF-8:
>> s.bytes.pack('U*')
=> "Çäëëî öîñëã!"
当然,正确的做法是在任何情况下都不要让数字溢出到 8 位 space。你的加密算法有bug,需要保证输出在7位ASCII范围内
更好的解决方案
就像@tadman 建议的那样,您可以改用 tr
:
AZ_SEQUENCE = *'A'..'Z' + *'a'..'z'
"Hello world!".tr(AZ_SEQUENCE.join, AZ_SEQUENCE.rotate(127).join)
=> "eBIIL tLOIA!