类 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(Galois/Counter Mode)的示例。你有 16 字节的 key、12 字节(96 位)的 nonce 和关联数据 auth_data。确保不要重复使用 keynonce 对。重复使用 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

现在你是接收方。你知道 key,并且已经通过不可信网络收到了 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

公共类方法

OpenSSL::Cipher.ciphers → array[string...] click to toggle 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;
}
new(string) → cipher click to toggle source

字符串必须包含一个有效的密码名称,例如“aes-256-cbc”。

可以通过调用 OpenSSL::Cipher.ciphers 获取密码名称列表。

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;
}

公共实例方法

auth_data = string → string click to toggle source

设置密码的附加认证数据。使用 AEAD 密码模式(如 GCM 或 CCM)时,必须设置此字段。如果不需要使用关联数据,则必须仍然使用“”的值调用此方法。此字段的内容应该是非敏感数据,它将被添加到密文中以生成验证密文内容的身份验证标签。

必须在加密或解密之前设置 AAD。在加密模式下,必须在调用 Cipher#encrypt 并设置 Cipher#key=Cipher#iv= 之后设置它。在解密时,必须在设置密钥、iv 以及尤其是在设置身份验证标签之后设置认证数据。即仅在调用 Cipher#decryptCipher#key=Cipher#iv=Cipher#auth_tag= 之后设置它。

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;
}
auth_tag(tag_len = 16) → String click to toggle source

获取由认证加密 Cipher 模式(例如 GCM)生成的认证标签。此标签可以与密文一起存储,然后在解密密码上设置,以验证密文内容是否被更改。如果给出了可选的整数参数 tag_len,则返回的标签将为 tag_len 字节长。如果省略参数,则将使用默认长度 16 字节或之前由 auth_tag_len= 设置的长度。为了获得最大的安全性,应该选择最长的长度。

只有在调用 Cipher#final 之后才能检索标签。

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;
}
auth_tag = string → string click to toggle source

设置身份验证标签以验证密文的完整性。只有在密码支持 AE 时才能调用此方法。必须在调用 Cipher#decryptCipher#key=Cipher#iv= 之后,但在调用 Cipher#final 之前设置标签。在完成所有解密后,标签将在调用 Cipher#final 时自动验证。

对于 OCB 模式,必须事先使用 auth_tag_len= 提供标签长度。

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;
}
auth_tag_len = Integer → Integer click to toggle source

设置要生成的认证标签的长度,或者为需要它作为输入参数的 AEAD 密码提供它。请注意,并非所有 AEAD 密码都支持此方法。

在 OCB 模式下,必须在加密和解密时都提供长度,并且必须在指定 IV 之前提供长度。

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;
}
authenticated? → true | false click to toggle source

指示此 Cipher 实例是否使用经过身份验证的加密模式。

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;
}
block_size → integer 点击切换源代码

返回此 Cipher 操作的块的大小(以字节为单位)。

static VALUE
ossl_cipher_block_size(VALUE self)
{
    EVP_CIPHER_CTX *ctx;

    GetCipher(self, ctx);

    return INT2NUM(EVP_CIPHER_CTX_block_size(ctx));
}
ccm_data_len = integer → integer 点击切换源代码

设置将在 CCM 模式下处理的明文/密文消息的长度。请确保在设置 key=iv= 之后,以及在设置 auth_data= 之前调用此方法。

仅在调用 Cipher#encryptCipher#decrypt 之后调用此方法。

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;
}
decrypt → self 点击切换源代码

初始化 Cipher 以进行解密。

请确保在使用以下任何方法之前调用 Cipher#encryptCipher#decrypt

在内部调用 EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 0)。

static VALUE
ossl_cipher_decrypt(int argc, VALUE *argv, VALUE self)
{
    return ossl_cipher_init(argc, argv, self, 0);
}
encrypt → self 点击切换源代码

初始化 Cipher 以进行加密。

请确保在使用以下任何方法之前调用 Cipher#encryptCipher#decrypt

在内部调用 EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 1)。

static VALUE
ossl_cipher_encrypt(int argc, VALUE *argv, VALUE self)
{
    return ossl_cipher_init(argc, argv, self, 1);
}
final → string 点击切换源代码

返回密码对象中保存的剩余数据。对 Cipher#updateCipher#final 的进一步调用将返回垃圾数据。此调用应始终作为加密或解密操作的最后一次调用,在将整个明文或密文提供给 Cipher 实例之后进行。

如果使用了经过身份验证的密码,则如果无法成功验证标签,则会引发 CipherError。仅在设置身份验证标签并将密文的所有内容传递到密码之后调用此方法。

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;
}
initialize_copy(p1) 点击切换源代码
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;
}
iv = string → string 点击切换源代码

设置密码 IV。请注意,由于您永远不应该使用 ECB 模式,因此始终明确需要 IV,并且应在加密之前设置。IV 本身可以在公共场合安全地传输,但它应该不可预测,以防止某些类型的攻击。您可以使用 Cipher#random_iv 创建安全的随机 IV。

仅在调用 Cipher#encryptCipher#decrypt 之后调用此方法。

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_len → integer 点击切换源代码

返回此 Cipher 的 IV 的预期长度(以字节为单位)。

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);
}
iv_len = integer → integer 点击切换源代码

设置 Cipher 的 IV/nonce 长度。通常,分组密码不允许更改 IV 长度,但有些分组密码使用 IV 作为“nonce”。您可能需要这样做才能与其他应用程序进行互操作。

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;
}
key = string → string 点击切换源代码

设置密码密钥。要生成密钥,您应该使用安全的随机字节字符串,或者,如果密钥要从密码派生,则应依赖于 OpenSSL::PKCS5 提供的 PBKDF2 功能。要生成基于安全的随机数的密钥,可以使用 Cipher#random_key

仅在调用 Cipher#encryptCipher#decrypt 之后调用此方法。

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;
}
key_len → integer 点击切换源代码

返回 Cipher 的密钥长度(以字节为单位)。

static VALUE
ossl_cipher_key_length(VALUE self)
{
    EVP_CIPHER_CTX *ctx;

    GetCipher(self, ctx);

    return INT2NUM(EVP_CIPHER_CTX_key_length(ctx));
}
key_len = integer → integer 点击切换源代码

设置密码的密钥长度。如果密码是固定长度的密码,则尝试将密钥长度设置为除固定值以外的任何值都是错误的。

在正常情况下,您不需要(也不应该)调用此方法。

有关更多信息,请参阅 EVP_CIPHER_CTX_set_key_length。

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;
}
name → string 点击切换源代码

返回密码的名称,该名称可能与提供的原始名称略有不同。

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)));
}
padding = integer → integer 点击切换源代码

启用或禁用填充。默认情况下,加密操作使用标准块填充进行填充,并且在解密时会检查和删除填充。如果 pad 参数为零,则不执行任何填充,则加密或解密的总数据量必须是块大小的倍数,否则会发生错误。

有关更多信息,请参阅 EVP_CIPHER_CTX_set_padding。

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;
}
pkcs5_keyivgen(pass, salt = nil, iterations = 2048, digest = "MD5") → nil 点击切换源代码

根据密码生成并设置密钥/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 次迭代。

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;
}
random_iv → iv 点击切换源代码

使用 OpenSSL::Random.random_bytes 生成随机 IV,将其设置为密码,并返回它。

您必须在调用此方法之前调用 encryptdecrypt

# File ext/openssl/lib/openssl/cipher.rb, line 55
def random_iv
  str = OpenSSL::Random.random_bytes(self.iv_len)
  self.iv = str
end
random_key → key 点击切换源代码

使用 OpenSSL::Random.random_bytes 生成随机密钥,将其设置为密码,并返回它。

您必须在调用此方法之前调用 encryptdecrypt

# File ext/openssl/lib/openssl/cipher.rb, line 43
def random_key
  str = OpenSSL::Random.random_bytes(self.key_len)
  self.key = str
end
reset → self 点击切换源代码

完全重置 Cipher 的内部状态。通过使用此方法,同一个 Cipher 实例可以多次用于加密或解密任务。

在内部调用 EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, -1)。

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;
}
update(data [, buffer]) → string or buffer 点击切换源代码

以流式方式加密数据。将连续的数据块按顺序传递给 update 方法以对其进行加密。返回加密的数据块。完成后,应将 Cipher#final 的输出附加到结果中。

如果给出了 buffer,则加密/解密结果将写入其中。buffer 将自动调整大小。

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);
    out_len = in_len+EVP_CIPHER_CTX_block_size(ctx);
    if (out_len <= 0) {
        ossl_raise(rb_eRangeError,
                   "data too big to make output buffer: %ld bytes", in_len);
    }

    if (NIL_P(str)) {
        str = rb_str_new(0, out_len);
    } else {
        StringValue(str);
        rb_str_resize(str, out_len);
    }

    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;
}