模块 OpenSSL::KDF
提供各种 KDF(密钥派生函数)的功能。
KDF
通常用于从密码安全地派生任意长度的对称密钥,以与 OpenSSL::Cipher
一起使用。另一个用例是存储密码:由于能够通过增加迭代次数来调整计算量,因此可以人为地降低计算速度,以使可能的攻击变得不可行。
目前,OpenSSL::KDF
提供以下 KDF 的实现
-
PKCS #5 PBKDF2(基于密码的密钥派生函数 2)与
HMAC
结合使用 -
scrypt
-
HKDF
示例¶ ↑
为 Cipher
生成 128 位密钥(例如 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")
存储密码¶ ↑
pass = "secret" # store this with the generated value salt = OpenSSL::Random.random_bytes(16) iter = 20_000 hash = OpenSSL::Digest.new('SHA256') len = hash.digest_length # the final value to be stored value = OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter, length: len, hash: hash)
关于检查密码的重要说明¶ ↑
在将用户提供的密码与先前存储的值进行比较时,一个常见的错误是使用“==”比较这两个值。通常,“==”在评估时会短路,因此容易受到计时攻击。正确的方法是使用一种在比较两个值时始终花费相同时间的方法,从而不会向潜在攻击者泄露任何信息。为此,请使用 OpenSSL.fixed_length_secure_compare
。
公共类方法
基于 HMAC 的提取和扩展密钥派生函数 (HKDF),如 RFC 5869 中所述。
在 OpenSSL
1.1.0 中新增。
参数¶ ↑
- ikm
-
输入密钥材料。
- salt
-
盐。
- info
-
上下文和特定于应用程序的信息。
- length
-
输出长度(以字节为单位)。必须 <=
255 * HashLen
,其中 HashLen 是哈希函数输出的长度(以字节为单位)。 - hash
-
哈希函数。
示例¶ ↑
# The values from https://datatracker.ietf.org/doc/html/rfc5869#appendix-A.1 ikm = ["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*") salt = ["000102030405060708090a0b0c"].pack("H*") info = ["f0f1f2f3f4f5f6f7f8f9"].pack("H*") p OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: 42, hash: "SHA256").unpack1("H*") # => "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"
static VALUE kdf_hkdf(int argc, VALUE *argv, VALUE self) { VALUE ikm, salt, info, opts, kwargs[4], str; static ID kwargs_ids[4]; int saltlen, ikmlen, infolen; size_t len; const EVP_MD *md; EVP_PKEY_CTX *pctx; if (!kwargs_ids[0]) { kwargs_ids[0] = rb_intern_const("salt"); kwargs_ids[1] = rb_intern_const("info"); kwargs_ids[2] = rb_intern_const("length"); kwargs_ids[3] = rb_intern_const("hash"); } rb_scan_args(argc, argv, "1:", &ikm, &opts); rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs); StringValue(ikm); ikmlen = RSTRING_LENINT(ikm); salt = StringValue(kwargs[0]); saltlen = RSTRING_LENINT(salt); info = StringValue(kwargs[1]); infolen = RSTRING_LENINT(info); len = (size_t)NUM2LONG(kwargs[2]); if (len > LONG_MAX) rb_raise(rb_eArgError, "length must be non-negative"); md = ossl_evp_get_digestbyname(kwargs[3]); str = rb_str_new(NULL, (long)len); pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); if (!pctx) ossl_raise(eKDF, "EVP_PKEY_CTX_new_id"); if (EVP_PKEY_derive_init(pctx) <= 0) { EVP_PKEY_CTX_free(pctx); ossl_raise(eKDF, "EVP_PKEY_derive_init"); } if (EVP_PKEY_CTX_set_hkdf_md(pctx, md) <= 0) { EVP_PKEY_CTX_free(pctx); ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_md"); } if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, (unsigned char *)RSTRING_PTR(salt), saltlen) <= 0) { EVP_PKEY_CTX_free(pctx); ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_salt"); } if (EVP_PKEY_CTX_set1_hkdf_key(pctx, (unsigned char *)RSTRING_PTR(ikm), ikmlen) <= 0) { EVP_PKEY_CTX_free(pctx); ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_key"); } if (EVP_PKEY_CTX_add1_hkdf_info(pctx, (unsigned char *)RSTRING_PTR(info), infolen) <= 0) { EVP_PKEY_CTX_free(pctx); ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_info"); } if (EVP_PKEY_derive(pctx, (unsigned char *)RSTRING_PTR(str), &len) <= 0) { EVP_PKEY_CTX_free(pctx); ossl_raise(eKDF, "EVP_PKEY_derive"); } rb_str_set_len(str, (long)len); EVP_PKEY_CTX_free(pctx); return str; }
PKCS #5 PBKDF2(基于密码的密钥派生函数 2)与 HMAC
结合使用。接受 pass、salt 和 iterations,然后派生一个 length 字节的密钥。
有关 PBKDF2 的更多信息,请参见 RFC 2898 第 5.2 节 (tools.ietf.org/html/rfc2898#section-5.2).
参数¶ ↑
- 密码
-
密码。
- salt
-
盐。盐可以防止基于常用密码字典的攻击和基于彩虹表的攻击。它是一个公共值,可以安全地与密码一起存储(例如,如果派生值用于密码存储)。
- 迭代次数
-
迭代次数。这提供了调整算法的能力。最好使用尽可能高的计数来最大限度地抵抗暴力攻击。
- length
-
派生密钥的所需长度(以字节为单位)。
- hash
-
与
HMAC
一起用于 PRF 的哈希算法。可以是表示算法名称的字符串,也可以是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 = ossl_evp_get_digestbyname(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; }
使用给定的参数从 pass 中派生一个密钥,使用 scrypt 基于密码的密钥派生函数。结果可用于密码存储。
scrypt 旨在对内存进行硬化,并且比 PBKDF2 或 bcrypt 等其他 KDF 更安全地抵御使用定制硬件的暴力攻击。
关键字参数 N、r 和 p 可用于调整 scrypt。RFC 7914(于 2016 年 8 月发布,tools.ietf.org/html/rfc7914#section-2)指出使用 r=8 和 p=1 的值似乎可以产生良好的结果。
有关更多信息,请参见 RFC 7914 (tools.ietf.org/html/rfc7914).
参数¶ ↑
- 密码
-
密码短语。
- salt
-
盐。
- N
-
CPU/内存成本参数。这必须是 2 的幂。
- r
-
块大小参数。
- p
-
并行化参数。
- length
-
派生密钥的长度(以字节为单位)。
示例¶ ↑
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; }