Module: OpenSSL::KDF
Relationships & Source Files | |
Namespace Children | |
Exceptions:
| |
Defined in: | ext/openssl/ossl_kdf.c |
Overview
Provides functionality of various KDFs (key derivation function).
KDF
is typically used for securely deriving arbitrary length symmetric keys to be used with an Cipher
from passwords. Another use case is for storing passwords: Due to the ability to tweak the effort of computation by increasing the iteration count, computation can be slowed down artificially in order to render possible attacks infeasible.
Currently, KDF
provides implementations for the following KDF
:
-
PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in combination with HMAC
-
scrypt
-
HKDF
Examples
Generating a 128 bit key for a Cipher (e.g. 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")
Storing Passwords
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)
Important Note on Checking Passwords
When comparing passwords provided by the user with previously stored values, a common mistake made is comparing the two values using “==”. Typically, “==” short-circuits on evaluation, and is therefore vulnerable to timing attacks. The proper way is to use a method that always takes the same amount of time when comparing two values, thus not leaking any information to potential attackers. To compare two values, the following could be used:
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
Please note that the premature return in case of differing lengths typically does not leak valuable information - when using PBKDF2, the length of the values to be compared is of fixed size.
Class Method Summary
-
.hkdf(ikm, salt:, info:, length:, hash:) ⇒ String
mod_func
HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as specified in RFC 5869.
-
.pbkdf2_hmac(pass, salt:, iterations:, length:, hash:) ⇒ String
mod_func
PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in combination with
HMAC
. -
.scrypt(pass, salt:, N:, r:, p:, length:) ⇒ String
mod_func
Derives a key from pass using given parameters with the scrypt password-based key derivation function.
Class Method Details
.hkdf(ikm, salt:, info:, length:, hash:) ⇒ String
(mod_func)
HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as specified in RFC 5869.
New in ::OpenSSL
1.1.0.
Parameters
- ikm
-
The input keying material.
- salt
-
The salt.
- info
-
The context and application specific information.
- length
-
The output length in octets. Must be <=
255 * HashLen
, where HashLen is the length of the hash function output in octets. - hash
-
The hash function.
# File 'ext/openssl/ossl_kdf.c', line 167
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; }
.pbkdf2_hmac(pass, salt:, iterations:, length:, hash:) ⇒ String
(mod_func)
PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in combination with HMAC
. Takes pass, salt and iterations, and then derives a key of length bytes.
For more information about PBKDF2, see RFC 2898 Section 5.2 (tools.ietf.org/html/rfc2898#section-5.2).
Parameters
- pass
-
The passphrase.
- salt
-
The salt. Salts prevent attacks based on dictionaries of common passwords and attacks based on rainbow tables. It is a public value that can be safely stored along with the password (e.g. if the derived value is used for password storage).
- iterations
-
The iteration count. This provides the ability to tune the algorithm. It is better to use the highest count possible for the maximum resistance to brute-force attacks.
- length
-
The desired length of the derived key in octets.
- hash
-
The hash algorithm used with
HMAC
for the PRF. May be a String representing the algorithm name, or an instance of OpenSSL::Digest.
# File 'ext/openssl/ossl_kdf.c', line 37
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; }
.scrypt(pass, salt:, N:, r:, p:, length:) ⇒ String
(mod_func)
Derives a key from pass using given parameters with the scrypt password-based key derivation function. The result can be used for password storage.
scrypt is designed to be memory-hard and more secure against brute-force attacks using custom hardwares than alternative KDFs such as PBKDF2 or bcrypt.
The keyword arguments N, r and p can be used to tune scrypt. RFC 7914 (published on 2016-08, tools.ietf.org/html/rfc7914#section-2) states that using values r=8 and p=1 appears to yield good results.
See RFC 7914 (tools.ietf.org/html/rfc7914) for more information.
Parameters
- pass
-
Passphrase.
- salt
-
Salt.
- N
-
CPU/memory cost parameter. This must be a power of 2.
- r
-
Block size parameter.
- p
-
Parallelization parameter.
- length
-
Length in octets of the derived key.
Example
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"
# File 'ext/openssl/ossl_kdf.c', line 103
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; }