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

已认证加密和关联数据 (AEAD)

如果使用的 OpenSSL 版本支持,应始终优先选择已认证加密模式(如 GCM 或 CCM)而非任何未认证模式。当前,OpenSSL 仅在与关联数据 (AEAD) 结合使用时支持 AE,其中额外的关联数据包含在加密过程中,以便在加密结束时生成标签。此标签也将用于解密过程,通过验证其有效性来建立给定密文的真实性。

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

关联数据,也称为附加已认证数据 (AAD),是可选使用的,其中存在必须同时进行认证但不一定需要加密的附加信息,例如头部或一些元数据。

使用 GCM(Galois/Counter Mode)的示例。您有 16 字节的 *key*,12 字节(96 位)*nonce* 和关联数据 *auth_data*。请确保不要重复使用 *key* 和 *nonce* 对。重复使用 nonce 会破坏 GCM 模式的安全保证。

key = OpenSSL::Random.random_bytes(16)
nonce = OpenSSL::Random.random_bytes(12)
auth_data = "authenticated but unencrypted data"
data = "encrypted data"

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(16)

现在您是接收者。您知道 *key*,并通过不可信的网络收到了 *nonce*、*auth_data*、*encrypted* 和 *tag*。请注意,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 # could be called at any time before #final
decipher.auth_data = auth_data

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

puts data == decrypted #=> true

请注意,其他 AEAD 密码可能需要额外的步骤,例如提前设置预期的标签长度(auth_tag_len=)或总数据长度(ccm_data_len=)。请务必阅读相关手册页以获取详细信息。