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_sha1 或 OpenSSL::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=)。请务必阅读相关手册页以获取详细信息。
Public Class Methods
Source
static VALUE
ossl_s_ciphers(VALUE self)
{
VALUE ary;
ary = rb_ary_new();
OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_CIPHER_METH,
add_cipher_name_to_ary,
(void*)ary);
return ary;
}
返回所有可用密码的名称数组。
Source
static VALUE
ossl_cipher_initialize(VALUE self, VALUE str)
{
EVP_CIPHER_CTX *ctx;
const EVP_CIPHER *cipher;
VALUE cipher_holder;
GetCipherInit(self, ctx);
if (ctx) {
ossl_raise(rb_eRuntimeError, "Cipher already initialized!");
}
cipher = ossl_evp_cipher_fetch(str, &cipher_holder);
AllocCipher(self, ctx);
if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1)
ossl_raise(eCipherError, "EVP_CipherInit_ex");
rb_ivar_set(self, id_cipher_holder, cipher_holder);
return self;
}
字符串必须包含有效的密码名称,例如“aes-256-cbc”。
可以通过调用 OpenSSL::Cipher.ciphers 来获取密码名称列表。
Public Instance Methods
Source
static VALUE
ossl_cipher_set_auth_data(VALUE self, VALUE data)
{
EVP_CIPHER_CTX *ctx;
unsigned char *in;
long in_len, out_len;
StringValue(data);
in = (unsigned char *) RSTRING_PTR(data);
in_len = RSTRING_LEN(data);
GetCipher(self, ctx);
if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER))
ossl_raise(eCipherError, "AEAD not supported by this cipher");
if (!ossl_cipher_update_long(ctx, NULL, &out_len, in, in_len))
ossl_raise(eCipherError, "couldn't set additional authenticated data");
return data;
}
为该 Cipher 设置附加已认证数据 (AAD),也称为关联数据。此方法适用于 AEAD 密码。
此字段的内容应该是无敏感数据,它将被添加到密文中以生成用于验证密文内容的认证标签。
此方法必须在设置了 key= 和 iv= 之后,但在开始实际加密或解密(使用 update)之前调用。在某些加密模式下,可能还需要在调用此方法之前调用 auth_tag_len= 和 ccm_data_len=。
另请参阅手册页 EVP_EncryptInit(3) 的“AEAD 接口”部分。此方法在输出缓冲区设置为 NULL 的情况下内部调用 EVP_CipherUpdate()。
Source
static VALUE
ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self)
{
VALUE vtag_len, ret;
EVP_CIPHER_CTX *ctx;
int tag_len = 16;
rb_scan_args(argc, argv, "01", &vtag_len);
if (NIL_P(vtag_len))
vtag_len = rb_attr_get(self, id_auth_tag_len);
if (!NIL_P(vtag_len))
tag_len = NUM2INT(vtag_len);
GetCipher(self, ctx);
if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER))
ossl_raise(eCipherError, "authentication tag not supported by this cipher");
ret = rb_str_new(NULL, tag_len);
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, tag_len, RSTRING_PTR(ret)))
ossl_raise(eCipherError, "retrieving the authentication tag failed");
return ret;
}
获取生成的认证标签。此方法适用于 AEAD 密码,并且应在调用 final 完成加密后调用。
返回的标签将是 *tag_len* 字节长。某些密码模式要求在开始加密之前通过单独调用 auth_tag_len= 来设置所需的长度。
另请参阅手册页 EVP_EncryptInit(3) 的“AEAD 接口”部分。此方法在 EVP_CIPHER_CTX_ctrl() 中使用 EVP_CTRL_AEAD_GET_TAG 内部调用。
Source
static VALUE
ossl_cipher_set_auth_tag(VALUE self, VALUE vtag)
{
EVP_CIPHER_CTX *ctx;
unsigned char *tag;
int tag_len;
StringValue(vtag);
tag = (unsigned char *) RSTRING_PTR(vtag);
tag_len = RSTRING_LENINT(vtag);
GetCipher(self, ctx);
if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER))
ossl_raise(eCipherError, "authentication tag not supported by this cipher");
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, tag))
ossl_raise(eCipherError, "unable to set AEAD tag");
return vtag;
}
设置用于验证密文完整性的认证标签。
必须在调用 final 之前设置认证标签。在 final 调用期间验证标签。
注意:对于 CCM 模式和 OCB 模式,在开始解密之前,必须通过单独调用 auth_tag_len= 来设置预期的标签长度。标签的内容可以在调用 final 之前的任何时间提供。
注意:调用者必须确保传递给此方法的方法的 String 具有所需的长度。某些密码模式支持可变标签长度,此方法可能会接受截断的标签而不会引发异常。
另请参阅手册页 EVP_EncryptInit(3) 的“AEAD 接口”部分。此方法在 EVP_CIPHER_CTX_ctrl() 中使用 EVP_CTRL_AEAD_SET_TAG 内部调用。
Source
static VALUE
ossl_cipher_set_auth_tag_len(VALUE self, VALUE vlen)
{
int tag_len = NUM2INT(vlen);
EVP_CIPHER_CTX *ctx;
GetCipher(self, ctx);
if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER))
ossl_raise(eCipherError, "AEAD not supported by this cipher");
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, NULL))
ossl_raise(eCipherError, "unable to set authentication tag length");
/* for #auth_tag */
rb_ivar_set(self, id_auth_tag_len, INT2NUM(tag_len));
return vlen;
}
Source
static VALUE
ossl_cipher_is_authenticated(VALUE self)
{
EVP_CIPHER_CTX *ctx;
GetCipher(self, ctx);
return (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) ? Qtrue : Qfalse;
}
指示此 Cipher 实例是否使用 AEAD 模式。
Source
static VALUE
ossl_cipher_block_size(VALUE self)
{
EVP_CIPHER_CTX *ctx;
GetCipher(self, ctx);
return INT2NUM(EVP_CIPHER_CTX_block_size(ctx));
}
返回此 Cipher 操作的数据块的大小(以字节为单位)。
Source
static VALUE
ossl_cipher_set_ccm_data_len(VALUE self, VALUE data_len)
{
int in_len, out_len;
EVP_CIPHER_CTX *ctx;
in_len = NUM2INT(data_len);
GetCipher(self, ctx);
if (EVP_CipherUpdate(ctx, NULL, &out_len, NULL, in_len) != 1)
ossl_raise(eCipherError, NULL);
return data_len;
}
设置将在 CCM 模式下由 update 处理的明文/密文消息的总长度。
确保在调用 auth_data= 或 update 之前,在设置了 key= 和 iv= 之后调用此方法。
此方法仅适用于 CCM 模式密码。
另请参阅手册页 EVP_EncryptInit(3) 的“AEAD 接口”部分。
Source
static VALUE
ossl_cipher_decrypt(VALUE self)
{
return ossl_cipher_init(self, 0);
}
Source
static VALUE
ossl_cipher_encrypt(VALUE self)
{
return ossl_cipher_init(self, 1);
}
Source
static VALUE
ossl_cipher_final(VALUE self)
{
EVP_CIPHER_CTX *ctx;
int out_len;
VALUE str;
GetCipher(self, ctx);
str = rb_str_new(0, EVP_CIPHER_CTX_block_size(ctx));
if (!EVP_CipherFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), &out_len)) {
/* For AEAD ciphers, this is likely an authentication failure */
if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) {
/* For AEAD ciphers, EVP_CipherFinal_ex failures are authentication tag verification failures */
ossl_raise(eAuthTagError, "AEAD authentication tag verification failed");
}
else {
/* For non-AEAD ciphers */
ossl_raise(eCipherError, "cipher final failed");
}
}
assert(out_len <= RSTRING_LEN(str));
rb_str_set_len(str, out_len);
return str;
}
返回密码对象中剩余的数据。进一步调用 Cipher#update 或 Cipher#final 是无效的。此调用应始终在加密或解密操作的最后调用,在将整个明文或密文馈送给 Cipher 实例之后。
使用 AEAD 密码进行加密时,在调用 final 后,可以通过 auth_tag 检索认证标签。
使用 AEAD 密码进行解密时,此方法将使用认证标签验证密文和关联数据的完整性,该认证标签必须在调用此方法之前通过 auth_tag= 设置。如果验证失败,将引发 CipherError。
Source
static VALUE
ossl_cipher_set_iv(VALUE self, VALUE iv)
{
EVP_CIPHER_CTX *ctx;
int iv_len = 0;
StringValue(iv);
GetCipher(self, ctx);
if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)
iv_len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx);
if (!iv_len)
iv_len = EVP_CIPHER_CTX_iv_length(ctx);
if (RSTRING_LEN(iv) != iv_len)
ossl_raise(rb_eArgError, "iv must be %d bytes", iv_len);
if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, (unsigned char *)RSTRING_PTR(iv), -1) != 1)
ossl_raise(eCipherError, NULL);
return iv;
}
Source
static VALUE
ossl_cipher_iv_length(VALUE self)
{
EVP_CIPHER_CTX *ctx;
int len = 0;
GetCipher(self, ctx);
if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)
len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx);
if (!len)
len = EVP_CIPHER_CTX_iv_length(ctx);
return INT2NUM(len);
}
返回此 Cipher 的 IV 的预期长度(以字节为单位)。
Source
static VALUE
ossl_cipher_set_iv_length(VALUE self, VALUE iv_length)
{
int len = NUM2INT(iv_length);
EVP_CIPHER_CTX *ctx;
GetCipher(self, ctx);
if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER))
ossl_raise(eCipherError, "cipher does not support AEAD");
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, len, NULL))
ossl_raise(eCipherError, "unable to set IV length");
/*
* EVP_CIPHER_CTX_iv_length() returns the default length. So we need to save
* the length somewhere. Luckily currently we aren't using app_data.
*/
EVP_CIPHER_CTX_set_app_data(ctx, (void *)(VALUE)len);
return iv_length;
}
Source
static VALUE
ossl_cipher_set_key(VALUE self, VALUE key)
{
EVP_CIPHER_CTX *ctx;
int key_len;
StringValue(key);
GetCipher(self, ctx);
key_len = EVP_CIPHER_CTX_key_length(ctx);
if (RSTRING_LEN(key) != key_len)
ossl_raise(rb_eArgError, "key must be %d bytes", key_len);
if (EVP_CipherInit_ex(ctx, NULL, NULL, (unsigned char *)RSTRING_PTR(key), NULL, -1) != 1)
ossl_raise(eCipherError, NULL);
rb_ivar_set(self, id_key_set, Qtrue);
return key;
}
设置密码密钥。要生成密钥,您应该使用安全的随机字节字符串,或者,如果密钥要从密码派生,您应该依赖 OpenSSL::PKCS5 提供的 PBKDF2 功能。要生成基于安全随机数的密钥,可以使用 Cipher#random_key。
仅在调用 Cipher#encrypt 或 Cipher#decrypt 之后调用此方法。
另请参阅手册页 EVP_CipherInit_ex(3)。
Source
static VALUE
ossl_cipher_key_length(VALUE self)
{
EVP_CIPHER_CTX *ctx;
GetCipher(self, ctx);
return INT2NUM(EVP_CIPHER_CTX_key_length(ctx));
}
返回 Cipher 的密钥长度(以字节为单位)。
Source
static VALUE
ossl_cipher_set_key_length(VALUE self, VALUE key_length)
{
int len = NUM2INT(key_length);
EVP_CIPHER_CTX *ctx;
GetCipher(self, ctx);
if (EVP_CIPHER_CTX_set_key_length(ctx, len) != 1)
ossl_raise(eCipherError, NULL);
return key_length;
}
设置密码的密钥长度。如果密码是固定长度的密码,则尝试将密钥长度设置为除固定值以外的任何值都是错误。
在正常情况下,您不需要调用此方法(并且可能不应该)。
有关更多信息,请参阅 EVP_CIPHER_CTX_set_key_length。
Source
static VALUE
ossl_cipher_name(VALUE self)
{
EVP_CIPHER_CTX *ctx;
GetCipher(self, ctx);
return rb_str_new2(EVP_CIPHER_name(EVP_CIPHER_CTX_cipher(ctx)));
}
返回密码的短名称,该名称可能与提供的原始名称略有不同。
Source
static VALUE
ossl_cipher_set_padding(VALUE self, VALUE padding)
{
EVP_CIPHER_CTX *ctx;
int pad = NUM2INT(padding);
GetCipher(self, ctx);
if (EVP_CIPHER_CTX_set_padding(ctx, pad) != 1)
ossl_raise(eCipherError, NULL);
return padding;
}
启用或禁用填充。默认情况下,加密操作使用标准块填充进行填充,并且在解密时会检查和删除填充。如果填充参数为零,则不执行填充,加密或解密数据的总长度必须是块大小的倍数,否则将发生错误。
有关更多信息,请参阅 EVP_CIPHER_CTX_set_padding。
Source
static VALUE
ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self)
{
EVP_CIPHER_CTX *ctx;
const EVP_MD *digest;
VALUE vpass, vsalt, viter, vdigest, md_holder;
unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH], *salt = NULL;
int iter;
rb_scan_args(argc, argv, "13", &vpass, &vsalt, &viter, &vdigest);
StringValue(vpass);
if(!NIL_P(vsalt)){
StringValue(vsalt);
if(RSTRING_LEN(vsalt) != PKCS5_SALT_LEN)
ossl_raise(eCipherError, "salt must be an 8-octet string");
salt = (unsigned char *)RSTRING_PTR(vsalt);
}
iter = NIL_P(viter) ? 2048 : NUM2INT(viter);
if (iter <= 0)
rb_raise(rb_eArgError, "iterations must be a positive integer");
digest = NIL_P(vdigest) ? EVP_md5() : ossl_evp_md_fetch(vdigest, &md_holder);
GetCipher(self, ctx);
EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), digest, salt,
(unsigned char *)RSTRING_PTR(vpass), RSTRING_LENINT(vpass), iter, key, iv);
if (EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, -1) != 1)
ossl_raise(eCipherError, NULL);
OPENSSL_cleanse(key, sizeof key);
OPENSSL_cleanse(iv, sizeof iv);
rb_ivar_set(self, id_key_set, Qtrue);
return Qnil;
}
根据密码生成并设置密钥/IV。
警告:此方法已被弃用,不应使用。此方法对应于 EVP_BytesToKey(),它是旧 PKCS #5 v1.5 密钥派生函数的非标准 OpenSSL 扩展。有关从密码派生密钥的其他选项,请参阅 OpenSSL::KDF。
参数
-
如果提供了 *salt*,则必须是 8 字节字符串。
-
*iterations* 是一个整数,默认为 2048。
-
*digest* 是一个
Digest对象,默认为“MD5”。
Source
# File ext/openssl/lib/openssl/cipher.rb, line 55 def random_iv str = OpenSSL::Random.random_bytes(self.iv_len) self.iv = str end
使用 OpenSSL::Random.random_bytes 生成随机 IV 并将其设置到密码中,然后返回它。
Source
# File ext/openssl/lib/openssl/cipher.rb, line 43 def random_key str = OpenSSL::Random.random_bytes(self.key_len) self.key = str end
使用 OpenSSL::Random.random_bytes 生成随机密钥并将其设置到密码中,然后返回它。
Source
static VALUE
ossl_cipher_reset(VALUE self)
{
EVP_CIPHER_CTX *ctx;
GetCipher(self, ctx);
if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, -1) != 1)
ossl_raise(eCipherError, NULL);
return self;
}
Source
static VALUE
ossl_cipher_update(int argc, VALUE *argv, VALUE self)
{
EVP_CIPHER_CTX *ctx;
unsigned char *in;
long in_len, out_len;
VALUE data, str;
rb_scan_args(argc, argv, "11", &data, &str);
if (!RTEST(rb_attr_get(self, id_key_set)))
ossl_raise(eCipherError, "key not set");
StringValue(data);
in = (unsigned char *)RSTRING_PTR(data);
in_len = RSTRING_LEN(data);
GetCipher(self, ctx);
/*
* As of OpenSSL 3.2, there is no reliable way to determine the required
* output buffer size for arbitrary cipher modes.
* https://github.com/openssl/openssl/issues/22628
*
* in_len+block_size is usually sufficient, but AES key wrap with padding
* ciphers require in_len+15 even though they have a block size of 8 bytes.
*
* Using EVP_MAX_BLOCK_LENGTH (32) as a safe upper bound for ciphers
* currently implemented in OpenSSL, but this can change in the future.
*/
if (in_len > LONG_MAX - EVP_MAX_BLOCK_LENGTH) {
ossl_raise(rb_eRangeError,
"data too big to make output buffer: %ld bytes", in_len);
}
out_len = in_len + EVP_MAX_BLOCK_LENGTH;
if (NIL_P(str)) {
str = rb_str_new(0, out_len);
} else {
StringValue(str);
if ((long)rb_str_capacity(str) >= out_len)
rb_str_modify(str);
else
rb_str_modify_expand(str, out_len - RSTRING_LEN(str));
}
if (!ossl_cipher_update_long(ctx, (unsigned char *)RSTRING_PTR(str), &out_len, in, in_len))
ossl_raise(eCipherError, NULL);
assert(out_len <= RSTRING_LEN(str));
rb_str_set_len(str, out_len);
return str;
}
以流式方式加密数据。将连续的数据块传递给 update 方法以进行加密。返回加密后的数据块。完成后,应将 Cipher#final 的输出额外添加到结果中。
如果提供了 *buffer*,则加密/解密结果将写入其中。*buffer* 将自动调整大小。
注意:使用 AEAD 密码进行解密时,直到调用 final 才会验证输出的完整性。