类 OpenSSL::Digest
OpenSSL::Digest
允许您计算任意数据的消息摘要(有时可互换地称为“哈希”),这些摘要在密码学上是安全的,即 Digest
实现了一个安全的单向函数。
单向函数提供了一些有用的属性。例如,给定两个不同的输入,它们产生相同输出的概率非常低。结合每个消息摘要算法都具有固定长度的输出(只有几个字节)这一事实,摘要通常用于为任意数据创建唯一标识符。一个常见的例子是为存储在数据库中的二进制文档创建唯一 ID。
单向函数的另一个有用特性(也是其名称的由来)是,给定一个摘要,无法推断出产生它的原始数据,即,识别原始输入的唯一方法是“暴力破解”所有可能的输入组合。
这些特性使单向函数成为公钥签名算法的理想伴侣:与其对整个文档进行签名,不如先使用速度更快的消息摘要算法对文档进行哈希运算,然后仅使用速度较慢的公钥算法对输出的几个字节进行签名。为了验证已签名文档的完整性,只需重新计算哈希值并验证它是否等于签名中的哈希值。
您可以在终端中运行以下命令,获取系统支持的所有摘要算法列表
openssl list -digest-algorithms
在 OpenSSL
1.1.1 支持的消息摘要算法中,有
-
SHA224、SHA256、SHA384、SHA512、SHA512-224 和 SHA512-256
-
SHA3-224、SHA3-256、SHA3-384 和 SHA3-512
-
BLAKE2s256 和 BLAKE2b512
每个算法都可以使用其名称进行实例化
digest = OpenSSL::Digest.new('SHA256')
“破解”消息摘要算法意味着违反其单向函数特性,即产生冲突或找到比暴力破解等更有效的方法来获取原始数据。从这个意义上说,大多数支持的摘要算法都可以被认为是已破解的,即使是广受欢迎的 MD5 和 SHA1 算法也是如此。如果安全性是您最关心的问题,那么您可能应该依赖 SHA224、SHA256、SHA384 或 SHA512。
对文件进行哈希运算¶ ↑
data = File.binread('document') sha256 = OpenSSL::Digest.new('SHA256') digest = sha256.digest(data)
一次对多个数据进行哈希运算¶ ↑
data1 = File.binread('file1') data2 = File.binread('file2') data3 = File.binread('file3') sha256 = OpenSSL::Digest.new('SHA256') sha256 << data1 sha256 << data2 sha256 << data3 digest = sha256.digest
重用 Digest
实例¶ ↑
data1 = File.binread('file1') sha256 = OpenSSL::Digest.new('SHA256') digest1 = sha256.digest(data1) data2 = File.binread('file2') sha256.reset digest2 = sha256.digest(data2)
公共类方法
根据 string 创建 Digest
实例,string 是支持的摘要算法的 ln(完整名称)或 sn(简称)。
如果给出了 data(一个 String
),则将其用作 Digest
实例的初始输入,即
digest = OpenSSL::Digest.new('sha256', 'digestdata')
等效于
digest = OpenSSL::Digest.new('sha256') digest.update('digestdata')
static VALUE ossl_digest_initialize(int argc, VALUE *argv, VALUE self) { EVP_MD_CTX *ctx; const EVP_MD *md; VALUE type, data; rb_scan_args(argc, argv, "11", &type, &data); md = ossl_evp_get_digestbyname(type); if (!NIL_P(data)) StringValue(data); TypedData_Get_Struct(self, EVP_MD_CTX, &ossl_digest_type, ctx); if (!ctx) { RTYPEDDATA_DATA(self) = ctx = EVP_MD_CTX_new(); if (!ctx) ossl_raise(eDigestError, "EVP_MD_CTX_new"); } if (!EVP_DigestInit_ex(ctx, md, NULL)) ossl_raise(eDigestError, "Digest initialization failed"); if (!NIL_P(data)) return ossl_digest_update(self, data); return self; }
公共实例方法
static VALUE ossl_digest_copy(VALUE self, VALUE other) { EVP_MD_CTX *ctx1, *ctx2; rb_check_frozen(self); if (self == other) return self; TypedData_Get_Struct(self, EVP_MD_CTX, &ossl_digest_type, ctx1); if (!ctx1) { RTYPEDDATA_DATA(self) = ctx1 = EVP_MD_CTX_new(); if (!ctx1) ossl_raise(eDigestError, "EVP_MD_CTX_new"); } GetDigest(other, ctx2); if (!EVP_MD_CTX_copy(ctx1, ctx2)) { ossl_raise(eDigestError, NULL); } return self; }
重置 Digest
,这意味着放弃任何已执行的 Digest#update
,并将 Digest
恢复到其初始状态。
static VALUE ossl_digest_reset(VALUE self) { EVP_MD_CTX *ctx; GetDigest(self, ctx); if (EVP_DigestInit_ex(ctx, EVP_MD_CTX_get0_md(ctx), NULL) != 1) { ossl_raise(eDigestError, "Digest initialization failed."); } return self; }
并非所有消息摘要都可以一步到位地计算。如果要从多个后续来源计算消息摘要,则可以将每个来源分别传递给 Digest
实例。
示例¶ ↑
digest = OpenSSL::Digest.new('SHA256') digest.update('First input') digest << 'Second input' # equivalent to digest.update('Second input') result = digest.digest
VALUE ossl_digest_update(VALUE self, VALUE data) { EVP_MD_CTX *ctx; StringValue(data); GetDigest(self, ctx); if (!EVP_DigestUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data))) ossl_raise(eDigestError, "EVP_DigestUpdate"); return self; }
私有实例方法
static VALUE ossl_digest_finish(int argc, VALUE *argv, VALUE self) { EVP_MD_CTX *ctx; VALUE str; int out_len; GetDigest(self, ctx); rb_scan_args(argc, argv, "01", &str); out_len = EVP_MD_CTX_size(ctx); if (NIL_P(str)) { str = rb_str_new(NULL, out_len); } else { StringValue(str); rb_str_resize(str, out_len); } if (!EVP_DigestFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), NULL)) ossl_raise(eDigestError, "EVP_DigestFinal_ex"); return str; }