Skip to content

Commit 850fb5e

Browse files
committed
kdf: add scrypt
Add OpenSSL::KDF.scrypt as a wrapper around EVP_PBE_scrypt(). This is added by OpenSSL 1.1.0.
1 parent 941050c commit 850fb5e

File tree

3 files changed

+115
-0
lines changed

3 files changed

+115
-0
lines changed

ext/openssl/extconf.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
have_func("SSL_CTX_get_security_level")
116116
have_func("X509_get0_notBefore")
117117
have_func("SSL_SESSION_get_protocol_version")
118+
have_func("EVP_PBE_scrypt")
118119

119120
Logging::message "=== Checking done. ===\n"
120121

ext/openssl/ossl_kdf.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,80 @@ kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self)
6464
return str;
6565
}
6666

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+
67141
void
68142
Init_ossl_kdf(void)
69143
{
@@ -87,6 +161,7 @@ Init_ossl_kdf(void)
87161
*
88162
* * PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in
89163
* combination with HMAC
164+
* * scrypt
90165
*
91166
* == Examples
92167
* === Generating a 128 bit key for a Cipher (e.g. AES)
@@ -140,4 +215,7 @@ Init_ossl_kdf(void)
140215
eKDF = rb_define_class_under(mKDF, "KDFError", eOSSLError);
141216

142217
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
143221
}

test/test_kdf.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,40 @@ def test_pbkdf2_hmac_sha256_c_20000_len_32
100100
assert_equal(value1, value2)
101101
end
102102

103+
def test_scrypt_rfc7914_first
104+
pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0
105+
pass = ""
106+
salt = ""
107+
n = 16
108+
r = 1
109+
p = 1
110+
dklen = 64
111+
expected = B(%w{ 77 d6 57 62 38 65 7b 20 3b 19 ca 42 c1 8a 04 97
112+
f1 6b 48 44 e3 07 4a e8 df df fa 3f ed e2 14 42
113+
fc d0 06 9d ed 09 48 f8 32 6a 75 3a 0f c8 1f 17
114+
e8 d3 e0 fb 2e 0d 36 28 cf 35 e2 0c 38 d1 89 06 })
115+
assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen))
116+
end
117+
118+
def test_scrypt_rfc7914_second
119+
pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0
120+
pass = "password"
121+
salt = "NaCl"
122+
n = 1024
123+
r = 8
124+
p = 16
125+
dklen = 64
126+
expected = B(%w{ fd ba be 1c 9d 34 72 00 78 56 e7 19 0d 01 e9 fe
127+
7c 6a d7 cb c8 23 78 30 e7 73 76 63 4b 37 31 62
128+
2e af 30 d9 2e 22 a3 88 6f f1 09 27 9d 98 30 da
129+
c7 27 af b9 4a 83 ee 6d 83 60 cb df a2 cc 06 40 })
130+
assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen))
131+
end
132+
133+
private
134+
135+
def B(ary)
136+
[Array(ary).join].pack("H*")
137+
end
138+
103139
end

0 commit comments

Comments
 (0)