Skip to content

Commit 36cf2f0

Browse files
committed
kdf: introduce OpenSSL::KDF module
Introduce a new OpenSSL::KDF module as a namespace for to-be-added KDFs. This makes it easier to add new KDFs in future. We already have a stand-alone KDF, OpenSSL::PKCS5.pbkdf2_hmac. This is migrated to the new namespace. The backwards compatibility is retained by the method defined in the newly added lib/openssl/pkcs5.rb.
1 parent 7b0ae45 commit 36cf2f0

File tree

9 files changed

+189
-190
lines changed

9 files changed

+189
-190
lines changed

ext/openssl/ossl.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1169,14 +1169,14 @@ Init_openssl(void)
11691169
Init_ossl_ns_spki();
11701170
Init_ossl_pkcs12();
11711171
Init_ossl_pkcs7();
1172-
Init_ossl_pkcs5();
11731172
Init_ossl_pkey();
11741173
Init_ossl_rand();
11751174
Init_ossl_ssl();
11761175
Init_ossl_x509();
11771176
Init_ossl_ocsp();
11781177
Init_ossl_engine();
11791178
Init_ossl_asn1();
1179+
Init_ossl_kdf();
11801180

11811181
#if defined(OSSL_DEBUG)
11821182
/*

ext/openssl/ossl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,13 @@ void ossl_debug(const char *, ...);
173173
#include "ossl_ocsp.h"
174174
#include "ossl_pkcs12.h"
175175
#include "ossl_pkcs7.h"
176-
#include "ossl_pkcs5.h"
177176
#include "ossl_pkey.h"
178177
#include "ossl_rand.h"
179178
#include "ossl_ssl.h"
180179
#include "ossl_version.h"
181180
#include "ossl_x509.h"
182181
#include "ossl_engine.h"
182+
#include "ossl_kdf.h"
183183

184184
void Init_openssl(void);
185185

ext/openssl/ossl_kdf.c

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Ruby/OpenSSL Project
3+
* Copyright (C) 2007, 2017 Ruby/OpenSSL Project Authors
4+
*/
5+
#include "ossl.h"
6+
7+
static VALUE mKDF, eKDF;
8+
9+
/*
10+
* call-seq:
11+
* KDF.pbkdf2_hmac(pass, salt:, iterations:, length:, hash:) -> aString
12+
*
13+
* PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in combination
14+
* with HMAC. Takes _pass_, _salt_ and _iterations_, and then derives a key
15+
* of _length_ bytes.
16+
*
17+
* For more information about PBKDF2, see RFC 2898 Section 5.2
18+
* (https://tools.ietf.org/html/rfc2898#section-5.2).
19+
*
20+
* === Parameters
21+
* pass :: The passphrase.
22+
* salt :: The salt. Salts prevent attacks based on dictionaries of common
23+
* passwords and attacks based on rainbow tables. It is a public
24+
* value that can be safely stored along with the password (e.g.
25+
* if the derived value is used for password storage).
26+
* iterations :: The iteration count. This provides the ability to tune the
27+
* algorithm. It is better to use the highest count possible for
28+
* the maximum resistance to brute-force attacks.
29+
* length :: The desired length of the derived key in octets.
30+
* hash :: The hash algorithm used with HMAC for the PRF. May be a String
31+
* representing the algorithm name, or an instance of
32+
* OpenSSL::Digest.
33+
*/
34+
static VALUE
35+
kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self)
36+
{
37+
VALUE pass, salt, opts, kwargs[4], str;
38+
static ID kwargs_ids[4];
39+
int iters, len;
40+
const EVP_MD *md;
41+
42+
if (!kwargs_ids[0]) {
43+
kwargs_ids[0] = rb_intern_const("salt");
44+
kwargs_ids[1] = rb_intern_const("iterations");
45+
kwargs_ids[2] = rb_intern_const("length");
46+
kwargs_ids[3] = rb_intern_const("hash");
47+
}
48+
rb_scan_args(argc, argv, "1:", &pass, &opts);
49+
rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs);
50+
51+
StringValue(pass);
52+
salt = StringValue(kwargs[0]);
53+
iters = NUM2INT(kwargs[1]);
54+
len = NUM2INT(kwargs[2]);
55+
md = GetDigestPtr(kwargs[3]);
56+
57+
str = rb_str_new(0, len);
58+
if (!PKCS5_PBKDF2_HMAC(RSTRING_PTR(pass), RSTRING_LENINT(pass),
59+
(unsigned char *)RSTRING_PTR(salt),
60+
RSTRING_LENINT(salt), iters, md, len,
61+
(unsigned char *)RSTRING_PTR(str)))
62+
ossl_raise(eKDF, "PKCS5_PBKDF2_HMAC");
63+
64+
return str;
65+
}
66+
67+
void
68+
Init_ossl_kdf(void)
69+
{
70+
#if 0
71+
mOSSL = rb_define_module("OpenSSL");
72+
eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
73+
#endif
74+
75+
/*
76+
* Document-module: OpenSSL::KDF
77+
*
78+
* Provides functionality of various KDFs (key derivation function).
79+
*
80+
* KDF is typically used for securely deriving arbitrary length symmetric
81+
* keys to be used with an OpenSSL::Cipher from passwords. Another use case
82+
* is for storing passwords: Due to the ability to tweak the effort of
83+
* computation by increasing the iteration count, computation can be slowed
84+
* down artificially in order to render possible attacks infeasible.
85+
*
86+
* Currently, OpenSSL::KDF provides implementations for the following KDF:
87+
*
88+
* * PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in
89+
* combination with HMAC
90+
*
91+
* == Examples
92+
* === Generating a 128 bit key for a Cipher (e.g. AES)
93+
* pass = "secret"
94+
* salt = OpenSSL::Random.random_bytes(16)
95+
* iter = 20_000
96+
* key_len = 16
97+
* key = OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter,
98+
* length: key_len, hash: "sha1")
99+
*
100+
* === Storing Passwords
101+
* pass = "secret"
102+
* # store this with the generated value
103+
* salt = OpenSSL::Random.random_bytes(16)
104+
* iter = 20_000
105+
* hash = OpenSSL::Digest::SHA256.new
106+
* len = hash.digest_length
107+
* # the final value to be stored
108+
* value = OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter,
109+
* length: len, hash: hash)
110+
*
111+
* == Important Note on Checking Passwords
112+
* When comparing passwords provided by the user with previously stored
113+
* values, a common mistake made is comparing the two values using "==".
114+
* Typically, "==" short-circuits on evaluation, and is therefore
115+
* vulnerable to timing attacks. The proper way is to use a method that
116+
* always takes the same amount of time when comparing two values, thus
117+
* not leaking any information to potential attackers. To compare two
118+
* values, the following could be used:
119+
*
120+
* def eql_time_cmp(a, b)
121+
* unless a.length == b.length
122+
* return false
123+
* end
124+
* cmp = b.bytes
125+
* result = 0
126+
* a.bytes.each_with_index {|c,i|
127+
* result |= c ^ cmp[i]
128+
* }
129+
* result == 0
130+
* end
131+
*
132+
* Please note that the premature return in case of differing lengths
133+
* typically does not leak valuable information - when using PBKDF2, the
134+
* length of the values to be compared is of fixed size.
135+
*/
136+
mKDF = rb_define_module_under(mOSSL, "KDF");
137+
/*
138+
* Generic exception class raised if an error occurs in OpenSSL::KDF module.
139+
*/
140+
eKDF = rb_define_class_under(mKDF, "KDFError", eOSSLError);
141+
142+
rb_define_module_function(mKDF, "pbkdf2_hmac", kdf_pbkdf2_hmac, -1);
143+
}

ext/openssl/ossl_kdf.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#if !defined(OSSL_KDF_H)
2+
#define OSSL_KDF_H
3+
4+
void Init_ossl_kdf(void);
5+
6+
#endif

ext/openssl/ossl_pkcs5.c

Lines changed: 0 additions & 172 deletions
This file was deleted.

ext/openssl/ossl_pkcs5.h

Lines changed: 0 additions & 6 deletions
This file was deleted.

lib/openssl.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@
1919
require 'openssl/digest'
2020
require 'openssl/x509'
2121
require 'openssl/ssl'
22+
require 'openssl/pkcs5'

lib/openssl/pkcs5.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# frozen_string_literal: false
2+
#--
3+
# Ruby/OpenSSL Project
4+
# Copyright (C) 2017 Ruby/OpenSSL Project Authors
5+
#++
6+
7+
module OpenSSL
8+
module PKCS5
9+
module_function
10+
11+
# OpenSSL::PKCS5.pbkdf2_hmac has been renamed to OpenSSL::KDF.pbkdf2_hmac.
12+
# This method is provided for backwards compatibility.
13+
def pbkdf2_hmac(pass, salt, iter, keylen, digest)
14+
OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter,
15+
length: keylen, hash: digest)
16+
end
17+
18+
def pbkdf2_hmac_sha1(pass, salt, iter, keylen)
19+
pbkdf2_hmac(pass, salt, iter, keylen, "sha1")
20+
end
21+
end
22+
end

0 commit comments

Comments
 (0)