Skip to content

Commit 380a586

Browse files
committed
Merge branch 'topic/kdf-module'
scrypt support is added. * topic/kdf-module: kdf: add scrypt ossl.h: add NUM2UINT64T() macro kdf: introduce OpenSSL::KDF module
2 parents b7ae376 + 850fb5e commit 380a586

File tree

11 files changed

+405
-278
lines changed

11 files changed

+405
-278
lines changed

ext/openssl/extconf.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
have_func("SSL_CTX_get_security_level")
120120
have_func("X509_get0_notBefore")
121121
have_func("SSL_SESSION_get_protocol_version")
122+
have_func("EVP_PBE_scrypt")
122123

123124
Logging::message "=== Checking done. ===\n"
124125

ext/openssl/ossl.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1170,14 +1170,14 @@ Init_openssl(void)
11701170
Init_ossl_ns_spki();
11711171
Init_ossl_pkcs12();
11721172
Init_ossl_pkcs7();
1173-
Init_ossl_pkcs5();
11741173
Init_ossl_pkey();
11751174
Init_ossl_rand();
11761175
Init_ossl_ssl();
11771176
Init_ossl_x509();
11781177
Init_ossl_ocsp();
11791178
Init_ossl_engine();
11801179
Init_ossl_asn1();
1180+
Init_ossl_kdf();
11811181

11821182
#if defined(OSSL_DEBUG)
11831183
/*

ext/openssl/ossl.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,19 @@ extern VALUE eOSSLError;
6969
}\
7070
} while (0)
7171

72+
/*
73+
* Type conversions
74+
*/
75+
#if !defined(NUM2UINT64T) /* in case Ruby starts to provide */
76+
# if SIZEOF_LONG == 8
77+
# define NUM2UINT64T(x) ((uint64_t)NUM2ULONG(x))
78+
# elif defined(HAVE_LONG_LONG) && SIZEOF_LONG_LONG == 8
79+
# define NUM2UINT64T(x) ((uint64_t)NUM2ULL(x))
80+
# else
81+
# error "unknown platform; no 64-bit width integer"
82+
# endif
83+
#endif
84+
7285
/*
7386
* Data Conversion
7487
*/
@@ -173,13 +186,13 @@ void ossl_debug(const char *, ...);
173186
#include "ossl_ocsp.h"
174187
#include "ossl_pkcs12.h"
175188
#include "ossl_pkcs7.h"
176-
#include "ossl_pkcs5.h"
177189
#include "ossl_pkey.h"
178190
#include "ossl_rand.h"
179191
#include "ossl_ssl.h"
180192
#include "ossl_version.h"
181193
#include "ossl_x509.h"
182194
#include "ossl_engine.h"
195+
#include "ossl_kdf.h"
183196

184197
void Init_openssl(void);
185198

ext/openssl/ossl_kdf.c

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
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+
#if defined(HAVE_EVP_PBE_SCRYPT)
68+
/*
69+
* call-seq:
70+
* KDF.scrypt(pass, salt:, N:, r:, p:, length:) -> aString
71+
*
72+
* Derives a key from _pass_ using given parameters with the scrypt
73+
* password-based key derivation function. The result can be used for password
74+
* storage.
75+
*
76+
* scrypt is designed to be memory-hard and more secure against brute-force
77+
* attacks using custom hardwares than alternative KDFs such as PBKDF2 or
78+
* bcrypt.
79+
*
80+
* The keyword arguments _N_, _r_ and _p_ can be used to tune scrypt. RFC 7914
81+
* (published on 2016-08, https://tools.ietf.org/html/rfc7914#section-2) states
82+
* that using values r=8 and p=1 appears to yield good results.
83+
*
84+
* See RFC 7914 (https://tools.ietf.org/html/rfc7914) for more information.
85+
*
86+
* === Parameters
87+
* pass :: Passphrase.
88+
* salt :: Salt.
89+
* N :: CPU/memory cost parameter. This must be a power of 2.
90+
* r :: Block size parameter.
91+
* p :: Parallelization parameter.
92+
* length :: Length in octets of the derived key.
93+
*
94+
* === Example
95+
* pass = "password"
96+
* salt = SecureRandom.random_bytes(16)
97+
* dk = OpenSSL::KDF.scrypt(pass, salt: salt, N: 2**14, r: 8, p: 1, length: 32)
98+
* p dk #=> "\xDA\xE4\xE2...\x7F\xA1\x01T"
99+
*/
100+
static VALUE
101+
kdf_scrypt(int argc, VALUE *argv, VALUE self)
102+
{
103+
VALUE pass, salt, opts, kwargs[5], str;
104+
static ID kwargs_ids[5];
105+
size_t len;
106+
uint64_t N, r, p, maxmem;
107+
108+
if (!kwargs_ids[0]) {
109+
kwargs_ids[0] = rb_intern_const("salt");
110+
kwargs_ids[1] = rb_intern_const("N");
111+
kwargs_ids[2] = rb_intern_const("r");
112+
kwargs_ids[3] = rb_intern_const("p");
113+
kwargs_ids[4] = rb_intern_const("length");
114+
}
115+
rb_scan_args(argc, argv, "1:", &pass, &opts);
116+
rb_get_kwargs(opts, kwargs_ids, 5, 0, kwargs);
117+
118+
StringValue(pass);
119+
salt = StringValue(kwargs[0]);
120+
N = NUM2UINT64T(kwargs[1]);
121+
r = NUM2UINT64T(kwargs[2]);
122+
p = NUM2UINT64T(kwargs[3]);
123+
len = NUM2LONG(kwargs[4]);
124+
/*
125+
* OpenSSL uses 32MB by default (if zero is specified), which is too small.
126+
* Let's not limit memory consumption but just let malloc() fail inside
127+
* OpenSSL. The amount is controllable by other parameters.
128+
*/
129+
maxmem = SIZE_MAX;
130+
131+
str = rb_str_new(0, len);
132+
if (!EVP_PBE_scrypt(RSTRING_PTR(pass), RSTRING_LEN(pass),
133+
(unsigned char *)RSTRING_PTR(salt), RSTRING_LEN(salt),
134+
N, r, p, maxmem, (unsigned char *)RSTRING_PTR(str), len))
135+
ossl_raise(eKDF, "EVP_PBE_scrypt");
136+
137+
return str;
138+
}
139+
#endif
140+
141+
void
142+
Init_ossl_kdf(void)
143+
{
144+
#if 0
145+
mOSSL = rb_define_module("OpenSSL");
146+
eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
147+
#endif
148+
149+
/*
150+
* Document-module: OpenSSL::KDF
151+
*
152+
* Provides functionality of various KDFs (key derivation function).
153+
*
154+
* KDF is typically used for securely deriving arbitrary length symmetric
155+
* keys to be used with an OpenSSL::Cipher from passwords. Another use case
156+
* is for storing passwords: Due to the ability to tweak the effort of
157+
* computation by increasing the iteration count, computation can be slowed
158+
* down artificially in order to render possible attacks infeasible.
159+
*
160+
* Currently, OpenSSL::KDF provides implementations for the following KDF:
161+
*
162+
* * PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in
163+
* combination with HMAC
164+
* * scrypt
165+
*
166+
* == Examples
167+
* === Generating a 128 bit key for a Cipher (e.g. AES)
168+
* pass = "secret"
169+
* salt = OpenSSL::Random.random_bytes(16)
170+
* iter = 20_000
171+
* key_len = 16
172+
* key = OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter,
173+
* length: key_len, hash: "sha1")
174+
*
175+
* === Storing Passwords
176+
* pass = "secret"
177+
* # store this with the generated value
178+
* salt = OpenSSL::Random.random_bytes(16)
179+
* iter = 20_000
180+
* hash = OpenSSL::Digest::SHA256.new
181+
* len = hash.digest_length
182+
* # the final value to be stored
183+
* value = OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter,
184+
* length: len, hash: hash)
185+
*
186+
* == Important Note on Checking Passwords
187+
* When comparing passwords provided by the user with previously stored
188+
* values, a common mistake made is comparing the two values using "==".
189+
* Typically, "==" short-circuits on evaluation, and is therefore
190+
* vulnerable to timing attacks. The proper way is to use a method that
191+
* always takes the same amount of time when comparing two values, thus
192+
* not leaking any information to potential attackers. To compare two
193+
* values, the following could be used:
194+
*
195+
* def eql_time_cmp(a, b)
196+
* unless a.length == b.length
197+
* return false
198+
* end
199+
* cmp = b.bytes
200+
* result = 0
201+
* a.bytes.each_with_index {|c,i|
202+
* result |= c ^ cmp[i]
203+
* }
204+
* result == 0
205+
* end
206+
*
207+
* Please note that the premature return in case of differing lengths
208+
* typically does not leak valuable information - when using PBKDF2, the
209+
* length of the values to be compared is of fixed size.
210+
*/
211+
mKDF = rb_define_module_under(mOSSL, "KDF");
212+
/*
213+
* Generic exception class raised if an error occurs in OpenSSL::KDF module.
214+
*/
215+
eKDF = rb_define_class_under(mKDF, "KDFError", eOSSLError);
216+
217+
rb_define_module_function(mKDF, "pbkdf2_hmac", kdf_pbkdf2_hmac, -1);
218+
#if defined(HAVE_EVP_PBE_SCRYPT)
219+
rb_define_module_function(mKDF, "scrypt", kdf_scrypt, -1);
220+
#endif
221+
}

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

0 commit comments

Comments
 (0)