小编典典

哈希算法和加密算法之间的根本区别

all

我看到哈希和加密算法之间存在很多混淆,我想听听更多关于以下方面的专家建议:

  1. 何时使用哈希与加密

  2. 是什么使散列或加密算法不同(从理论/数学层面),即是什么使散列不可逆(没有彩虹树的帮助)

这里有一些 类似 的SO Questions 并没有像我想要的那样详细:

混淆、散列和加密有什么区别?
加密和散列之间的区别


阅读 244

收藏
2022-03-11

共1个答案

小编典典

好吧,您可以在维基百科中查找它......但是既然您想要解释,我会在这里尽力而为:

哈希函数

它们提供了任意长度输入和(通常)固定长度(或更小长度)输出之间的映射。它可以是任何东西,从简单的 crc32 到完整的加密哈希函数,例如 MD5 或
SHA1/2/256/512。关键是正在进行单向映射。它总是一个多:1
映射(意味着总会有冲突),因为每个函数产生的输出都比它能够输入的要小(如果你将每个可能的 1mb 文件输入 MD5,你会得到大量的冲突)。

它们很难(或实际上不可能)逆转的原因是它们内部的工作方式。大多数加密哈希函数会多次迭代输入集以产生输出。因此,如果我们查看每个固定长度的输入块(取决于算法),散列函数将称其为当前状态。然后它将遍历状态并将其更改为新状态并将其用作自身的反馈(MD5
对每个 512 位数据块执行 64 次此操作)。然后它以某种方式将所有这些迭代的结果状态组合在一起,形成最终的哈希值。

现在,如果您想解码散列,您首先需要弄清楚如何将给定的散列拆分为其迭代状态(输入小于数据块大小的一种可能性,许多用于较大的输入)。然后你需要反转每个状态的迭代。现在,为了解释为什么这非常困难,想象一下尝试从以下公式中推导a出来:
. 有 10 种和的积极组合可以发挥作用。现在循环了很多次:b``10 = a + b``a``b``tmp = a + b; a = b; b = tmp. 对于 64 次迭代,您将有超过 10^64 次尝试的可能性。这只是一个简单的添加,在迭代中保留了一些状态。真正的散列函数做的操作远不止 1
次(MD5 对 4 个状态变量做了大约 15
次操作)。而且由于下一次迭代取决于前一次的状态,并且前一次在创建当前状态时被破坏,因此几乎不可能确定导致给定输出状态的输入状态(对于每次迭代来说不少于)。结合这些,再加上涉及的大量可能性,即使是
MD5 解码也将占用近乎无限(但不是无限)的资源量。这么多资源,它’

加密函数

它们在任意长度的输入和输出之间提供 1:1 映射。而且它们总是可逆的。需要注意的重要一点是,使用某种方法它是可逆的。对于给定的键,它始终是
1:1。现在,有多个输入:密钥对可能生成相同的输出(实际上通常有,取决于加密函数)。良好的加密数据与随机噪声无法区分。这与始终具有一致格式的良好哈希输出不同。

用例

当您想要比较一个值但不能存储普通表示时(出于多种原因),请使用哈希函数。密码应该非常适合这个用例,因为出于安全原因(也不应该),您不想将它们存储为纯文本。但是,如果您想检查文件系统中的盗版音乐文件怎么办?每个音乐文件存储
3 mb 是不切实际的。因此,取而代之的是,获取文件的哈希值并存储它(md5 将存储 16 个字节而不是
3mb)。这样,您只需对每个文件进行哈希处理并与存储的哈希数据库进行比较(由于重新编码、更改文件头等,这在实践中效果不佳,但这是一个示例用例)。

在检查输入数据的有效性时使用散列函数。这就是它们的设计目的。如果您有 2
条输入,并且想检查它们是否相同,请通过哈希函数运行它们。对于小输入大小(假设散列函数良好),碰撞的概率非常低。这就是为什么建议使用密码。对于最多 32
个字符的密码,md5 有 4 倍的输出空间。SHA1 有 6 倍的输出空间(大约)。SHA512 的输出空间大约是 16 倍。您并不真正关心密码
什么,您关心的是它是否与存储的密码相同。这就是为什么你应该使用哈希作为密码。

每当您需要取回输入数据时,请使用加密。注意 需要
这个词。如果您要存储信用卡号,则需要在某个时候将它们取回,但不希望以纯文本形式存储它们。因此,请存储加密版本并尽可能安全地保存密钥。

散列函数也非常适合签署数据。例如,如果您使用 HMAC,您可以通过将数据与已知但未传输的值(秘密值)连接起来的哈希值对数据进行签名。因此,您发送纯文本和
HMAC 哈希。然后,接收方简单地用已知值对提交的数据进行哈希处理,并检查它是否与传输的 HMAC
匹配。如果相同,您就知道它没有被没有秘密值的一方篡改。这通常用于 HTTP 框架的安全 cookie 系统,以及通过 HTTP
进行数据的消息传输,在这种情况下您需要确保数据的完整性。

关于密码哈希的注释:

加密散列函数的一个关键特征是它们应该非常快地创建,并且 非常 难以/缓慢地反转(以至于几乎不可能)。这带来了密码问题。如果你 store
sha512(password),你就没有做任何事情来防范彩虹表或暴力攻击。请记住,哈希函数是为速度而设计的。因此,攻击者只需通过哈希函数运行字典并测试每个结果是微不足道的。

添加盐会有所帮助,因为它会在哈希中添加一些未知数据。因此,他们不需要找到任何匹配的东西,而是md5(foo)需要找到添加到已知盐中时产生的东西md5(foo.salt)(这很难做到)。但它仍然不能解决速度问题,因为如果他们知道盐,这只是运行字典的问题。

因此,有一些方法可以解决这个问题。一种流行的方法称为键加强(或键拉伸)。基本上,您会多次迭代哈希(通常是数千次)。这有两件事。首先,它显着减慢了散列算法的运行时间。其次,如果实施得当(在每次迭代中将输入和盐返回)实际上会增加输出的熵(可用空间),从而减少冲突的机会。一个简单的实现是:

var hash = password + salt;
for (var i = 0; i < 5000; i++) {
    hash = sha512(hash + password + salt);
}

还有其他更标准的实现,例如PBKDF2BCrypt。但这种技术被相当多的安全相关系统(如
PGP、WPA、Apache 和 OpenSSL)使用。

归根结底,hash(password)还不够好。 hash(password + salt)更好,但还不够好…使用拉伸哈希机制生成密码哈希…

关于琐碎拉伸的另一个注意事项

在任何情况下都不要将一个哈希的输出直接反馈回哈希函数

hash = sha512(password + salt); 
for (i = 0; i < 1000; i++) {
    hash = sha512(hash); // <-- Do NOT do this!
}

其原因与碰撞有关。请记住,所有哈希函数都会发生冲突,因为可能的输出空间(可能输出的数量)小于输入空间。要了解原因,让我们看看会发生什么。作为前言,我们假设有
0.001% 的碰撞几率sha1()(实际上 要低得多 ,但出于演示目的)。

hash1 = sha1(password + salt);

现在,hash1碰撞概率为 0.001%。但是当我们做下一个时hash2 = sha1(hash1);
所有的碰撞都hash1自动变成了的碰撞hash2。所以现在,我们将 hash1 的比率设为
0.001%,第二次sha1()调用增加了这一点。所以现在,hash2有 0.002%
的碰撞概率。那是两倍的机会!每次迭代都会0.001%给结果增加另一个碰撞机会。因此,经过 1000 次迭代,碰撞的几率从微不足道的 0.001% 跃升至
1%。现在,退化是线性的,实际概率 小得多,但效果是一样的(估计单次碰撞的几率md5约为 1/(2 128 ) 或 1/(3x10
38)。虽然这看起来很小,但由于生日攻击,它并没有看起来那么小)。

相反,通过每次重新附加盐和密码,您将数据重新引入哈希函数。因此,任何特定回合的任何碰撞都不再是下一轮的碰撞。所以:

hash = sha512(password + salt);
for (i = 0; i < 1000; i++) {
    hash = sha512(hash + password + salt);
}

sha512与本机函数具有相同的碰撞机会。这就是你想要的。改用那个。

2022-03-11