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
认证加密和关联 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
现在您是接收者。您知道密钥,并通过不受信任的网络收到了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 decipher.auth_data = auth_data decrypted = decipher.update(encrypted) + decipher.final puts data == decrypted #=> true
公共类方法
来源
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; }
返回数组中所有可用密码的名称。
来源
static VALUE ossl_cipher_initialize(VALUE self, VALUE str) { EVP_CIPHER_CTX *ctx; const EVP_CIPHER *cipher; char *name; name = StringValueCStr(str); GetCipherInit(self, ctx); if (ctx) { ossl_raise(rb_eRuntimeError, "Cipher already initialized!"); } AllocCipher(self, ctx); if (!(cipher = EVP_get_cipherbyname(name))) { ossl_raise(rb_eRuntimeError, "unsupported cipher algorithm (%"PRIsVALUE")", str); } if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1) ossl_raise(eCipherError, NULL); return self; }
该字符串必须包含一个有效的密码名称,例如“aes-256-cbc”。
可以通过调用 OpenSSL::Cipher.ciphers
获取密码名称列表。
公共实例方法
来源
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; }
设置密码的附加身份验证数据。当使用 AEAD 密码模式(例如 GCM 或 CCM)时,必须设置此字段。如果不应使用关联数据,则必须仍然使用值“”调用此方法。此字段的内容应为非敏感数据,将添加到密文中以生成身份验证标签,该标签验证密文的内容。
AAD 必须在加密或解密之前设置。在加密模式下,它必须在调用 Cipher#encrypt
并设置 Cipher#key=
和 Cipher#iv=
之后设置。解密时,必须在设置密钥、iv,尤其是在设置身份验证标签之后设置身份验证数据。即,仅在先调用 Cipher#decrypt
、Cipher#key=
、Cipher#iv=
和 Cipher#auth_tag=
之后设置。
来源
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; }
获取由身份验证加密 Cipher
模式(例如 GCM)生成的身份验证标签。此标签可以与密文一起存储,然后在解密密码上设置以验证密文内容是否被更改。如果给定了可选的整数参数 tag_len,则返回的标签的长度将为 tag_len 个字节。如果省略该参数,则将使用默认长度 16 个字节或先前由 auth_tag_len=
设置的长度。为了最大程度的安全性,应选择最长的可能长度。
只有在调用 Cipher#final
后才能检索标签。
来源
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; }
设置身份验证标签以验证密文的完整性。这只能在密码支持 AE 时调用。必须在调用 Cipher#decrypt
、Cipher#key=
和 Cipher#iv=
之后,但在调用 Cipher#final
之前设置该标签。在执行所有解密后,在调用 Cipher#final
时会自动验证该标签。
对于 OCB 模式,必须事先使用 auth_tag_len=
提供标签长度。
来源
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; }
设置要生成的身份验证标签的长度,或为需要将其作为输入参数的 AEAD 密码提供身份验证标签的长度。请注意,并非所有 AEAD 密码都支持此方法。
在 OCB 模式下,加密和解密时都必须提供长度,并且必须在指定 IV 之前提供。
来源
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
实例是否使用身份验证加密模式。
来源
static VALUE ossl_cipher_block_size(VALUE self) { EVP_CIPHER_CTX *ctx; GetCipher(self, ctx); return INT2NUM(EVP_CIPHER_CTX_block_size(ctx)); }
返回此 Cipher
操作的块的大小(以字节为单位)。
来源
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 模式下处理的明文/密文消息的长度。请确保在设置 key=
和 iv=
之后,并在设置 auth_data=
之前调用此方法。
仅在调用 Cipher#encrypt
或 Cipher#decrypt
之后调用此方法。
来源
static VALUE ossl_cipher_decrypt(int argc, VALUE *argv, VALUE self) { return ossl_cipher_init(argc, argv, self, 0); }
初始化 Cipher
以进行解密。
请确保在调用以下任何方法之前先调用 Cipher#encrypt
或 Cipher#decrypt
。
内部调用 EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 0)。
来源
static VALUE ossl_cipher_encrypt(int argc, VALUE *argv, VALUE self) { return ossl_cipher_init(argc, argv, self, 1); }
初始化 Cipher
以进行加密。
请确保在调用以下任何方法之前先调用 Cipher#encrypt
或 Cipher#decrypt
。
内部调用 EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 1)。
来源
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)) ossl_raise(eCipherError, NULL); assert(out_len <= RSTRING_LEN(str)); rb_str_set_len(str, out_len); return str; }
返回密码对象中保留的剩余数据。进一步调用 Cipher#update
或 Cipher#final
将返回垃圾数据。此调用应始终作为加密或解密操作的最后一次调用,在将整个明文或密文馈送到 Cipher
实例之后。
如果使用了身份验证密码,则如果无法成功验证标签,则会引发 CipherError
。仅在设置身份验证标签并将密文的全部内容传递到密码中后才调用此方法。
来源
static VALUE ossl_cipher_copy(VALUE self, VALUE other) { EVP_CIPHER_CTX *ctx1, *ctx2; rb_check_frozen(self); if (self == other) return self; GetCipherInit(self, ctx1); if (!ctx1) { AllocCipher(self, ctx1); } GetCipher(other, ctx2); if (EVP_CIPHER_CTX_copy(ctx1, ctx2) != 1) ossl_raise(eCipherError, NULL); return self; }
来源
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; }
设置密码 IV。请注意,由于您永远不应使用 ECB 模式,因此始终需要显式 IV,并且应在加密之前设置。IV 本身可以安全地公开传输,但应不可预测,以防止某些类型的攻击。您可以使用 Cipher#random_iv
创建安全的随机 IV。
仅在调用 Cipher#encrypt
或 Cipher#decrypt
之后调用此方法。
来源
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 的预期长度(以字节为单位)。
来源
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; }
设置 Cipher
的 IV/nonce 长度。通常,分组密码不允许更改 IV 长度,但有些分组密码使用 IV 作为“nonce”。您可能需要此功能以与其他应用程序互操作。
来源
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
之后调用此方法。
来源
static VALUE ossl_cipher_key_length(VALUE self) { EVP_CIPHER_CTX *ctx; GetCipher(self, ctx); return INT2NUM(EVP_CIPHER_CTX_key_length(ctx)); }
返回 Cipher
的密钥长度(以字节为单位)。
来源
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。
来源
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))); }
返回密码的短名称,该名称可能与提供的原始名称略有不同。
来源
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; }
启用或禁用填充。默认情况下,加密操作使用标准块填充进行填充,并在解密时检查并删除填充。如果 pad 参数为零,则不执行填充,加密或解密的数据总量必须是块大小的倍数,否则会发生错误。
有关更多信息,请参阅 EVP_CIPHER_CTX_set_padding。
来源
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; 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_get_digestbyname(vdigest); 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。
警告:仅当使用 RC2、RC4-40 或 DES 以及 MD5 或 SHA1 时,此方法才符合 PKCS5
v1.5 标准。使用其他任何内容(如 AES)将使用 OpenSSL
特定方法生成密钥/iv。此方法已弃用,不应再使用。请改为使用 OpenSSL::PKCS5
中的 PKCS5
v2 密钥生成方法。
参数¶ ↑
-
如果提供 salt,则它必须是 8 字节字符串。
-
iterations 是一个整数,默认值为 2048。
-
digest 是一个
Digest
对象,默认为“MD5”
建议至少 1000 次迭代。
来源
# 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,并将其设置为密码,然后返回它。
来源
# 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
生成一个随机密钥,并将其设置为密码,然后返回它。
来源
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; }
完全重置 Cipher
的内部状态。通过使用此方法,同一 Cipher
实例可以多次用于加密或解密任务。
内部调用 EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, -1)。
来源
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 将自动调整大小。