Skip to content

Commit 4e53940

Browse files
authored
Merge pull request #173 from rhenium/ky/kdf-hkdf
kdf: add HKDF support
2 parents 1f7fdd6 + d8e9c54 commit 4e53940

File tree

2 files changed

+140
-0
lines changed

2 files changed

+140
-0
lines changed

ext/openssl/ossl_kdf.c

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
* Copyright (C) 2007, 2017 Ruby/OpenSSL Project Authors
44
*/
55
#include "ossl.h"
6+
#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
7+
# include <openssl/kdf.h>
8+
#endif
69

710
static VALUE mKDF, eKDF;
811

@@ -138,6 +141,97 @@ kdf_scrypt(int argc, VALUE *argv, VALUE self)
138141
}
139142
#endif
140143

144+
#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
145+
/*
146+
* call-seq:
147+
* KDF.hkdf(ikm, salt:, info:, length:, hash:) -> String
148+
*
149+
* HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as specified in
150+
* {RFC 5869}[https://tools.ietf.org/html/rfc5869].
151+
*
152+
* New in OpenSSL 1.1.0.
153+
*
154+
* === Parameters
155+
* _ikm_::
156+
* The input keying material.
157+
* _salt_::
158+
* The salt.
159+
* _info_::
160+
* The context and application specific information.
161+
* _length_::
162+
* The output length in octets. Must be <= <tt>255 * HashLen</tt>, where
163+
* HashLen is the length of the hash function output in octets.
164+
* _hash_::
165+
* The hash function.
166+
*/
167+
static VALUE
168+
kdf_hkdf(int argc, VALUE *argv, VALUE self)
169+
{
170+
VALUE ikm, salt, info, opts, kwargs[4], str;
171+
static ID kwargs_ids[4];
172+
int saltlen, ikmlen, infolen;
173+
size_t len;
174+
const EVP_MD *md;
175+
EVP_PKEY_CTX *pctx;
176+
177+
if (!kwargs_ids[0]) {
178+
kwargs_ids[0] = rb_intern_const("salt");
179+
kwargs_ids[1] = rb_intern_const("info");
180+
kwargs_ids[2] = rb_intern_const("length");
181+
kwargs_ids[3] = rb_intern_const("hash");
182+
}
183+
rb_scan_args(argc, argv, "1:", &ikm, &opts);
184+
rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs);
185+
186+
StringValue(ikm);
187+
ikmlen = RSTRING_LENINT(ikm);
188+
salt = StringValue(kwargs[0]);
189+
saltlen = RSTRING_LENINT(salt);
190+
info = StringValue(kwargs[1]);
191+
infolen = RSTRING_LENINT(info);
192+
len = (size_t)NUM2LONG(kwargs[2]);
193+
if (len > LONG_MAX)
194+
rb_raise(rb_eArgError, "length must be non-negative");
195+
md = ossl_evp_get_digestbyname(kwargs[3]);
196+
197+
str = rb_str_new(NULL, (long)len);
198+
pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
199+
if (!pctx)
200+
ossl_raise(eKDF, "EVP_PKEY_CTX_new_id");
201+
if (EVP_PKEY_derive_init(pctx) <= 0) {
202+
EVP_PKEY_CTX_free(pctx);
203+
ossl_raise(eKDF, "EVP_PKEY_derive_init");
204+
}
205+
if (EVP_PKEY_CTX_set_hkdf_md(pctx, md) <= 0) {
206+
EVP_PKEY_CTX_free(pctx);
207+
ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_md");
208+
}
209+
if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, (unsigned char *)RSTRING_PTR(salt),
210+
saltlen) <= 0) {
211+
EVP_PKEY_CTX_free(pctx);
212+
ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_salt");
213+
}
214+
if (EVP_PKEY_CTX_set1_hkdf_key(pctx, (unsigned char *)RSTRING_PTR(ikm),
215+
ikmlen) <= 0) {
216+
EVP_PKEY_CTX_free(pctx);
217+
ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_key");
218+
}
219+
if (EVP_PKEY_CTX_add1_hkdf_info(pctx, (unsigned char *)RSTRING_PTR(info),
220+
infolen) <= 0) {
221+
EVP_PKEY_CTX_free(pctx);
222+
ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_info");
223+
}
224+
if (EVP_PKEY_derive(pctx, (unsigned char *)RSTRING_PTR(str), &len) <= 0) {
225+
EVP_PKEY_CTX_free(pctx);
226+
ossl_raise(eKDF, "EVP_PKEY_derive");
227+
}
228+
rb_str_set_len(str, (long)len);
229+
EVP_PKEY_CTX_free(pctx);
230+
231+
return str;
232+
}
233+
#endif
234+
141235
void
142236
Init_ossl_kdf(void)
143237
{
@@ -162,6 +256,7 @@ Init_ossl_kdf(void)
162256
* * PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in
163257
* combination with HMAC
164258
* * scrypt
259+
* * HKDF
165260
*
166261
* == Examples
167262
* === Generating a 128 bit key for a Cipher (e.g. AES)
@@ -218,4 +313,7 @@ Init_ossl_kdf(void)
218313
#if defined(HAVE_EVP_PBE_SCRYPT)
219314
rb_define_module_function(mKDF, "scrypt", kdf_scrypt, -1);
220315
#endif
316+
#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
317+
rb_define_module_function(mKDF, "hkdf", kdf_hkdf, -1);
318+
#endif
221319
}

test/test_kdf.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,48 @@ def test_scrypt_rfc7914_second
131131
assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen))
132132
end
133133

134+
def test_hkdf_rfc5869_test_case_1
135+
pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0
136+
hash = "sha256"
137+
ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
138+
salt = B("000102030405060708090a0b0c")
139+
info = B("f0f1f2f3f4f5f6f7f8f9")
140+
l = 42
141+
142+
okm = B("3cb25f25faacd57a90434f64d0362f2a" \
143+
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf" \
144+
"34007208d5b887185865")
145+
assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash))
146+
end
147+
148+
def test_hkdf_rfc5869_test_case_3
149+
pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0
150+
hash = "sha256"
151+
ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
152+
salt = B("")
153+
info = B("")
154+
l = 42
155+
156+
okm = B("8da4e775a563c18f715f802a063c5a31" \
157+
"b8a11f5c5ee1879ec3454e5f3c738d2d" \
158+
"9d201395faa4b61a96c8")
159+
assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash))
160+
end
161+
162+
def test_hkdf_rfc5869_test_case_4
163+
pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0
164+
hash = "sha1"
165+
ikm = B("0b0b0b0b0b0b0b0b0b0b0b")
166+
salt = B("000102030405060708090a0b0c")
167+
info = B("f0f1f2f3f4f5f6f7f8f9")
168+
l = 42
169+
170+
okm = B("085a01ea1b10f36933068b56efa5ad81" \
171+
"a4f14b822f5b091568a9cdd4f155fda2" \
172+
"c22e422478d305f3f896")
173+
assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash))
174+
end
175+
134176
private
135177

136178
def B(ary)

0 commit comments

Comments
 (0)