【问题标题】:Base62 hash of a string字符串的 Base62 哈希
【发布时间】:2019-10-22 07:24:24
【问题描述】:

我想做fingerprint = Digest::SHA256.base64digest(str) 之类的事情,但要使用 base62 而不是 base64。如何有效地构建任何字符串的唯一 base62 编码字符串哈希?

【问题讨论】:

  • 这会有帮助吗?.. rubygems.org/gems/base62
  • 为什么要使用 Base62 而不是 64?
  • @TerenceEden 主要是为了 URL 友好性,总体而言,它看起来更好,并且在剪切和粘贴之类的东西上效果更好,因为它被视为带有 + 和 / 的单个单词
  • 与 Base64 不同,Base62 可以安全地用于 DNS 条目、电子邮件地址和路径名。 [0-9a-zA-Z] 中没有太多歧义——Base64 有多种常见实现,因为它需要的额外 2 个字符总是与某些上下文或其他冲突。

标签: ruby encoding base64


【解决方案1】:

Base 64 广泛用于编码二进制数据,因为 6 位恰好适合一个字符,但仍有足够的可打印 ASCII 字符来表示所有可能的模式。换句话说,64 个可用字符代表从十进制 0 到十进制 63 的所有 64 种不同的位模式。

将二进制数据编码为基数 62 存在几个问题,因为大小为 62 的字母表并不适合。您可以将摘要算法中的二进制数据映射为 32 位块,然后将这些 5 位块中的每一个分配给一个字符。然而,这意味着“v”上面的字符将不再被使用,所以你最终会得到一个基本的 32 编码。

就效率而言,base 62永远不会接近 base64。 Base 64 编码非常简单:取 6 位,将它们映射到一个字符上,重复直到完成。这很简单,因为 64 是 2 的幂。但是,对于基数 62,您将不得不转换为整数并开始在每一步中结转“余数”,因为模式不均匀。

所以我的建议是使用不同的编码,你可能不喜欢。

--

如果您需要 url 安全编码,例如,您可以使用以下之一:

# sample string
str = 'foo'

# original base 64 method for comparison
Digest::SHA256.base64digest(str)
#=> "LCa0a2j/xo/5m0U8HTBBNBNCLXBkg7+g+YpeiGJm564="

# url safe variant (no slash or plus characters)
Base64.urlsafe_encode64(Digest::SHA256.digest(str))
#=> "LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564="

# hexadecimal (base 16)
Digest::SHA256.hexdigest(str)
#=> "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"

# or base 32
# gem install base32
require 'base32'
Base32.encode(Digest::SHA256.digest(str))
#=> "FQTLI23I77DI76M3IU6B2MCBGQJUELLQMSB37IHZRJPIQYTG46XA===="

# or with direct url encoding
# not pretty, but url safe!
require 'open-uri'
URI::encode(Digest::SHA256.digest(str))
#=> ",&%B4kh%FF%C6%8F%F9%9BE%3C%1D0A4%13B-pd%83%BF%A0%F9%8A%5E%88bf%E7%AE"

# or url url escaped base 64
# not pretty, but url safe!
require 'cgi'
CGI::escape(Digest::SHA256.base64digest(str))
#=> "LCa0a2j%2Fxo%2F5m0U8HTBBNBNCLXBkg7%2Bg%2BYpeiGJm564%3D"

--

编辑:这是一个非常非常非常低效的 base62 实现 ;-)

# gem install base62
require 'base62'

def pack_int(str)
  str.unpack('C*').each_with_index.reduce(0){|r,(x,i)| r + (x << 8*i) }
end

def unpack_int(int)
  n = (Math.log2(int)/8).ceil
  n.times.map{|i| (int >> 8*i) & 255 }.pack('C*')
end

def base62_encode(str)
  Base62.encode(pack_int(str))
end

def base62_decode(encoded)
  unpack_int(Base62.decode(encoded))
end

str = "foo"

# encode
digest = Digest::SHA256.digest(str)
fingerprint = base62_encode(digest)
#=> "fTSIMrZT3fDTvW7XDBq1b7nhWa24Zl55EVpsaO3TBBE"

# decode
recovered_digest = base62_decode(fingerprint)
#=> ",&\xB4kh\xFF\xC6\x8F\xF9\x9BE<\x1D0A4\x13B-pd\x83\xBF\xA0\xF9\x8A^\x88bf\xE7\xAE"

digest == recovered_digest
#=> true

【讨论】:

  • 公平点,但在这种情况下,我不太担心效率。主要是要有一个整洁的 URL。 (我尝试使用 _ 而不是 + 和 - 而不是 _ 的 base64,但它很难看。而且我不想只删除额外的 2 个字符。)
  • 好的,因为您似乎并不关心效率,除了您的问题中所述之外,我包括了一个 base 62 示例,取消了 base62 gem。
  • @mahemoff 再次编辑,现在干净多了。干杯!
  • @mahemoff 又一次; unpack_int 中有一个错误(除法后需要ceil!)这导致在某些情况下解码截断。
猜你喜欢
  • 1970-01-01
  • 2013-01-20
  • 2011-08-18
  • 2012-08-23
  • 2019-08-08
  • 2021-12-23
  • 2010-12-01
  • 2023-04-08
  • 2011-04-28
相关资源
最近更新 更多