Ruby openssl gem 等效于用于 aes-256-cbc 加密的 openssl 命令

问题描述 投票:0回答:2

什么是 ruby(openssl gem 的 cypher / pbkdf2_hmac 咒语)相当于用这样的命令加密:

echo 'Top secret text' | openssl enc -base64 -e -aes-256-cbc -salt -pass pass:'mypassword' -pbkdf2 -p

我正在尝试使用 ruby openssl gem 对纯文本字符串进行 AES-256-CBC 加密。我找到了很多关于如何做到这一点的例子,但是然后我希望能够给我的朋友提供在另一端解密它所必需的unix

openssl
命令,这意味着我需要使用相同的设置和unix 命令执行的字符串连接方法。

我想出了一对 unix 命令来加密然后解密。我正在使用 PBKDF2 密钥派生。我已将这些命令包装在 ruby 代码中以执行系统调用,所以我的

encrypt_openssl_system_call
成功地反转了
decrypt
.

我希望我的

open_ssl_gem_encrypt
方法也可以使用
decrypt
解密...目前它几乎可以工作,因为我作弊并通过了
@salt_used
@key_used
@iv_used
。相反,我需要找出正确的方法来调用
OpenSSL::KDF.pbkdf2_hmac
(或类似的?)来进行密钥推导。

但是通过匹配这些值,我想我希望看到它加密为完全相同的密文,或者至少得出正确的纯文本。奇怪的是,它几乎可以工作,生成纯文本,但开头有 14 个随机字符。

require 'openssl'
require 'base64'

def password
  "mypassword"
end

def encrypt_openssl_system_call(plain_text)
  command = "echo '#{plain_text}' | openssl enc -base64 -e -aes-256-cbc -salt -pass pass:'#{password}' -pbkdf2 -p"
  puts command
  output = `#{command}`
  puts output
  raise(output) unless $?.success?

  # Parse the actual used salt key and iv from this output
  rows = output.split("\n")
  @salt_used = [rows[0].split("salt=").last].pack('H*')
  @key_used = [rows[1].split("key=").last].pack('H*')
  @iv_used = [rows[2].split("iv =").last].pack('H*')
  encrypted = rows.last

  encrypted.rstrip!
  encrypted
end

def decrypt(encrypted)
  command = "echo '#{encrypted}' | openssl enc -base64 -d -aes-256-cbc -salt -pass pass:'#{password}' -pbkdf2"
  puts command
  output = `#{command}`

  raise output unless $?.success?

  output.rstrip
rescue RuntimeError => e
  puts ">>> ERROR #{e.message}"
  puts e.backtrace

  e.message
end

def open_ssl_gem_encrypt(plain_text)
  # Key derivation (PBKDF2) not working yet
 # salt = 'Salted__' + OpenSSL::Random.random_bytes(8)
 # key = OpenSSL::KDF.pbkdf2_hmac(password, salt: salt, iterations: 10000, length: 32, hash: "sha1")

  salt = 'Salted__' + @salt_used
  key = @key_used

  cipher = OpenSSL::Cipher.new('AES-256-CBC')
  cipher.encrypt
  cipher.key = key
  iv = @iv_used # cipher.random_iv
  cipher.iv  = iv

  # Concatenate bits in hopefully the right order?!
  encrypted = salt
  encrypted << iv
  encrypted << cipher.update(plain_text)
  encrypted << cipher.final

  Base64.encode64(encrypted).gsub(/\n/, '')
end

PLAIN_TEXT = "Top secret text"

puts "encrypt_openssl_system_call(#{PLAIN_TEXT.dump})"
encrypted = encrypt_openssl_system_call(PLAIN_TEXT)
puts "openssl command produced '#{encrypted}'"

puts "\n"
puts "decrypt('#{encrypted}')"
decrypted = decrypt(encrypted)
puts "openssl command decryption produced '#{decrypted}'"

puts "So far so good!" if decrypted==PLAIN_TEXT

puts "\n"
puts "open_ssl_gem_encrypt(#{PLAIN_TEXT.dump})"
encrypted_b = open_ssl_gem_encrypt(PLAIN_TEXT)
puts "OpenSSL gem produced '#{encrypted_b}'"

puts "\n"
puts "decrypt(#{encrypted_b.dump})"
decrypted_b = decrypt(encrypted_b)
puts "openssl command decryption produced '#{decrypted_b}'"

输出:

encrypt_openssl_system_call("Top secret text")
echo 'Top secret text' | openssl enc -base64 -e -aes-256-cbc -salt -pass pass:'mypassword' -pbkdf2 -p
salt=27BD7552C3308BBA
key=B99FCDC5F9296AD2B1488E49B8CD29EDF0D15E13C408B1EEB11A2050F6403E94
iv =1294745B0A06C42939283E51EAE29E5E
U2FsdGVkX18nvXVSwzCLuhZeGj9c+fKLG+nDaitgeRahxKftT20Rax2sYFpjiO3h
openssl command produced 'U2FsdGVkX18nvXVSwzCLuhZeGj9c+fKLG+nDaitgeRahxKftT20Rax2sYFpjiO3h'

decrypt('U2FsdGVkX18nvXVSwzCLuhZeGj9c+fKLG+nDaitgeRahxKftT20Rax2sYFpjiO3h')
echo 'U2FsdGVkX18nvXVSwzCLuhZeGj9c+fKLG+nDaitgeRahxKftT20Rax2sYFpjiO3h' | openssl enc -base64 -d -aes-256-cbc -salt -pass pass:'mypassword' -pbkdf2
openssl command decryption produced 'Top secret text'
So far so good!

open_ssl_gem_encrypt("Top secret text")
OpenSSL gem produced 'U2FsdGVkX18nvXVSwzCLuhKUdFsKBsQpOSg+Uerinl4DIK8yljJ2aHCR+8m9Yrq3'

decrypt("U2FsdGVkX18nvXVSwzCLuhKUdFsKBsQpOSg+Uerinl4DIK8yljJ2aHCR+8m9Yrq3")
echo 'U2FsdGVkX18nvXVSwzCLuhKUdFsKBsQpOSg+Uerinl4DIK8yljJ2aHCR+8m9Yrq3' | openssl enc -base64 -d -aes-256-cbc -salt -pass pass:'mypassword' -pbkdf2
openssl command decryption produced ' |�S��p��C��H�PTop secret text'

部分成功表明我肯定至少以正确的顺序将字符串放在一起!?

openssl gem 版本为 2.1.2

openssl 版本是“LibreSSL 3.3.6”

ruby aes
2个回答
0
投票

OpenSSL,当使用密码/salt 时,将结果存储为

Salted__
的 ASCII 编码的串联,后跟 8 字节 salt 和实际密文(-base64 选项另外执行 Base64 编码)。

在目前的代码中,

open_ssl_gem_encrypt()
在salt之后额外写了IV,这是不正确的。要解决此问题,必须删除行
encrypted << iv
。那么输出是:

encrypt_openssl_system_call("Top secret text")
echo 'Top secret text' | openssl enc -base64 -e -aes-256-cbc -salt -pass pass:'mypassword' -pbkdf2 -p
salt=6C70B083F3E5820D
key=091EB1C0043A17F1CB7932023BEBED42E36A8EB05709A93B8A35CBD02CAAEFEF
iv =C7D740EF0B0AB71E45C21A95C4004ADC
U2FsdGVkX19scLCD8+WCDRSr0MrSj82D5EOvO4nmG0jkd4KVyApoO2mIB1Tn7B8v
openssl command produced 'U2FsdGVkX19scLCD8+WCDRSr0MrSj82D5EOvO4nmG0jkd4KVyApoO2mIB1Tn7B8v'

decrypt('U2FsdGVkX19scLCD8+WCDRSr0MrSj82D5EOvO4nmG0jkd4KVyApoO2mIB1Tn7B8v')
echo 'U2FsdGVkX19scLCD8+WCDRSr0MrSj82D5EOvO4nmG0jkd4KVyApoO2mIB1Tn7B8v' | openssl enc -base64 -d -aes-256-cbc -salt -pass pass:'mypassword' -pbkdf2
openssl command decryption produced 'Top secret text'
So far so good!

open_ssl_gem_encrypt("Top secret text")
OpenSSL gem produced 'U2FsdGVkX19scLCD8+WCDUflsXl8c0g44eFNBNy3S5Q='

decrypt("U2FsdGVkX19scLCD8+WCDUflsXl8c0g44eFNBNy3S5Q=")
echo 'U2FsdGVkX19scLCD8+WCDUflsXl8c0g44eFNBNy3S5Q=' | openssl enc -base64 -d -aes-256-cbc -salt -pass pass:'mypassword' -pbkdf2
openssl command decryption produced 'Top secret text'

可以看出,用

open_ssl_gem_encrypt()
生成的密文现在可以解密了!


但是,使用

encrypt_openssl_system_call()
open_ssl_gem_encrypt()
生成的密文是不同的,这不应该是不同的,因为您的代码在这两种情况下都应用了 same 盐。
原因是在
encrypt_openssl_system_call()
中产生了一个换行符,这可以通过使用
-n
来防止(s. here)。如果使用
-n
,则输出为:

encrypt_openssl_system_call("Top secret text")
echo -n 'Top secret text' | openssl enc -base64 -e -aes-256-cbc -salt -pass pass:'mypassword' -pbkdf2 -p
salt=F7083C17A99C44C2
key=E424DA2D1AD290B3FA89829495C3F04898150E1C722B2B86159CE10610553BB7
iv =F0ACA075C63C8D8D80F66137645F8333
U2FsdGVkX1/3CDwXqZxEwuInAQktjTeuW7TRmwETBgw=
openssl command produced 'U2FsdGVkX1/3CDwXqZxEwuInAQktjTeuW7TRmwETBgw='

decrypt('U2FsdGVkX1/3CDwXqZxEwuInAQktjTeuW7TRmwETBgw=')
echo 'U2FsdGVkX1/3CDwXqZxEwuInAQktjTeuW7TRmwETBgw=' | openssl enc -base64 -d -aes-256-cbc -salt -pass pass:'mypassword' -pbkdf2
openssl command decryption produced 'Top secret text'
So far so good!

open_ssl_gem_encrypt("Top secret text")
OpenSSL gem produced 'U2FsdGVkX1/3CDwXqZxEwuInAQktjTeuW7TRmwETBgw='

decrypt("U2FsdGVkX1/3CDwXqZxEwuInAQktjTeuW7TRmwETBgw=")
echo 'U2FsdGVkX1/3CDwXqZxEwuInAQktjTeuW7TRmwETBgw=' | openssl enc -base64 -d -aes-256-cbc -salt -pass pass:'mypassword' -pbkdf2
openssl command decryption produced 'Top secret text'

现在,使用相同的盐,生成的两个密文是相同的!


使用 salt/password 加密的常见实现是首先生成一个随机的 8 字节 salt(在注释掉的部分可能已经尝试过),然后与密码和密钥推导函数一起,密钥和IV 被计算出来。
使用以这种方式导出的密钥和 IV,然后执行加密。一个可能的实现是:

def open_ssl_gem_encrypt_v2(plain_text)

  # Key derivation (PBKDF2) 
  salt = OpenSSL::Random.random_bytes(8)
  keyIv = OpenSSL::KDF.pbkdf2_hmac(password, salt: salt, iterations: 10000, length: 32+16, hash: "sha256")
  key = keyIv[0..31]
  iv = keyIv[32..-1]

  # Encrypt
  cipher = OpenSSL::Cipher.new('AES-256-CBC')
  cipher.encrypt
  cipher.key = key
  cipher.iv  = iv
  ciphertext = cipher.update(plain_text) + cipher.final

  # Concatenate bits
  encrypted = 'Salted__' + salt + ciphertext

  Base64.encode64(encrypted).gsub(/\n/, '')
end

如果使用这个实现而不是旧的,输出的最后一部分是例如:

...
So far so good!

open_ssl_gem_encrypt("Top secret text")
OpenSSL gem produced 'U2FsdGVkX18KvUdJOd/Pz0Sf0FmNMA2HzQWeXkR63Y8='

decrypt("U2FsdGVkX18KvUdJOd/Pz0Sf0FmNMA2HzQWeXkR63Y8=")
echo 'U2FsdGVkX18KvUdJOd/Pz0Sf0FmNMA2HzQWeXkR63Y8=' | openssl enc -base64 -d -aes-256-cbc -salt -pass pass:'mypassword' -pbkdf2
openssl command decryption produced 'Top secret text'

可以看出,使用OpenSSL成功解密密文


注意 OpenSSL 在早期版本中默认使用 MD5 作为密钥派生函数的摘要,从版本 v1.1.0 开始默认使用 SHA256。
为了解密成功,密钥派生函数

KDF.pbkdf2_hmac()
中使用的摘要必须与用于解密的OpenSSL版本的摘要相匹配。
可以使用 -md 选项在 OpenSSL 语句中显式设置摘要,这样您就不会受限于默认摘要 s。 openssl enc.


0
投票

你可以使用我的 gem evp_bytes_to_key。来自自述文件:

此 gem 是 OpenSSL

EVP_BytesToKey()
函数的纯 Ruby 实现,因为它由
openssl
命令行实用程序使用。此函数用于从给定密码生成密钥和 IV。 (和可选的盐)

此 gem 的目的是通过复制用于从给定密码派生密钥和 IV 的逻辑,使加密或解密由

openssl
在命令行上使用密码加密或解密的数据变得更加容易。

examples 部分下,我有一个几乎与您的初始请求重复的示例。

aes256
加盐和静脉注射

openssl
加密:

echo -n "foo" | openssl enc -e -base64 -aes256 -S 73616c7473616c74 -pass pass:password

注意盐值是

saltsalt
openssl
要求是十六进制格式
73616c7473616c74
.

返回:

U2FsdGVkX19zYWx0c2FsdOnid6UWvFAXeeXIe+sL0l8=

用 Ruby 解密:

require 'openssl'
require 'base64'
require 'evp_bytes_to_key'

key = EvpBytesToKey::Key.new('password', 'saltsalt', 256, 16)
decipher = OpenSSL::Cipher.new('aes256')
decipher.decrypt
decipher.key = key.key
decipher.iv = key.iv
ciphertext = Base64.strict_decode64('U2FsdGVkX19zYWx0c2FsdOnid6UWvFAXeeXIe+sL0l8=')
ciphertext = ciphertext.byteslice(16..-1) if ciphertext.byteslice(0, 8) == 'Salted__'
plaintext = decipher.update(ciphertext) + decipher.final

返回:

"foo"

用 Ruby 加密:

require 'openssl'
require 'base64'
require 'evp_bytes_to_key'

salt = 'saltsalt'
key = EvpBytesToKey::Key.new('password', salt, 256, 16)
cipher = OpenSSL::Cipher.new('aes256')
cipher.encrypt
cipher.key = key.key
cipher.iv = key.iv
ciphertext = cipher.update('foo') + cipher.final
ciphertext = "Salted__#{salt}#{ciphertext}" if salt
ciphertext = Base64.strict_encode64(ciphertext)

返回:

"U2FsdGVkX19zYWx0c2FsdOnid6UWvFAXeeXIe+sL0l8="

openssl
解密:

echo -n "U2FsdGVkX19zYWx0c2FsdOnid6UWvFAXeeXIe+sL0l8=" | base64 --decode | openssl enc -d -aes256 -S 73616c7473616c74 -pass pass:password

返回:

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