Ruby OpenSSL::Cipher 在 CBC 模式下给出不同的结果 Triple DES

Ruby OpenSSL::Cipher giving different result Tripple DES in CBC Mode

我需要在 CBC 模式下使用双倍长度的 Triple-DES (TDEA) 对称密钥来加密数据。我正在使用 Ruby OpenSSl::Cipher。但与 BP 工具密码计算器和 EMV DES 计算器相比,它给出了不同的结果。以下是我的代码

任何人都可以帮助我为什么我的结果不同?我也有检查在线工具来验证我的结果。

http://www.emvlab.org/descalc/?key=2315208C9110AD402315208C9110AD40&iv=0000000000000000&input=20205A4F534135366461746574696D653D32303138313032343130303332333B6578706972793D313232323B70616E3D343233363031373839303132333435362121212121212121&mode=cbc&action=Encrypt&output=20205A4F534135366461746574696D653D32303138313032343130303332333B6578706972793D313232323B70616E3D343233363031373839303132333435362121212121212121

它给出与 BP 工具相同的结果。

flavour = 'des-ede3-cbc'
# key and input text are in hexadecimal  
key = "2315208C9110AD402315208C9110AD40"
iv = "0000000000000000"
input_text = "2020205a4f534135366461746574696d653d32303138313032343130303332333b6578706972793d313232323b70616e3d343233363031373839303132333435362121212121212121"
begin
  c = OpenSSL::Cipher.new flavour
  c.encrypt
  c.key = key
  c.iv = iv
  str = input_text
  enc = c.update(str) + c.final
  puts "#{flavour} gives us #{enc.unpack('H*').first.upcase}"
rescue => e
    puts "#{flavour} didn't work because #{e.message}"
end
Result:
8AF0D655A844EC016E171F1892188DD5C77E6F76D79A4582313D2415D9AD6944E16C7CA680CCD0C251BA8921E7C35153F345126A89ECCD68E4B0485FFDCDA778DBBE32B38451A59AFA443A96C46E30BD7CE983078EA40F8F0196C607FF537E5A36B910A40C3B0DB8C42BD2AB135928B0BDDF6DC85CF516EACCD14E0C21B93CE428A9E4F78A13AE9834966B41C4C1835B1910BE5716D117F7

The expected result should be:
4A9E9B245BBDC16D76998143CB6FC1C2B8780539C1C9A100AEC3D745B8BF00DF43A4B51A29A6205845E510E18E26AB940152F90F12E86543A9E5239B30DFDBCD8D3FCDB65F603979

要获得预期结果,您需要调整代码,以便 OpenSSL 实际上知道您正在使用 hex-encoded 数据。由于 OpenSSL 无论如何都处理纯字节,因此您需要先将十六进制字符串编码为原始字节:

key = ["2315208C9110AD402315208C9110AD40"].pack('H*')
iv = ["0000000000000000"].pack('H*')
input_text = ["2020205a4f534135366461746574696d653d32303138313032343130303332333b6578706972793d313232323b70616e3d343233363031373839303132333435362121212121212121"].pack('H*')

之后,您需要选择正确的加密算法。有多种不同的 3DES 风格,它们(除其他细节外)在预期的密钥长度上有所不同。由于您正在处理一个 16 字节的密钥,看来您正在使用 OpenSSL 调用的 des-ede-cbc

flavour = 'des-ede-cbc'

仅这些调整通常就足以产生与您在链接到的网站上看到的相同的结果。然而,出于某种我不知道的原因,他们在实际加密之前悄悄地更改了输入数据。

我不知道他们使用的具体规则,但要获得与网站相同的密文输出,您需要删除所有尾随感叹号并从输入字符串中删除前导 space 字符。

input_text # packed as you provided it originally
# => "   ZOSA56datetime=20181024100323;expiry=1222;pan=4236017890123456!!!!!!!!"

# remove trailing exclamation marks
input_text = input_text.sub(/!*$/, '') 
# remove the first character
input_text = input_text[1..-1]

最后,您现在可以加密您的 "improved" input_text:

begin
  c = OpenSSL::Cipher.new(flavour)
  c.encrypt
  c.key = key
  c.iv = iv
  enc = c.update(input_text) + c.final
  puts "#{flavour} gives us #{enc.unpack('H*').first.upcase}"
rescue => e
  puts "#{flavour} didn't work because #{e.message}"
end

这应该会产生几乎预期的密文。

虽然最后一个 8 字节块略有不同。我假设该网站使用了一些奇怪的 non-standard 填充(这是确保输入文本可以分为完整的 8 字节块所必需的)导致最终块不同。

在任何情况下,您都可以解密结果(以及 "expected" 网站的密文,代码如下:

flavour = 'des-ede-cbc'
key = ["2315208C9110AD402315208C9110AD40"].pack('H*')
iv = ["0000000000000000"].pack('H*')
encrypted = ["4A9E9B245BBDC16D76998143CB6FC1C2B8780539C1C9A100AEC3D745B8BF00DF43A4B51A29A6205845E510E18E26AB940152F90F12E86543A9E5239B30DFDBCD8D3FCDB65F603979"].pack('H*')

c = OpenSSL::Cipher.new(flavour)
c.decrypt
c.key = key
c.iv = iv

decrypted = c.update(encrypted)

如何使填充正确以匹配网站的结果将作为练习留给 reader :) 不幸的是,我没有找到他们工具的任何文档或源代码。

无论如何,请注意 3DES 是一种非常过时的加密算法,它不再被认为是安全的。除非你必须实际使用 3DES,否则你应该使用更安全的算法。

libsodium 项目提供了用于加密和签名的安全算法的强化实现,可以为您所需的协议形成有效和安全的基础。 Ruby 绑定可用 rbnacl gem.