diff options
author | Kazuki Yamaguchi <k@rhe.jp> | 2017-03-23 18:35:11 +0900 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2017-03-23 18:35:11 +0900 |
commit | 380a5860be750d72ebf093aacbd09528f4259550 (patch) | |
tree | ff58552f001354c4277615a5be6b37adb5df77e8 | |
parent | b7ae3765a033dbbc884a05636306c07522d360f2 (diff) | |
parent | 850fb5e9cfa5169f33a0843a6924d97a27edbd80 (diff) | |
download | ruby-openssl-380a5860be750d72ebf093aacbd09528f4259550.tar.gz |
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
-rw-r--r-- | ext/openssl/extconf.rb | 1 | ||||
-rw-r--r-- | ext/openssl/ossl.c | 2 | ||||
-rw-r--r-- | ext/openssl/ossl.h | 15 | ||||
-rw-r--r-- | ext/openssl/ossl_kdf.c | 221 | ||||
-rw-r--r-- | ext/openssl/ossl_kdf.h | 6 | ||||
-rw-r--r-- | ext/openssl/ossl_pkcs5.c | 172 | ||||
-rw-r--r-- | ext/openssl/ossl_pkcs5.h | 6 | ||||
-rw-r--r-- | lib/openssl.rb | 1 | ||||
-rw-r--r-- | lib/openssl/pkcs5.rb | 22 | ||||
-rw-r--r-- | test/test_kdf.rb | 139 | ||||
-rw-r--r-- | test/test_pkcs5.rb | 98 |
11 files changed, 405 insertions, 278 deletions
diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 7e014f8e..b887d922 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -119,6 +119,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.c b/ext/openssl/ossl.c index 0cc88c7b..3ddc3f56 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1170,7 +1170,6 @@ Init_openssl(void) Init_ossl_ns_spki(); Init_ossl_pkcs12(); Init_ossl_pkcs7(); - Init_ossl_pkcs5(); Init_ossl_pkey(); Init_ossl_rand(); Init_ossl_ssl(); @@ -1178,6 +1177,7 @@ Init_openssl(void) Init_ossl_ocsp(); Init_ossl_engine(); Init_ossl_asn1(); + Init_ossl_kdf(); #if defined(OSSL_DEBUG) /* diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 78eddd09..a11d93cd 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -70,6 +70,19 @@ extern VALUE eOSSLError; } while (0) /* + * Type conversions + */ +#if !defined(NUM2UINT64T) /* in case Ruby starts to provide */ +# if SIZEOF_LONG == 8 +# define NUM2UINT64T(x) ((uint64_t)NUM2ULONG(x)) +# elif defined(HAVE_LONG_LONG) && SIZEOF_LONG_LONG == 8 +# define NUM2UINT64T(x) ((uint64_t)NUM2ULL(x)) +# else +# error "unknown platform; no 64-bit width integer" +# endif +#endif + +/* * Data Conversion */ STACK_OF(X509) *ossl_x509_ary2sk0(VALUE); @@ -173,13 +186,13 @@ void ossl_debug(const char *, ...); #include "ossl_ocsp.h" #include "ossl_pkcs12.h" #include "ossl_pkcs7.h" -#include "ossl_pkcs5.h" #include "ossl_pkey.h" #include "ossl_rand.h" #include "ossl_ssl.h" #include "ossl_version.h" #include "ossl_x509.h" #include "ossl_engine.h" +#include "ossl_kdf.h" void Init_openssl(void); diff --git a/ext/openssl/ossl_kdf.c b/ext/openssl/ossl_kdf.c new file mode 100644 index 00000000..8bf376d7 --- /dev/null +++ b/ext/openssl/ossl_kdf.c @@ -0,0 +1,221 @@ +/* + * Ruby/OpenSSL Project + * Copyright (C) 2007, 2017 Ruby/OpenSSL Project Authors + */ +#include "ossl.h" + +static VALUE mKDF, eKDF; + +/* + * call-seq: + * KDF.pbkdf2_hmac(pass, salt:, iterations:, length:, hash:) -> aString + * + * PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in combination + * with HMAC. Takes _pass_, _salt_ and _iterations_, and then derives a key + * of _length_ bytes. + * + * For more information about PBKDF2, see RFC 2898 Section 5.2 + * (https://tools.ietf.org/html/rfc2898#section-5.2). + * + * === Parameters + * pass :: The passphrase. + * salt :: The salt. Salts prevent attacks based on dictionaries of common + * passwords and attacks based on rainbow tables. It is a public + * value that can be safely stored along with the password (e.g. + * if the derived value is used for password storage). + * iterations :: The iteration count. This provides the ability to tune the + * algorithm. It is better to use the highest count possible for + * the maximum resistance to brute-force attacks. + * length :: The desired length of the derived key in octets. + * hash :: The hash algorithm used with HMAC for the PRF. May be a String + * representing the algorithm name, or an instance of + * OpenSSL::Digest. + */ +static VALUE +kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self) +{ + VALUE pass, salt, opts, kwargs[4], str; + static ID kwargs_ids[4]; + int iters, len; + const EVP_MD *md; + + if (!kwargs_ids[0]) { + kwargs_ids[0] = rb_intern_const("salt"); + kwargs_ids[1] = rb_intern_const("iterations"); + kwargs_ids[2] = rb_intern_const("length"); + kwargs_ids[3] = rb_intern_const("hash"); + } + rb_scan_args(argc, argv, "1:", &pass, &opts); + rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs); + + StringValue(pass); + salt = StringValue(kwargs[0]); + iters = NUM2INT(kwargs[1]); + len = NUM2INT(kwargs[2]); + md = GetDigestPtr(kwargs[3]); + + str = rb_str_new(0, len); + if (!PKCS5_PBKDF2_HMAC(RSTRING_PTR(pass), RSTRING_LENINT(pass), + (unsigned char *)RSTRING_PTR(salt), + RSTRING_LENINT(salt), iters, md, len, + (unsigned char *)RSTRING_PTR(str))) + ossl_raise(eKDF, "PKCS5_PBKDF2_HMAC"); + + 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) +{ +#if 0 + mOSSL = rb_define_module("OpenSSL"); + eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); +#endif + + /* + * Document-module: OpenSSL::KDF + * + * Provides functionality of various KDFs (key derivation function). + * + * KDF is typically used for securely deriving arbitrary length symmetric + * keys to be used with an OpenSSL::Cipher from passwords. Another use case + * is for storing passwords: Due to the ability to tweak the effort of + * computation by increasing the iteration count, computation can be slowed + * down artificially in order to render possible attacks infeasible. + * + * Currently, OpenSSL::KDF provides implementations for the following KDF: + * + * * 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) + * pass = "secret" + * salt = OpenSSL::Random.random_bytes(16) + * iter = 20_000 + * key_len = 16 + * key = OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter, + * length: key_len, hash: "sha1") + * + * === Storing Passwords + * pass = "secret" + * # store this with the generated value + * salt = OpenSSL::Random.random_bytes(16) + * iter = 20_000 + * hash = OpenSSL::Digest::SHA256.new + * len = hash.digest_length + * # the final value to be stored + * value = OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter, + * length: len, hash: hash) + * + * == Important Note on Checking Passwords + * When comparing passwords provided by the user with previously stored + * values, a common mistake made is comparing the two values using "==". + * Typically, "==" short-circuits on evaluation, and is therefore + * vulnerable to timing attacks. The proper way is to use a method that + * always takes the same amount of time when comparing two values, thus + * not leaking any information to potential attackers. To compare two + * values, the following could be used: + * + * def eql_time_cmp(a, b) + * unless a.length == b.length + * return false + * end + * cmp = b.bytes + * result = 0 + * a.bytes.each_with_index {|c,i| + * result |= c ^ cmp[i] + * } + * result == 0 + * end + * + * Please note that the premature return in case of differing lengths + * typically does not leak valuable information - when using PBKDF2, the + * length of the values to be compared is of fixed size. + */ + mKDF = rb_define_module_under(mOSSL, "KDF"); + /* + * Generic exception class raised if an error occurs in OpenSSL::KDF module. + */ + 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/ext/openssl/ossl_kdf.h b/ext/openssl/ossl_kdf.h new file mode 100644 index 00000000..b6503f8d --- /dev/null +++ b/ext/openssl/ossl_kdf.h @@ -0,0 +1,6 @@ +#if !defined(OSSL_KDF_H) +#define OSSL_KDF_H + +void Init_ossl_kdf(void); + +#endif diff --git a/ext/openssl/ossl_pkcs5.c b/ext/openssl/ossl_pkcs5.c deleted file mode 100644 index 7811c5fe..00000000 --- a/ext/openssl/ossl_pkcs5.c +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2007 Technorama Ltd. <oss-ruby@technorama.net> - */ -#include "ossl.h" - -VALUE mPKCS5; -VALUE ePKCS5; - -/* - * call-seq: - * PKCS5.pbkdf2_hmac(pass, salt, iter, keylen, digest) => string - * - * === Parameters - * * +pass+ - string - * * +salt+ - string - should be at least 8 bytes long. - * * +iter+ - integer - should be greater than 1000. 20000 is better. - * * +keylen+ - integer - * * +digest+ - a string or OpenSSL::Digest object. - * - * Digests other than SHA1 may not be supported by other cryptography libraries. - */ -static VALUE -ossl_pkcs5_pbkdf2_hmac(VALUE self, VALUE pass, VALUE salt, VALUE iter, VALUE keylen, VALUE digest) -{ - VALUE str; - const EVP_MD *md; - int len = NUM2INT(keylen); - - StringValue(pass); - StringValue(salt); - md = GetDigestPtr(digest); - - str = rb_str_new(0, len); - - if (PKCS5_PBKDF2_HMAC(RSTRING_PTR(pass), RSTRING_LENINT(pass), - (unsigned char *)RSTRING_PTR(salt), RSTRING_LENINT(salt), - NUM2INT(iter), md, len, - (unsigned char *)RSTRING_PTR(str)) != 1) - ossl_raise(ePKCS5, "PKCS5_PBKDF2_HMAC"); - - return str; -} - -/* - * call-seq: - * PKCS5.pbkdf2_hmac_sha1(pass, salt, iter, keylen) => string - * - * === Parameters - * * +pass+ - string - * * +salt+ - string - should be at least 8 bytes long. - * * +iter+ - integer - should be greater than 1000. 20000 is better. - * * +keylen+ - integer - * - * This method is available in almost any version of OpenSSL. - * - * Conforms to RFC 2898. - */ -static VALUE -ossl_pkcs5_pbkdf2_hmac_sha1(VALUE self, VALUE pass, VALUE salt, VALUE iter, VALUE keylen) -{ - VALUE str; - int len = NUM2INT(keylen); - - StringValue(pass); - StringValue(salt); - - str = rb_str_new(0, len); - - if (PKCS5_PBKDF2_HMAC_SHA1(RSTRING_PTR(pass), RSTRING_LENINT(pass), - (const unsigned char *)RSTRING_PTR(salt), RSTRING_LENINT(salt), NUM2INT(iter), - len, (unsigned char *)RSTRING_PTR(str)) != 1) - ossl_raise(ePKCS5, "PKCS5_PBKDF2_HMAC_SHA1"); - - return str; -} - -void -Init_ossl_pkcs5(void) -{ -#if 0 - mOSSL = rb_define_module("OpenSSL"); - eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); -#endif - - /* Document-class: OpenSSL::PKCS5 - * - * Provides password-based encryption functionality based on PKCS#5. - * Typically used for securely deriving arbitrary length symmetric keys - * to be used with an OpenSSL::Cipher from passwords. Another use case - * is for storing passwords: Due to the ability to tweak the effort of - * computation by increasing the iteration count, computation can be - * slowed down artificially in order to render possible attacks infeasible. - * - * PKCS5 offers support for PBKDF2 with an OpenSSL::Digest::SHA1-based - * HMAC, or an arbitrary Digest. - * - * === Parameters - * ==== Password - * Typically an arbitrary String that represents the password to be used - * for deriving a key. - * ==== Salt - * Prevents attacks based on dictionaries of common passwords. It is a - * public value that can be safely stored along with the password (e.g. - * if PBKDF2 is used for password storage). For maximum security, a fresh, - * random salt should be generated for each stored password. According - * to PKCS#5, a salt should be at least 8 bytes long. - * ==== Iteration Count - * Allows to tweak the length that the actual computation will take. The - * larger the iteration count, the longer it will take. - * ==== Key Length - * Specifies the length in bytes of the output that will be generated. - * Typically, the key length should be larger than or equal to the output - * length of the underlying digest function, otherwise an attacker could - * simply try to brute-force the key. According to PKCS#5, security is - * limited by the output length of the underlying digest function, i.e. - * security is not improved if a key length strictly larger than the - * digest output length is chosen. Therefore, when using PKCS5 for - * password storage, it suffices to store values equal to the digest - * output length, nothing is gained by storing larger values. - * - * == Examples - * === Generating a 128 bit key for a Cipher (e.g. AES) - * pass = "secret" - * salt = OpenSSL::Random.random_bytes(16) - * iter = 20000 - * key_len = 16 - * key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(pass, salt, iter, key_len) - * - * === Storing Passwords - * pass = "secret" - * salt = OpenSSL::Random.random_bytes(16) #store this with the generated value - * iter = 20000 - * digest = OpenSSL::Digest::SHA256.new - * len = digest.digest_length - * #the final value to be stored - * value = OpenSSL::PKCS5.pbkdf2_hmac(pass, salt, iter, len, digest) - * - * === Important Note on Checking Passwords - * When comparing passwords provided by the user with previously stored - * values, a common mistake made is comparing the two values using "==". - * Typically, "==" short-circuits on evaluation, and is therefore - * vulnerable to timing attacks. The proper way is to use a method that - * always takes the same amount of time when comparing two values, thus - * not leaking any information to potential attackers. To compare two - * values, the following could be used: - * def eql_time_cmp(a, b) - * unless a.length == b.length - * return false - * end - * cmp = b.bytes.to_a - * result = 0 - * a.bytes.each_with_index {|c,i| - * result |= c ^ cmp[i] - * } - * result == 0 - * end - * Please note that the premature return in case of differing lengths - * typically does not leak valuable information - when using PKCS#5, the - * length of the values to be compared is of fixed size. - */ - - mPKCS5 = rb_define_module_under(mOSSL, "PKCS5"); - /* Document-class: OpenSSL::PKCS5::PKCS5Error - * - * Generic Exception class that is raised if an error occurs during a - * computation. - */ - ePKCS5 = rb_define_class_under(mPKCS5, "PKCS5Error", eOSSLError); - - rb_define_module_function(mPKCS5, "pbkdf2_hmac", ossl_pkcs5_pbkdf2_hmac, 5); - rb_define_module_function(mPKCS5, "pbkdf2_hmac_sha1", ossl_pkcs5_pbkdf2_hmac_sha1, 4); -} diff --git a/ext/openssl/ossl_pkcs5.h b/ext/openssl/ossl_pkcs5.h deleted file mode 100644 index a3b132bc..00000000 --- a/ext/openssl/ossl_pkcs5.h +++ /dev/null @@ -1,6 +0,0 @@ -#if !defined(_OSSL_PKCS5_H_) -#define _OSSL_PKCS5_H_ - -void Init_ossl_pkcs5(void); - -#endif /* _OSSL_PKCS5_H_ */ diff --git a/lib/openssl.rb b/lib/openssl.rb index 26d167a9..09142829 100644 --- a/lib/openssl.rb +++ b/lib/openssl.rb @@ -19,3 +19,4 @@ require 'openssl/config' require 'openssl/digest' require 'openssl/x509' require 'openssl/ssl' +require 'openssl/pkcs5' diff --git a/lib/openssl/pkcs5.rb b/lib/openssl/pkcs5.rb new file mode 100644 index 00000000..959447df --- /dev/null +++ b/lib/openssl/pkcs5.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: false +#-- +# Ruby/OpenSSL Project +# Copyright (C) 2017 Ruby/OpenSSL Project Authors +#++ + +module OpenSSL + module PKCS5 + module_function + + # OpenSSL::PKCS5.pbkdf2_hmac has been renamed to OpenSSL::KDF.pbkdf2_hmac. + # This method is provided for backwards compatibility. + def pbkdf2_hmac(pass, salt, iter, keylen, digest) + OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter, + length: keylen, hash: digest) + end + + def pbkdf2_hmac_sha1(pass, salt, iter, keylen) + pbkdf2_hmac(pass, salt, iter, keylen, "sha1") + end + end +end diff --git a/test/test_kdf.rb b/test/test_kdf.rb new file mode 100644 index 00000000..9346be7c --- /dev/null +++ b/test/test_kdf.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: false +require_relative 'utils' + +class OpenSSL::TestKDF < OpenSSL::TestCase + + def test_pkcs5_pbkdf2_hmac_compatibility + expected = OpenSSL::KDF.pbkdf2_hmac("password", salt: "salt", iterations: 1, length: 20, hash: "sha1") + assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac("password", "salt", 1, 20, "sha1")) + assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac_sha1("password", "salt", 1, 20)) + end + + def test_pbkdf2_hmac_sha1_rfc6070_c_1_len_20 + p ="password" + s = "salt" + c = 1 + dk_len = 20 + raw = %w{ 0c 60 c8 0f 96 1f 0e 71 + f3 a9 b5 24 af 60 12 06 + 2f e0 37 a6 } + expected = [raw.join('')].pack('H*') + value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") + assert_equal(expected, value) + end + + def test_pbkdf2_hmac_sha1_rfc6070_c_2_len_20 + p ="password" + s = "salt" + c = 2 + dk_len = 20 + raw = %w{ ea 6c 01 4d c7 2d 6f 8c + cd 1e d9 2a ce 1d 41 f0 + d8 de 89 57 } + expected = [raw.join('')].pack('H*') + value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") + assert_equal(expected, value) + end + + def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_20 + p ="password" + s = "salt" + c = 4096 + dk_len = 20 + raw = %w{ 4b 00 79 01 b7 65 48 9a + be ad 49 d9 26 f7 21 d0 + 65 a4 29 c1 } + expected = [raw.join('')].pack('H*') + value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") + assert_equal(expected, value) + end + +# takes too long! +# def test_pbkdf2_hmac_sha1_rfc6070_c_16777216_len_20 +# p ="password" +# s = "salt" +# c = 16777216 +# dk_len = 20 +# raw = %w{ ee fe 3d 61 cd 4d a4 e4 +# e9 94 5b 3d 6b a2 15 8c +# 26 34 e9 84 } +# expected = [raw.join('')].pack('H*') +# value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") +# assert_equal(expected, value) +# end + + def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25 + p ="passwordPASSWORDpassword" + s = "saltSALTsaltSALTsaltSALTsaltSALTsalt" + c = 4096 + dk_len = 25 + + raw = %w{ 3d 2e ec 4f e4 1c 84 9b + 80 c8 d8 36 62 c0 e4 4a + 8b 29 1a 96 4c f2 f0 70 + 38 } + expected = [raw.join('')].pack('H*') + value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") + assert_equal(expected, value) + end + + def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_16 + p ="pass\0word" + s = "sa\0lt" + c = 4096 + dk_len = 16 + raw = %w{ 56 fa 6a a7 55 48 09 9d + cc 37 d7 f0 34 25 e0 c3 } + expected = [raw.join('')].pack('H*') + value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") + assert_equal(expected, value) + end + + def test_pbkdf2_hmac_sha256_c_20000_len_32 + #unfortunately no official test vectors available yet for SHA-2 + p ="password" + s = OpenSSL::Random.random_bytes(16) + c = 20000 + dk_len = 32 + value1 = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha256") + value2 = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha256") + 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 diff --git a/test/test_pkcs5.rb b/test/test_pkcs5.rb deleted file mode 100644 index 59a7e7c9..00000000 --- a/test/test_pkcs5.rb +++ /dev/null @@ -1,98 +0,0 @@ -# frozen_string_literal: false -require_relative 'utils' - -class OpenSSL::TestPKCS5 < OpenSSL::TestCase - - def test_pbkdf2_hmac_sha1_rfc6070_c_1_len_20 - p ="password" - s = "salt" - c = 1 - dk_len = 20 - raw = %w{ 0c 60 c8 0f 96 1f 0e 71 - f3 a9 b5 24 af 60 12 06 - 2f e0 37 a6 } - expected = [raw.join('')].pack('H*') - value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len) - assert_equal(expected, value) - end - - def test_pbkdf2_hmac_sha1_rfc6070_c_2_len_20 - p ="password" - s = "salt" - c = 2 - dk_len = 20 - raw = %w{ ea 6c 01 4d c7 2d 6f 8c - cd 1e d9 2a ce 1d 41 f0 - d8 de 89 57 } - expected = [raw.join('')].pack('H*') - value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len) - assert_equal(expected, value) - end - - def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_20 - p ="password" - s = "salt" - c = 4096 - dk_len = 20 - raw = %w{ 4b 00 79 01 b7 65 48 9a - be ad 49 d9 26 f7 21 d0 - 65 a4 29 c1 } - expected = [raw.join('')].pack('H*') - value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len) - assert_equal(expected, value) - end - -# takes too long! -# def test_pbkdf2_hmac_sha1_rfc6070_c_16777216_len_20 -# p ="password" -# s = "salt" -# c = 16777216 -# dk_len = 20 -# raw = %w{ ee fe 3d 61 cd 4d a4 e4 -# e9 94 5b 3d 6b a2 15 8c -# 26 34 e9 84 } -# expected = [raw.join('')].pack('H*') -# value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len) -# assert_equal(expected, value) -# end - - def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25 - p ="passwordPASSWORDpassword" - s = "saltSALTsaltSALTsaltSALTsaltSALTsalt" - c = 4096 - dk_len = 25 - - raw = %w{ 3d 2e ec 4f e4 1c 84 9b - 80 c8 d8 36 62 c0 e4 4a - 8b 29 1a 96 4c f2 f0 70 - 38 } - expected = [raw.join('')].pack('H*') - value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len) - assert_equal(expected, value) - end - - def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_16 - p ="pass\0word" - s = "sa\0lt" - c = 4096 - dk_len = 16 - raw = %w{ 56 fa 6a a7 55 48 09 9d - cc 37 d7 f0 34 25 e0 c3 } - expected = [raw.join('')].pack('H*') - value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len) - assert_equal(expected, value) - end - - def test_pbkdf2_hmac_sha256_c_20000_len_32 - #unfortunately no official test vectors available yet for SHA-2 - p ="password" - s = OpenSSL::Random.random_bytes(16) - c = 20000 - dk_len = 32 - digest = OpenSSL::Digest::SHA256.new - value1 = OpenSSL::PKCS5.pbkdf2_hmac(p, s, c, dk_len, digest) - value2 = OpenSSL::PKCS5.pbkdf2_hmac(p, s, c, dk_len, digest) - assert_equal(value1, value2) - end - -end |