module OpenSSL::KDF
提供各种 KDF(密钥派生函数)的功能。
KDF 通常用于从密码安全地派生任意长度的对称密钥,这些密钥将与 OpenSSL::Cipher 一起使用。另一个用例是存储密码:通过增加迭代次数来调整计算量,可以人为地减慢计算速度,从而使可能的攻击变得不可行。
目前,OpenSSL::KDF 提供了以下 KDF 的实现
-
PKCS #5 PBKDF2(基于密码的密钥派生函数 2),结合
HMAC -
scrypt
-
HKDF
示例
为 Cipher(例如 AES)生成 128 位密钥
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。
Public Class Methods
Source
static VALUE
kdf_hkdf(int argc, VALUE *argv, VALUE self)
{
VALUE ikm, salt, info, opts, kwargs[4], str, md_holder;
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_md_fetch(kwargs[3], &md_holder);
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;
}
HMAC-based Extract-and-Expand Key Derivation Function (HKDF),如 RFC 5869 中所述。
在 OpenSSL 1.1.0 中新增。
参数
- ikm
-
输入密钥材料。
- salt
-
盐。
- info
-
上下文和应用程序特定的信息。
- length
-
输出长度(以字节为单位)。必须小于或等于
255 * HashLen,其中 HashLen 是哈希函数输出的长度(以字节为单位)。 - hash
-
哈希函数。
示例
# The values from https://www.rfc-editor.org/rfc/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"
Source
static VALUE
kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self)
{
VALUE pass, salt, opts, kwargs[4], str, md_holder;
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_md_fetch(kwargs[3], &md_holder);
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;
}
PKCS #5 PBKDF2(基于密码的密钥派生函数 2),结合 HMAC。接受pass、salt和iterations,然后派生一个length字节的密钥。
有关 PBKDF2 的更多信息,请参阅 RFC 2898 第 5.2 节(www.rfc-editor.org/rfc/rfc2898#section-5.2)。
参数
- 密码
-
密码。
- salt
-
盐。盐可防止基于常用密码字典的攻击和基于彩虹表的攻击。它是一个公共值,可以与密码一起安全地存储(例如,如果派生值用于密码存储)。
- iterations
-
迭代次数。这提供了调整算法的能力。最好使用尽可能高的次数,以最大程度地抵抗暴力破解攻击。
- length
-
所需的派生密钥长度(以字节为单位)。
- hash
-
与
HMAC结合使用的 PRF 的哈希算法。可以是代表算法名称的String,也可以是OpenSSL::Digest的实例。
Source
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;
}
使用 scrypt 基于密码的密钥派生函数,通过给定的参数从pass派生密钥。结果可用于密码存储。
scrypt 设计为内存密集型,并且比 PBKDF2 或 bcrypt 等替代 KDF 更能抵抗使用定制硬件进行的暴力破解攻击。
关键字参数N、r和p可用于调整 scrypt。RFC 7914(发布于 2016-08,www.rfc-editor.org/rfc/rfc7914#section-2)指出,使用 r=8 和 p=1 的值似乎能产生良好的结果。
有关更多信息,请参阅 RFC 7914(www.rfc-editor.org/rfc/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"