diff options
author | Kazuki Yamaguchi <k@rhe.jp> | 2016-12-21 18:52:14 +0900 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2017-03-23 18:34:42 +0900 |
commit | 850fb5e9cfa5169f33a0843a6924d97a27edbd80 (patch) | |
tree | dcbb6182b016222920293b80274fefcd4226b7a1 | |
parent | 941050c2a2ff71e7792b2a1c63db9f27e68fe7ae (diff) | |
download | ruby-openssl-topic/kdf-module.tar.gz |
kdf: add scrypttopic/kdf-module
Add OpenSSL::KDF.scrypt as a wrapper around EVP_PBE_scrypt(). This is
added by OpenSSL 1.1.0.
-rw-r--r-- | ext/openssl/extconf.rb | 1 | ||||
-rw-r--r-- | ext/openssl/ossl_kdf.c | 78 | ||||
-rw-r--r-- | test/test_kdf.rb | 36 |
3 files changed, 115 insertions, 0 deletions
diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index a97883c1..5a046678 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -115,6 +115,7 @@ OpenSSL.check_func_or_macro("SSL_CTX_set_min_proto_version", "openssl/ssl.h") have_func("SSL_CTX_get_security_level") have_func("X509_get0_notBefore") have_func("SSL_SESSION_get_protocol_version") +have_func("EVP_PBE_scrypt") Logging::message "=== Checking done. ===\n" diff --git a/ext/openssl/ossl_kdf.c b/ext/openssl/ossl_kdf.c index c785ee6d..8bf376d7 100644 --- a/ext/openssl/ossl_kdf.c +++ b/ext/openssl/ossl_kdf.c @@ -64,6 +64,80 @@ kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self) return str; } +#if defined(HAVE_EVP_PBE_SCRYPT) +/* + * call-seq: + * KDF.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, https://tools.ietf.org/html/rfc7914#section-2) states + * that using values r=8 and p=1 appears to yield good results. + * + * See RFC 7914 (https://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" + */ +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; +} +#endif + void Init_ossl_kdf(void) { @@ -87,6 +161,7 @@ Init_ossl_kdf(void) * * * PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in * combination with HMAC + * * scrypt * * == Examples * === Generating a 128 bit key for a Cipher (e.g. AES) @@ -140,4 +215,7 @@ Init_ossl_kdf(void) eKDF = rb_define_class_under(mKDF, "KDFError", eOSSLError); rb_define_module_function(mKDF, "pbkdf2_hmac", kdf_pbkdf2_hmac, -1); +#if defined(HAVE_EVP_PBE_SCRYPT) + rb_define_module_function(mKDF, "scrypt", kdf_scrypt, -1); +#endif } diff --git a/test/test_kdf.rb b/test/test_kdf.rb index 21f4bf99..9346be7c 100644 --- a/test/test_kdf.rb +++ b/test/test_kdf.rb @@ -100,4 +100,40 @@ class OpenSSL::TestKDF < OpenSSL::TestCase assert_equal(value1, value2) end + def test_scrypt_rfc7914_first + pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0 + pass = "" + salt = "" + n = 16 + r = 1 + p = 1 + dklen = 64 + expected = B(%w{ 77 d6 57 62 38 65 7b 20 3b 19 ca 42 c1 8a 04 97 + f1 6b 48 44 e3 07 4a e8 df df fa 3f ed e2 14 42 + fc d0 06 9d ed 09 48 f8 32 6a 75 3a 0f c8 1f 17 + e8 d3 e0 fb 2e 0d 36 28 cf 35 e2 0c 38 d1 89 06 }) + assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen)) + end + + def test_scrypt_rfc7914_second + pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0 + pass = "password" + salt = "NaCl" + n = 1024 + r = 8 + p = 16 + dklen = 64 + expected = B(%w{ fd ba be 1c 9d 34 72 00 78 56 e7 19 0d 01 e9 fe + 7c 6a d7 cb c8 23 78 30 e7 73 76 63 4b 37 31 62 + 2e af 30 d9 2e 22 a3 88 6f f1 09 27 9d 98 30 da + c7 27 af b9 4a 83 ee 6d 83 60 cb df a2 cc 06 40 }) + assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen)) + end + + private + + def B(ary) + [Array(ary).join].pack("H*") + end + end |