Spec-Zone.ru › Ruby 3.1

модуль OpenSSL::KDF

Предоставляет функциональность различных KDF (функций вывода ключей).

KDF обычно используется для безопасного вывода симметричных ключей произвольной длины, которые будут использоваться с OpenSSL::Cipher из паролей. Другой случай использования — хранение паролей: благодаря возможности настраивать сложность вычислений, увеличивая количество итераций, вычисления можно искусственно замедлить, чтобы сделать возможные атаки невозможными.

В настоящее время OpenSSL::KDF предоставляет реализации следующих KDF:

  • PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) в сочетании с HMAC

  • scrypt

  • HKDF

Примеры

Генерация 128-битного ключа для Cipher (например, AES)

pass = "secret"
salt = OpenSSL::Random.random_bytes(16)
iter = 20_000
key_len = 16
key = OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter,
                               length: key_len, hash: "sha1")

Хранение паролей

pass = "secret"
# store this with the generated value
salt = OpenSSL::Random.random_bytes(16)
iter = 20_000
hash = OpenSSL::Digest.new('SHA256')
len = hash.digest_length
# the final value to be stored
value = OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter,
                                 length: len, hash: hash)

Важное замечание о проверке паролей

При сравнении паролей, введенных пользователем, с ранее сохраненными значениями, распространённой ошибкой является сравнение двух значений с помощью «==». Как правило, «==» выполняется по короткому замыканию и поэтому уязвим для атак на основе времени. Правильный способ — использовать метод, который всегда занимает одинаковое количество времени при сравнении двух значений, тем самым не раскрывая никакой информации потенциальным злоумышленникам. Для этого используйте OpenSSL.fixed_length_secure_compare.

Методы публичного класса

hkdf(ikm, salt:, info:, length:, hash:) → String Показать исходный код
static VALUE
kdf_hkdf(int argc, VALUE *argv, VALUE self)
{
    VALUE ikm, salt, info, opts, kwargs[4], str;
    static ID kwargs_ids[4];
    int saltlen, ikmlen, infolen;
    size_t len;
    const EVP_MD *md;
    EVP_PKEY_CTX *pctx;

    if (!kwargs_ids[0]) {
        kwargs_ids[0] = rb_intern_const("salt");
        kwargs_ids[1] = rb_intern_const("info");
        kwargs_ids[2] = rb_intern_const("length");
        kwargs_ids[3] = rb_intern_const("hash");
    }
    rb_scan_args(argc, argv, "1:", &ikm, &opts);
    rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs);

    StringValue(ikm);
    ikmlen = RSTRING_LENINT(ikm);
    salt = StringValue(kwargs[0]);
    saltlen = RSTRING_LENINT(salt);
    info = StringValue(kwargs[1]);
    infolen = RSTRING_LENINT(info);
    len = (size_t)NUM2LONG(kwargs[2]);
    if (len > LONG_MAX)
        rb_raise(rb_eArgError, "length must be non-negative");
    md = ossl_evp_get_digestbyname(kwargs[3]);

    str = rb_str_new(NULL, (long)len);
    pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
    if (!pctx)
        ossl_raise(eKDF, "EVP_PKEY_CTX_new_id");
    if (EVP_PKEY_derive_init(pctx) <= 0) {
        EVP_PKEY_CTX_free(pctx);
        ossl_raise(eKDF, "EVP_PKEY_derive_init");
    }
    if (EVP_PKEY_CTX_set_hkdf_md(pctx, md) <= 0) {
        EVP_PKEY_CTX_free(pctx);
        ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_md");
    }
    if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, (unsigned char *)RSTRING_PTR(salt),
                                    saltlen) <= 0) {
        EVP_PKEY_CTX_free(pctx);
        ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_salt");
    }
    if (EVP_PKEY_CTX_set1_hkdf_key(pctx, (unsigned char *)RSTRING_PTR(ikm),
                                   ikmlen) <= 0) {
        EVP_PKEY_CTX_free(pctx);
        ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_key");
    }
    if (EVP_PKEY_CTX_add1_hkdf_info(pctx, (unsigned char *)RSTRING_PTR(info),
                                    infolen) <= 0) {
        EVP_PKEY_CTX_free(pctx);
        ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_info");
    }
    if (EVP_PKEY_derive(pctx, (unsigned char *)RSTRING_PTR(str), &len) <= 0) {
        EVP_PKEY_CTX_free(pctx);
        ossl_raise(eKDF, "EVP_PKEY_derive");
    }
    rb_str_set_len(str, (long)len);
    EVP_PKEY_CTX_free(pctx);

    return str;
}

Функция вывода ключа HKDF (HMAC-based Extract-and-Expand Key Derivation Function), как указано в RFC 5869.

Новая функция в OpenSSL 1.1.0.

Параметры

ikm

Входной материал ключа.

salt

Соль.

info

Контекстная и прикладная информация.

length

Длина выходного значения в октетах. Должна быть <= 255 * HashLen, где HashLen — длина выходного значения функции хэширования в октетах.

hash

Функция хэширования.

Пример

# The values from https://datatracker.ietf.org/doc/html/rfc5869#appendix-A.1
ikm = ["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*")
salt = ["000102030405060708090a0b0c"].pack("H*")
info = ["f0f1f2f3f4f5f6f7f8f9"].pack("H*")
p OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: 42, hash: "SHA256").unpack1("H*")
# => "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"
pbkdf2_hmac(pass, salt:, iterations:, length:, hash:) → aString Показать исходный код
static VALUE
kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self)
{
    VALUE pass, salt, opts, kwargs[4], str;
    static ID kwargs_ids[4];
    int iters, len;
    const EVP_MD *md;

    if (!kwargs_ids[0]) {
        kwargs_ids[0] = rb_intern_const("salt");
        kwargs_ids[1] = rb_intern_const("iterations");
        kwargs_ids[2] = rb_intern_const("length");
        kwargs_ids[3] = rb_intern_const("hash");
    }
    rb_scan_args(argc, argv, "1:", &pass, &opts);
    rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs);

    StringValue(pass);
    salt = StringValue(kwargs[0]);
    iters = NUM2INT(kwargs[1]);
    len = NUM2INT(kwargs[2]);
    md = ossl_evp_get_digestbyname(kwargs[3]);

    str = rb_str_new(0, len);
    if (!PKCS5_PBKDF2_HMAC(RSTRING_PTR(pass), RSTRING_LENINT(pass),
                           (unsigned char *)RSTRING_PTR(salt),
                           RSTRING_LENINT(salt), iters, md, len,
                           (unsigned char *)RSTRING_PTR(str)))
        ossl_raise(eKDF, "PKCS5_PBKDF2_HMAC");

    return str;
}

PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) в сочетании с HMAC. Принимает pass, salt и iterations, а затем выводит ключ длиной length байтов.

Для получения дополнительной информации о PBKDF2 см. раздел 5.2 RFC 2898 (tools.ietf.org/html/rfc2898#section-5.2).

Параметры

pass

Пароль.

salt

Соль. Соль предотвращает атаки, основанные на словарях общих паролей и атаках, основанных на радужных таблицах. Это общедоступное значение, которое можно безопасно хранить вместе с паролем (например, если выведенное значение используется для хранения пароля).

iterations

Количество итераций. Это предоставляет возможность настройки алгоритма. Лучше использовать наибольшее возможное значение для максимальной защиты от атак методом грубой силы.

length

Желаемая длина выведенного ключа в октетах.

hash

Алгоритм хэширования, используемый с HMAC для PRF. Может быть String, представляющий имя алгоритма, или экземпляр OpenSSL::Digest.

scrypt(pass, salt:, N:, r:, p:, length:) → aString Показать исходный код
static VALUE
kdf_scrypt(int argc, VALUE *argv, VALUE self)
{
    VALUE pass, salt, opts, kwargs[5], str;
    static ID kwargs_ids[5];
    size_t len;
    uint64_t N, r, p, maxmem;

    if (!kwargs_ids[0]) {
        kwargs_ids[0] = rb_intern_const("salt");
        kwargs_ids[1] = rb_intern_const("N");
        kwargs_ids[2] = rb_intern_const("r");
        kwargs_ids[3] = rb_intern_const("p");
        kwargs_ids[4] = rb_intern_const("length");
    }
    rb_scan_args(argc, argv, "1:", &pass, &opts);
    rb_get_kwargs(opts, kwargs_ids, 5, 0, kwargs);

    StringValue(pass);
    salt = StringValue(kwargs[0]);
    N = NUM2UINT64T(kwargs[1]);
    r = NUM2UINT64T(kwargs[2]);
    p = NUM2UINT64T(kwargs[3]);
    len = NUM2LONG(kwargs[4]);
    /*
     * OpenSSL uses 32MB by default (if zero is specified), which is too small.
     * Let's not limit memory consumption but just let malloc() fail inside
     * OpenSSL. The amount is controllable by other parameters.
     */
    maxmem = SIZE_MAX;

    str = rb_str_new(0, len);
    if (!EVP_PBE_scrypt(RSTRING_PTR(pass), RSTRING_LEN(pass),
                        (unsigned char *)RSTRING_PTR(salt), RSTRING_LEN(salt),
                        N, r, p, maxmem, (unsigned char *)RSTRING_PTR(str), len))
        ossl_raise(eKDF, "EVP_PBE_scrypt");

    return str;
}

Вычисляет ключ из pass с заданными параметрами с помощью функции вывода ключа scrypt. Результат может быть использован для хранения паролей.

scrypt разработан как ресурсоёмкая функция, более устойчивая к атакам методом грубой силы с использованием специализированного оборудования, чем альтернативные KDF, такие как PBKDF2 или bcrypt.

Ключевые аргументы N, r и p могут быть использованы для настройки scrypt. RFC 7914 (опубликован в 2016-08, tools.ietf.org/html/rfc7914#section-2) указывает, что использование значений r=8 и p=1, как правило, даёт хорошие результаты.

См. RFC 7914 (tools.ietf.org/html/rfc7914) для получения дополнительной информации.

Параметры

pass

Пароль.

salt

Соль.

N

Параметр затрат ЦП/памяти. Должен быть степенью двойки.

r

Параметр размера блока.

p

Параметр распараллеливания.

length

Длина выведенного ключа в октетах.

Пример

pass = "password"
salt = SecureRandom.random_bytes(16)
dk = OpenSSL::KDF.scrypt(pass, salt: salt, N: 2**14, r: 8, p: 1, length: 32)
p dk #=> "\xDA\xE4\xE2...\x7F\xA1\x01T"

Ruby Core © 1993–2022 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API