class OpenSSL::Cipher

为加密和解密提供对称算法。可用的算法取决于安装的 OpenSSL 的特定版本。

列出所有支持的算法

可以通过以下方式获取支持的算法列表

puts OpenSSL::Cipher.ciphers

实例化一个 Cipher

有几种方法可以创建 Cipher 实例。一般来说,Cipher 算法按其名称、以位为单位的密钥长度以及要使用的密码模式进行分类。创建 Cipher 的最通用方法如下

cipher = OpenSSL::Cipher.new('<name>-<key length>-<mode>')

也就是说,一个字符串,由名称、密钥长度和模式的各个组件的连字符连接组成。可以使用全部大写或全部小写的字符串,例如

cipher = OpenSSL::Cipher.new('aes-128-cbc')

选择加密或解密模式

对于对称算法,加密和解密通常是非常相似的操作,这反映在不必为任一操作选择不同的类,两者都可以使用同一个类完成。但是,在获得 Cipher 实例后,我们需要告诉实例我们打算用它做什么,因此我们需要调用

cipher.encrypt

cipher.decrypt

Cipher 实例上。这应该是创建实例后的第一次调用,否则已经设置的配置可能会在此过程中丢失。

选择密钥

对称加密需要一个密钥,该密钥对于加密方和解密方是相同的,并且在初始密钥建立后应作为私人信息保留。有很多方法可以创建不安全的密钥,最值得注意的是简单地将密码作为密钥而不进一步处理密码。为特定 Cipher 创建密钥的简单而安全的方法是

cipher = OpenSSL::Cipher.new('aes-256-cfb')
cipher.encrypt
key = cipher.random_key # also sets the generated key on the Cipher

如果您绝对需要将密码用作加密密钥,则应使用基于密码的密钥派生函数 2 (PBKDF2),方法是借助 OpenSSL::PKCS5.pbkdf2_hmac_sha1OpenSSL::PKCS5.pbkdf2_hmac 提供的功能生成密钥。

尽管有 Cipher#pkcs5_keyivgen,但它的使用已被弃用,应该只在遗留应用程序中使用,因为它不使用较新的 PKCS#5 v2 算法。

选择 IV

密码模式 CBC、CFB、OFB 和 CTR 都需要一个“初始化向量”,或简称 IV。ECB 模式是唯一不需要 IV 的模式,但这种模式几乎没有合法的用例,因为它不能充分隐藏明文模式。因此

除非您绝对确定绝对需要,否则永远不要使用 ECB 模式

因此,您最终将获得一种明确要求 IV 的模式。尽管 IV 可以被视为公共信息,即它可以在生成后公开传输,但它仍然应该是不可预测的,以防止某些类型的攻击。因此,理想情况下

始终为 Cipher 的每次加密创建一个安全的随机 IV

应该为每次数据加密创建一个新的随机 IV。将 IV 视为 nonce(使用一次的数字) - 它是公开的,但随机且不可预测的。可以按如下方式创建安全的随机 IV

cipher = ...
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv # also sets the generated IV on the Cipher

尽管密钥通常也是一个随机值,但它作为 IV 是一个糟糕的选择。攻击者可以利用这种 IV 的方法有很多。作为一般经验法则,应不惜一切代价避免直接或间接暴露密钥,并且只能在有充分理由的情况下例外。

调用 Cipher#final

ECB(不应使用)和 CBC 都是基于块的模式。这意味着与基于流的其他模式不同,它们在固定大小的数据块上运行,因此它们需要一个“最终”步骤,以通过适当处理某种形式的填充来生成或正确解密最后一个数据块。因此,必须将 OpenSSL::Cipher#final 的输出添加到您的加密/解密缓冲区,否则您将最终遇到解密错误或数据截断。

尽管对于流模式密码来说这不是必需的,但仍然建议应用添加 Cipher#final 输出的相同模式 - 这也可以让您将来更容易地在模式之间切换。

加密和解密一些数据

data = "Very, very confidential data"

cipher = OpenSSL::Cipher.new('aes-128-cbc')
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv

encrypted = cipher.update(data) + cipher.final
...
decipher = OpenSSL::Cipher.new('aes-128-cbc')
decipher.decrypt
decipher.key = key
decipher.iv = iv

plain = decipher.update(encrypted) + decipher.final

puts data == plain #=> true

认证加密和关联 Data (AEAD)

如果使用的 OpenSSL 版本支持,则应始终首选认证加密模式(例如 GCM 或 CCM)而不是任何未经认证的模式。目前,OpenSSL 仅在与关联 Data (AEAD) 结合使用时支持 AE,其中附加的关联数据包含在加密过程中,以在加密结束时计算标签。此标签也将在解密过程中使用,并通过验证其有效性来确定给定密文的真实性。

这优于未经认证的模式,因为它允许检测是否有人在加密后有效地更改了密文。这可以防止恶意修改密文,否则可能会被利用以对潜在攻击者有利的方式修改密文。

关联数据用于存在其他信息(例如标头或某些元数据)的情况下,这些信息也必须经过身份验证,但不一定需要加密。如果加密和稍后解密不需要关联数据,则 OpenSSL 库仍然需要设置一个值 - 如果没有可用值,则可以使用“”。

一个使用 GCM(伽罗瓦/计数器模式)的示例。您有 16 个字节的密钥,12 个字节(96 位)的nonce 和关联数据 auth_data。请确保不要重复使用密钥nonce 对。重复使用 nonce 会破坏 GCM 模式的安全保证。

cipher = OpenSSL::Cipher.new('aes-128-gcm').encrypt
cipher.key = key
cipher.iv = nonce
cipher.auth_data = auth_data

encrypted = cipher.update(data) + cipher.final
tag = cipher.auth_tag # produces 16 bytes tag by default

现在您是接收者。您知道密钥,并通过不受信任的网络收到了nonceauth_dataencryptedtag。请注意,GCM 接受 1 到 16 个字节之间的任意长度标签。您可能还需要检查收到的标签是否具有正确的长度,或者您允许攻击者以 1/256 的概率为篡改的密文伪造一个有效的单字节标签。

raise "tag is truncated!" unless tag.bytesize == 16
decipher = OpenSSL::Cipher.new('aes-128-gcm').decrypt
decipher.key = key
decipher.iv = nonce
decipher.auth_tag = tag
decipher.auth_data = auth_data

decrypted = decipher.update(encrypted) + decipher.final

puts data == decrypted #=> true