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

时间:2019-03-26 08:01:13

标签: ruby-on-rails ruby openssl encryption-symmetric

我正在尝试解密在我的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

这只是显示错误的魔术数字

1 个答案:

答案 0 :(得分:1)

除非您了解并处理了两个系统如何进行加密的许多复杂细节,否则尝试对在不同系统中加密的数据进行解密是行不通的。尽管Rails和openssl命令行工具都在后台使用OpenSSL库进行加密操作,但是它们都以各自独特的方式使用了OpenSSL库,这些方法不能直接进行互操作。

如果您靠近两个系统,将会看到例如:

  • 启用消息加密器不仅对消息进行加密,还对消息进行签名
  • Rails加密器使用Marshal来序列化输入数据
  • openssl enc工具期望加密的数据以具有Salted__<salt>标头的独特文件格式显示(这就是为什么您从{{1}获得坏魔术数字消息的原因})
  • openssl工具必须正确配置为使用与Rails加密器和密钥生成器相同的密码,因为openssl的默认设置与Rails的默认设置不同
  • 从Rails 5.2开始,默认密码配置发生了显着变化。

使用此一般信息,我们可以看一个实际示例。它已在Rails 4.2中进行了测试,但在Rails 5.1之前同样可以正常工作。

Rails加密消息的剖析

让我从您提供的经过稍微修改的代码开始。唯一的更改是将opensslpassword预设为静态值并打印很多调试信息:

salt

运行此命令时,将获得类似以下输出的内容:

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消息加密器使用 --密码(请注意,自Rails 5.2起,此更改已更改)。该密码需要一个初始化向量,该向量由Rails随机生成,并且必须存在于加密输出中,以便我们可以将其与密钥一起使用来解密消息。

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

Marshal

重新运行该代码所产生的输出比以前的版本短一些,因为加密的数据现在尚未序列化:

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

最后,我们必须找到有关加密密钥派生过程的一些信息。 source告诉我们,密钥生成器将 salt (hexa) = 73616c7473616c74 key (hexa) = 196827b250431e911310f5dbc82d395782837b7ae56230dce24e497cf07b6518 output (base64) = SUlIWFBjSXRUc0JodEMzLzhXckJzUT09LS1oZGtPV1ZRc2I5Wi8zOG01dFNOdVdBPT0=--58bbaf983fd20459062df8b6c59eb470311cbca9 算法与pbkdf2_hmac_sha1迭代一起使用,以从密码/机密中得出密钥。

2**16 = 65536加密邮件的剖析

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

openssl enc

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

Salted__工具默认使用EVP_BytesToKey(请参阅source)来导出密钥,但可以配置为使用{{1} }和openssl选项。可以使用pbkdf2_hmac_sha1选项设置迭代次数。

如何在-pbkdf2中解密Rails加密的消息

因此,最终我们有了足够的信息来实际尝试对-md sha1中的Rails加密消息进行解密。

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

-iter

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

openssl

现在,我们需要获取经过Rails加密的数据,并将其转换为> Base64.strict_decode64("SUlIWFBjSXRUc0JodEMzLzhXckJzUT09LS1oZGtPV1ZRc2I5Wi8zOG01dFNOdVdBPT0=") => "IIHXPcItTsBhtC3/8WrBsQ==--hdkOWVQsb9Z/38m5tSNuWA==" 可以识别的格式,即在其前面加上魔术字符串和salt,然后再次对其进行Base64编码:

openssl

最后,我们可以构造> Base64.strict_decode64("hdkOWVQsb9Z/38m5tSNuWA==").unpack("H*").first => "85d90e59542c6fd67fdfc9b9b5236e58" # the initialization vector in hex form 命令来解密数据:

openssl

而且,我们成功解密了初始消息!

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

  • openssl设置与Rails用于加密的密码相同的密码
  • $ 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将用于导出加密密钥的密码设置为“密码”
  • -d-iv设置与Rails(-pass pass:password)相同的密钥派生算法
  • -pbkdf2为密钥派生设置了与Rails相同的迭代次数
  • -md sha1允许使用Base64编码的加密数据-无需处理文件中的原始字节

默认情况下,pbkdf2_hmac_sha1从STDIN读取数据,因此我们仅使用回显将加密的数据(格式正确)传递到-iter 65536

调试

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

openssl

openssl-P$ echo ... | openssl ... -P salt=73616C7473616C74 key=196827B250431E911310F5DBC82D395782837B7AE56230DCE24E497CF07B6518 iv =85D90E59542C6FD67FDFC9B9B5236E58 的值必须与上面打印的salt方法中原始代码打印的调试值相对应。如果它们不同,则说明您做错了事...

现在,我想您在尝试解密go中的消息时会遇到类似的问题,但是我认为您现在有一些很好的指针可以开始。