尝试使用已在rails中加密的openssl / golang解密字符串

问题描述 投票:3回答:1

我正在尝试解密在我的rails项目中加密的字符串。这就是我加密数据的方式:

def encrypt_text(text_To_encrypt)
        # 0. generate the key using command openssl rand -hex 16 on linux machines
        # 1. Read the secret from config
        # 2. Read the salt from config
        # 3. Encrypt the data
        # 4. return the encypted data
        # Ref: http://www.monkeyandcrow.com/blog/reading_rails_how_does_message_encryptor_work/
        secret = Rails.configuration.miscconfig['encryption_key']
        salt = Rails.configuration.miscconfig['encryption_salt']
        key = ActiveSupport::KeyGenerator.new(secret).generate_key(salt, 32)
        crypt = ActiveSupport::MessageEncryptor.new(key)
        encrypted_data = crypt.encrypt_and_sign(text_To_encrypt)
        encrypted_data
end

现在问题是我无法使用openssl解密它。它只显示了不好的幻数。一旦我在open ssl中执行此操作,我的计划是在golang中解密它。

以下是我尝试使用openssl解密它的方法:

openssl enc -d -aes-256-cbc -salt -in encrypted.txt -out decrypted.txt -d -pass pass:<the key given in rails> -a

这只是显示了不好的幻数

ruby-on-rails ruby openssl encryption-symmetric
1个回答
1
投票

尝试解密在不同系统中加密的数据将无法工作,除非您了解并处理两个系统如何进行加密的许多复杂细节。虽然Rails和openssl命令行工具都使用OpenSSL库进行加密操作,但它们都以自己不同的方式使用它,而这些方式不能直接互操作。

如果你看起来接近这两个系统,你会看到例如:

  • Rails消息加密器不仅对消息进行加密,还对其进行签名
  • Rails加密器使用Marshal来序列化输入数据
  • openssl enc工具期望使用Salted__<salt>标头以不同的文件格式加密数据(这就是为什么你从openssl得到了错误的幻数信息)
  • 必须正确配置openssl工具以使用与Rails加密器和密钥生成器相同的密码,因为openssl默认值与Rails默认值不同
  • 自Rails 5.2以来,默认密码配置发生了显着变化。

通过这个一般信息,我们可以看一个实际的例子。它在Rails 4.2中进行了测试,但应该同样适用于Rails 5.1。

Rails加密消息的剖析

让我先介绍一下您提供的略微修改的代码。唯一的变化是将passwordsalt预设为静态值并打印大量调试信息:

def encrypt_text(text_to_encrypt)
  password = "password" # the password to derive the key
  salt = "saltsalt" # salt must be 8 bytes

  key = ActiveSupport::KeyGenerator.new(password).generate_key(salt, 32)

  puts "salt (hexa) = #{salt.unpack('H*').first}" # print the saltin HEX
  puts "key (hexa) = #{key.unpack('H*').first}" # print the generated key in HEX

  crypt = ActiveSupport::MessageEncryptor.new(key)
  output = crypt.encrypt_and_sign(text_to_encrypt)
  puts "output (base64) = #{output}"
  output
end

encrypt_text("secret text")

当你运行它时,你会得到类似以下输出:

salt (hexa) = 73616c7473616c74
key (hexa) = 196827b250431e911310f5dbc82d395782837b7ae56230dce24e497cf07b6518
output (base64) = SGRTUXYxRys1N1haVWNpVWxxWTdCMHlyMk15SnQ0dWFBOCt3Z0djWVdBZz0tLTkrd1hBNWJMVm9HcnptZ3loOG1mNHc9PQ==--80d091e8799776113b2c0efd1bf75b344bf39994

最后一行(encrypt_and_sign方法的输出)是由--分隔的两个部分的组合(参见source):

  1. 加密的消息(Base64编码)和
  2. 消息签名(Base64编码)。

签名对于加密并不重要,让我们来看看第一部分 - 让我们在Rails控制台中对其进行解码:

> Base64.strict_decode64("SGRTUXYxRys1N1haVWNpVWxxWTdCMHlyMk15SnQ0dWFBOCt3Z0djWVdBZz0tLTkrd1hBNWJMVm9HcnptZ3loOG1mNHc9PQ==")
=> "HdSQv1G+57XZUciUlqY7B0yr2MyJt4uaA8+wgGcYWAg=--9+wXA5bLVoGrzmgyh8mf4w=="

您可以看到解码的消息再次由两个由--分隔的Base64编码部分组成(请参阅source):

  1. 加密的消息本身
  2. 加密中使用的初始化向量

Rails消息加密器默认使用aes-256-cbc密码(请注意,自Rails 5.2以来,这已经发生了变化)。此密码需要一个初始化向量,该向量由Rails随机生成,并且必须存在于加密输出中,以便我们可以将其与密钥一起用于解密消息。

此外,Rails不会将输入数据加密为简单的纯文本,而是加密数据的序列化版本,默认使用Marshal序列化程序(source)。如果我们用openssl解密这样的序列化值,我们仍会得到初始纯文本数据的略微乱码(序列化)版本。这就是为什么在加密Rails中的数据时禁用序列化更合适的原因。这可以通过将参数传递给加密方法来完成:

  # crypt = ActiveSupport::MessageEncryptor.new(key)
  crypt = ActiveSupport::MessageEncryptor.new(key, serializer: ActiveSupport::MessageEncryptor::NullSerializer)

重新运行代码会产生比先前版本略短的输出,因为加密数据现在还没有被序列化:

salt (hexa) = 73616c7473616c74
key (hexa) = 196827b250431e911310f5dbc82d395782837b7ae56230dce24e497cf07b6518
output (base64) = SUlIWFBjSXRUc0JodEMzLzhXckJzUT09LS1oZGtPV1ZRc2I5Wi8zOG01dFNOdVdBPT0=--58bbaf983fd20459062df8b6c59eb470311cbca9

最后,我们必须找到有关加密密钥派生过程的一些信息。 source告诉我们KeyGenerator使用pbkdf2_hmac_sha1算法和2**16 = 65536迭代来从密码/秘密中导出密钥。

解剖openssl加密消息

现在,需要在openssl方面进行类似的调查,以了解其解密过程的细节。首先,如果使用openssl enc工具加密任何内容,您会发现输出具有不同的格式:

Salted__<salt><encrypted_message>

它以Salted__魔术字符串开头,然后是盐(以十六进制形式),最后是加密数据。为了能够使用此工具解密任何数据,我们必须将加密数据转换为相同的格式。

openssl工具使用EVP_BytesToKey(参见source)默认情况下导出密钥,但可以配置为使用pbkdf2_hmac_sha1-pbkdf2选项使用-md sha1算法。可以使用-iter选项设置迭代次数。

如何在openssl中解密Rails加密的消息

所以,最后我们有足够的信息来实际尝试解密openssl中的Rails加密消息。

首先,我们必须再次解码Rails加密输出的第一部分,以获取加密数据和初始化向量:

> Base64.strict_decode64("SUlIWFBjSXRUc0JodEMzLzhXckJzUT09LS1oZGtPV1ZRc2I5Wi8zOG01dFNOdVdBPT0=")
=> "IIHXPcItTsBhtC3/8WrBsQ==--hdkOWVQsb9Z/38m5tSNuWA=="

现在让我们采用IV(第二部分)并将其转换为十六进制字符串形式,因为这是openssl需要的形式:

> Base64.strict_decode64("hdkOWVQsb9Z/38m5tSNuWA==").unpack("H*").first
=> "85d90e59542c6fd67fdfc9b9b5236e58"  # the initialization vector in hex form

现在我们需要获取Rails加密的数据并将其转换为openssl将识别的格式,即将其添加到魔术字符串和salt并再次对其进行Base64编码:

> Base64.strict_encode64("Salted__" + "saltsalt" + Base64.strict_decode64("IIHXPcItTsBhtC3/8WrBsQ=="))
=> "U2FsdGVkX19zYWx0c2FsdCCB1z3CLU7AYbQt//FqwbE=" # encrypted data suitable for openssl

最后,我们可以构造openssl命令来解密数据:

$ echo  "U2FsdGVkX19zYWx0c2FsdCCB1z3CLU7AYbQt//FqwbE=" | 
> openssl enc -aes-256-cbc -d -iv 85d90e59542c6fd67fdfc9b9b5236e58 \
>   -pass pass:password -pbkdf2 -iter 65536 -md sha1 -a
secret text

瞧,我们成功解密了最初的消息!

openssl参数如下:

  • -aes-256-cbc设置与Rails用于加密相同的密码
  • -d代表解密
  • -iv以十六进制字符串形式传递初始化向量
  • -pass pass:password设置用于将加密密钥派生为“密码”的密码
  • -pbkdf2-md sha1设置了与Rails(pbkdf2_hmac_sha1)使用的密钥推导算法相同的密钥推导算法
  • -iter 65536设置与Rails中相同的密钥派生迭代次数
  • -a允许使用Base64编码的加密数据 - 无需处理文件中的原始字节

默认情况下,openssl从STDIN读取,因此我们只需使用echo将加密数据(格式正确)传递给openssl

debugging

如果您在使用openssl解密时遇到任何问题,将-P参数添加到命令行会很有用,该命令行输出有关密码/密钥参数的调试信息:

$ echo ... | openssl ... -P
salt=73616C7473616C74
key=196827B250431E911310F5DBC82D395782837B7AE56230DCE24E497CF07B6518
iv =85D90E59542C6FD67FDFC9B9B5236E58

saltkeyiv值必须与上面打印的encrypt_text方法中原始代码打印的调试值相对应。如果它们不同,你知道你做错了什么......

现在,我猜你在尝试解密消息时会遇到类似的问题,但我认为你现在有一些好的指针可以启动。

© www.soinside.com 2019 - 2024. All rights reserved.