散列和加密算法之间的根本区别
我发现哈希和加密算法之间存在很多混淆,我想听到更多关于以下方面的专家建议:
何时使用哈希与加密
什么使散列或加密算法不同(从理论/数学层面),即什么使得散列不可逆(没有彩虹树的帮助)
这里有一些类似的SO问题没有像我期待的那么详细:
混淆,散列和加密有什么区别?
加密和散列之间的区别
那么,你可以在维基百科查看它......但既然你想要一个解释,我会尽力:
散列函数
它们提供了任意长度输入和(通常)固定长度(或更小长度)输出之间的映射。 它可以是简单的crc32,也可以是诸如MD5或SHA1 / 2/256/512等完整的加密散列函数。 关键是有一个单向映射正在进行。 因为每个函数产生的输出都小于它的输入(如果你将每个可能的1mb文件送入MD5,你将会碰到大量的冲突),它总是一个多:1映射(意思是总会有冲突)。
他们很难(或实际上不可能)扭转的原因是因为他们的内部工作方式。 大多数密码散列函数迭代输入集多次以产生输出。 因此,如果我们查看每个固定长度的输入块(这是与算法有关的),散列函数将调用当前状态。 然后它将遍历状态并将其更改为新状态,并将其用作自身的反馈(MD5为每个512位数据块执行64次)。 然后它以某种方式将来自所有这些迭代的结果状态合并在一起以形成结果散列。
现在,如果你想解码哈希,你首先需要弄清楚如何将给定的哈希分解为它的迭代状态(1个输入小于一个数据块大小的可能性,许多大于输入)。 然后,你需要颠倒每个状态的迭代。 现在,为了解释为什么这是非常困难的,试想从下面的公式中推导出a
和b
: 10 = a + b
。 有10个可以工作的a
和b
正面组合。 现在循环一堆: tmp = a + b; a = b; b = tmp
tmp = a + b; a = b; b = tmp
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进行消息传输,您希望确保数据中的完整性。
有关密码散列的说明:
加密散列函数的一个重要特点是,它们应该是非常快的创建, 而且很难/慢逆转(以至于它几乎是不可能的)。 这带来了密码问题。 如果你存储sha512(password)
,你不会做一些事情来防止彩虹表或暴力攻击。 请记住,散列函数是为了速度而设计的。 所以攻击者通过散列函数运行字典并测试每个结果是微不足道的。
添加盐有助于解决问题,因为它会向散列中添加一些未知数据。 因此,他们不需要找到与md5(foo)
匹配的任何东西,而是需要找到添加到已知盐中时会产生md5(foo.salt)
(这非常困难)。 但它仍然不能解决速度问题,因为如果他们知道盐,它只是通过运行字典的问题。
所以有办法解决这个问题。 一种流行的方法被称为关键强化(或关键拉伸)。 基本上,你多次迭代哈希(通常是数千)。 这有两件事。 首先,它明显减慢散列算法的运行时间。 其次,如果正确执行(在每次迭代中传递输入和盐值)实际上会增加输出的熵(可用空间),从而减少冲突的几率。 一个简单的实现是:
var hash = password + salt;
for (var i = 0; i < 5000; i++) {
hash = sha512(hash + password + salt);
}
还有其他更标准的实现,例如PBKDF2,BCrypt。 但是这个技术被很多安全相关的系统(比如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!
}
其原因与碰撞有关。 请记住,所有哈希函数都有冲突,因为可能的输出空间(可能的输出数)小于输入空间。 为了明白为什么,让我们看看会发生什么。 为了说明这一点,让我们假设sha1()
的碰撞几率为0.001%(实际情况要低得多 ,但出于演示的目的)。
hash1 = sha1(password + salt);
现在, hash1
的碰撞概率为0.001%。 但是当我们做下一个hash2 = sha1(hash1);
, hash1
所有冲突hash1
自动成为hash2
冲突 。 所以现在,我们将hash1的速率设置为0.001%,而第二个sha1()
调用将添加到此。 所以现在, hash2
的碰撞概率为0.002%。 这是机会的两倍! 每次迭代都会为结果增加0.001%
的碰撞几率。 所以,经过1000次迭代,碰撞机率从0.001%升至1%。 现在,退化是线性的,并且真实概率要小得多,但效果是一样的(估计与md5
单次碰撞的概率约为1 /(2128)或1 /(3x1038)。小,由于生日攻击,它并不像看起来那么小)。
相反,通过每次重新添加盐和密码,您都将数据重新引入哈希函数。 因此,任何特定轮次的任何冲突都不再是下一轮的冲突。 所以:
hash = sha512(password + salt);
for (i = 0; i < 1000; i++) {
hash = sha512(hash + password + salt);
}
与原生sha512
函数具有相同的碰撞机会。 这是你想要的。 改为使用它。
散列函数可以被认为与烘烤一块面包相同。 你从输入(面粉,水,酵母等)开始,并在应用散列函数(混合+烘焙)后,最终得到一个输出:一块面包。
换一种方式非常困难 - 你不能真正将面包分离回面粉,水,酵母中 - 有些在烘烤过程中丢失了,你永远无法确切知道有多少水或面粉或酵母被用于一个特定的面包,因为信息被散列函数(又名烤箱)破坏了。
许多不同的输入变量理论上会产生相同的面包(例如,2杯水和1茶匙酵母产生与2.1杯水和0.9tsbp酵母完全相同的面包),但给出其中一个面包,你不能说出究竟是什么组合输入产生它。
另一方面,加密可以被视为保险箱。 无论你放在哪里,只要你拥有首先锁定的钥匙,就会回来。 这是一个对称操作。 给定一个键和一些输入,你会得到一定的输出。 鉴于输出和相同的关键,你会回到原来的输入。 这是一个1:1的映射。
当你不想恢复原始输入时使用散列,当你使用加密时使用散列。
哈希需要一些输入并将它转换为一些位(通常被认为是一个数字,例如32位整数,64位整数等)。 相同的输入将始终生成相同的散列,但是您在该过程中主要丢失了信息,因此您无法可靠地再现原始输入(但有一些注意事项)。
加密主要是保留您加入加密功能的所有信息,只是让任何人都难以完成(最好是不可能),而不必拥有特定的密钥即可恢复原始输入。
简单的哈希示例
这里有一个简单的例子来帮助你理解为什么哈希不能(在一般情况下)取回原始输入。 假设我正在创建一个1位散列。 我的散列函数需要一个位串作为输入,如果在输入字符串中设置了偶数位,则将散列设置为1;否则,如果有奇数,则设置为0。
例:
Input Hash
0010 0
0011 1
0110 1
1000 0
请注意,有许多输入值导致散列值为0,导致散列值为1的许多输入值。如果您知道散列值为0,则无法确定原始输入是什么。
顺便说一句,这个1位散列并不完全是人为设计的...看看奇偶校验位。
加密的简单例子
你可以使用简单的字母替换来加密文本,比如说如果输入是A,那么你写入B.如果输入是B,那么你写C。一直到字母的末尾,如果输入是Z,那么你再写一次。
Input Encrypted
CAT DBU
ZOO APP
就像简单的哈希示例一样,这种类型的加密已被历史使用。
链接地址: http://www.djcxy.com/p/22257.html上一篇: Fundamental difference between Hashing and Encryption algorithms
下一篇: How do I verify logged in user account against SQL Sever