Module: OpenSSL::KDF

Defined in:
ext/openssl/ossl_kdf.c,
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 OpenSSL::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, OpenSSL::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.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)

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 do this, use OpenSSL.fixed_length_secure_compare.

Defined Under Namespace

Classes: KDFError

Class Method Summary collapse

Class Method Details

.hkdf(ikm, salt: , info: , length: , hash: ) ⇒ String

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.

Example

# 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"

Returns:

  • (String)


175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'ext/openssl/ossl_kdf.c', line 175

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: ) ⇒ aString

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.

Returns:

  • (aString)


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# 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: ) ⇒ aString

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"

Returns:

  • (aString)


103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# 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;
}