Spec-Zone.ru › Ruby 2.7

модуль 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::SHA256.new
len = hash.digest_length
# the final value to be stored
value = OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter,
                                 length: len, hash: hash)

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

При сравнении паролей, введённых пользователем, с ранее сохранёнными значениями распространённой ошибкой является сравнение двух значений с помощью оператора “==”. Обычно “==” выполняется по принципу короткого замыкания и поэтому уязвим к атакам с временными задержками. Правильный подход заключается в использовании метода, который всегда тратит одинаковое время на сравнение двух значений, тем самым не раскрывая никакой информации потенциальным злоумышленникам. Для сравнения двух значений можно использовать следующее:

def eql_time_cmp(a, b)
  unless a.length == b.length
    return false
  end
  cmp = b.bytes
  result = 0
  a.bytes.each_with_index {|c,i|
    result |= c ^ cmp[i]
  }
  result == 0
end

Обратите внимание, что преждевременное возвращение при разнице в длинах обычно не раскрывает ценной информации — при использовании PBKDF2 длина сравниваемых значений имеет фиксированный размер.

Публичные методы класса

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

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

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 см. в RFC 2898, раздел 5.2 (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–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

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