哈希算法和加密算法的基本区别

541

我看到很多人对哈希和加密算法感到困惑,我想听一些更专业的建议:

  1. 何时使用哈希与加密

  2. 哈希或加密算法与理论/数学水平上的不同之处是什么,即什么使得哈希不可逆(没有彩虹树的帮助)?

以下是一些类似的SO问题,但并没有像我想要的那样详细说明:

混淆、哈希和加密的区别是什么?
加密和哈希的区别


31
我可以预见当人们混淆这些术语时,这将成为他们要参考的问题。 :) - Adam Paynter
20
哈希是单向的(无法被还原),加密是双向的(可以被解密)。 - bestsss
1
哈希表也可用于索引大型结构和对象,例如文件。请参见哈希表 - HABO
25
哈希就像肉馅机。你可以将一头牛做成汉堡,但不能反过来做回一头牛。 - Neil McGuigan
我注意到我的问题已经被编辑了。我一直知道这两者之间的高层次差异,但更加好奇的是低层次/数学上的差异。:) 无论如何,SO上有很多好的内容!非常感谢! - Kenny Cason
14个回答

780

好的,你可以在Wikipedia上查找...但由于你想要解释,我会尽力在这里做出解释:

哈希函数

它们提供了一个将任意长度输入映射到(通常是)固定长度(或更小长度)输出的方法。它可以是从简单的crc32到完整的加密哈希函数,如MD5或SHA1 / 2/256/512。重点是存在一种单向映射。它始终是多对一映射(意味着总会有冲突),因为每个函数产生的输出比其能够输入的更小(如果您将每个可能的1mb文件输入到MD5中,您将得到大量冲突)。

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

现在,如果你想解码哈希值,首先需要弄清如何将给定的哈希值分割成其迭代状态(对于小于数据块大小的输入,有1种可能性,而对于较大的输入,则有许多种可能性)。然后你需要为每个状态反向迭代。现在,要解释这为什么非常困难,请想象试图从以下公式中推断出a和b:10 = a + b。有10个正组合的ab可以使用。现在重复多次:tmp = a + b; a = b; b = tmp。对于64次迭代,你将有超过10^64种可能性可供尝试。而这只是一个简单的加法,其中一些状态被保留从迭代到迭代。真正的哈希函数执行的操作不止一个(MD5在4个状态变量上执行约15个操作)。由于下一次迭代取决于前一次的状态,并且前一次在创建当前状态时被销毁,因此几乎不可能确定导致给定输出状态的输入状态(更不用说每次迭代了)。再加上涉及的大量可能性,即使是解码MD5也需要几乎无限(但不是无限)的资源。所需的资源太多了,以至于如果你知道输入的大小(对于较小的输入),暴力破解哈希值比尝试解码哈希值要便宜得多。

加密函数

他们提供了一个任意长度输入和输出之间的1:1映射。并且它们总是可逆的。需要注意的重要事情是,它是用某种方法可逆的。对于给定的密钥,它始终是1:1的。现在,可能会有多个输入:密钥对生成相同的输出(实际上通常会有,这取决于加密函数)。良好的加密数据与随机噪声无法区分。这与良好的哈希输出不同,后者始终具有一致的格式。
用例
当您想要比较一个值但不能存储明文表示(由于任何数量的原因)时,请使用哈希函数。密码应该非常适合这种用例,因为出于安全原因(而且不应该),您不希望以明文形式存储它们。但是,如果您想要检查文件系统中是否有盗版音乐文件怎么办?每个音乐文件存储3 mb是不切实际的。因此,取文件的哈希值,并将其存储(md5将存储16字节而不是3mb)。这样,您只需对每个文件进行哈希并与存储的哈希数据库进行比较即可(实际上由于重新编码、更改文件头等原因,这种方法不太有效,但这是一个示例用例)。
当你检查输入数据的有效性时,请使用哈希函数。这就是它们的设计目的。如果你有两个输入,想要检查它们是否相同,请将它们都通过哈希函数运行。对于小输入大小(假设使用良好的哈希函数),发生冲突的概率极低。这就是为什么建议用于密码的原因。对于长度不超过32个字符的密码,md5具有4倍的输出空间。SHA1具有6倍的输出空间(大约)。SHA512大约有16倍的输出空间。你真正关心的不是密码是什么,而是它是否与存储的密码相同。这就是为什么应该使用哈希来保护密码的原因。
每当需要取回输入数据时,请使用加密。请注意需要这个词。如果你正在存储信用卡号码,你需要在某些时候取回它们,但不想以明文形式存储它们。因此,存储加密版本并尽可能安全地保存密钥。
哈希函数也非常适合签署数据。例如,如果你使用HMAC,可以通过对数据连接到已知但未传输值(秘密值)的哈希来签署数据。因此,你发送明文和HMAC哈希。然后,接收者只需将提交的数据与已知值一起哈希,并检查它是否与传输的HMAC匹配。如果相同,你就知道它没有被没有秘密值的第三方篡改。这在HTTP框架的安全cookie系统中经常使用,以及在通过HTTP传输数据的消息传输中需要对数据的完整性进行某些保证。

关于密码哈希的注意事项:

加密哈希函数的一个重要特征是它们应该非常快速地创建,并且非常难/缓慢地反转(以至于实际上几乎不可能)。这对密码构成了问题。如果您存储sha512(password),则无法防止彩虹表或暴力攻击。请记住,哈希函数是为速度而设计的。因此,攻击者只需通过哈希函数运行字典并测试每个结果即可。

添加盐可以帮助解决问题,因为它向哈希中添加了一些未知数据。因此,他们需要找到与md5(foo.salt)匹配的内容,而不是md5(foo)(这更加困难)。但它仍然没有解决速度问题,因为如果他们知道盐,那么只需要通过字典即可。

因此,有处理此问题的方法。一种流行的方法称为key strengthening(或key stretching)。基本上,您需要多次迭代哈希(通常数千次)。这样做两件事情。首先,它显着减慢了哈希算法的运行时间。其次,如果正确实现(在每次迭代中将输入和盐传回),它实际上会增加输出的熵(可用空间),从而减少了冲突的可能性。一个简单的实现是:

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!
}

这是由于哈希冲突造成的。请记住,所有哈希函数都会发生冲突,因为可能的输出空间(可能的输出数量)比输入空间小。为了理解其中的原因,我们来看一下会发生什么。首先,假设从sha1()中有0.001%的冲突几率(实际上要低得多,但为了演示目的)。
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函数碰撞的机会相同。这正是您想要的。请使用它。

36
很遗憾,领英的程序员在将密码存储为未加盐的SHA1哈希之前没有阅读这篇文章... http://money.cnn.com/2012/06/06/technology/linkedin-password-hack/index.htm - Eric J.
2
@Pacerier:它也略微强调了哈希。 它专门详细介绍了密码哈希处理。 - ircmaxell
1
@Renren29 是的,你是正确的。在实践中,整个密码既不是满射的也不是单射的。但是,对于给定的密钥,它是满射的(每个明文恰好有一个密文)但不一定是单射的(并非每个可能的密文都有映射回)。这就是为什么我说 对于给定的密钥总是1:1。如果没有多个密钥可以输出到同一输出块,则密码将无用,因为密文将告诉您有关密钥的信息(而不需要知道它)。 - ircmaxell
8
很好的回答。我唯一的小问题是,微不足道的拉伸降解不能是线性的,否则它最终会超过100%。我认为在你的例子中,当0.001%时,第二步应该是0.001 +(1-0.001)* 0.001,即0.001999。 - AlexDev
1
根据这些宝贵的信息,您能解释一下ETEE是如何工作的吗? - homerun
显示剩余10条评论

169
一种哈希函数可以被视为烘焙面包。您从输入 (面粉、水、酵母等) 开始,经过哈希函数(混合+烘烤)处理后,得到一个输出: 一条面包。
反过来却极其困难 - 您无法将面包分离成面粉、水、酵母 - 在烘烤过程中有些内容被丢失了,而且您永远无法准确地知道用于制作特定面包的水、面粉或酵母的数量,因为这些信息已被哈希函数(即烤箱)销毁。
许多不同变体的输入理论上会产生相同的面包(例如,2杯水和1茶匙酵母与2.1杯水和0.9茶匙酵母完全产生相同的面包),但是给定其中一条面包,您无法准确地确定哪个组合的输入产生了它。
另一方面,加密可以被视为一个保险箱。只要您拥有最初用于锁定它的钥匙,您放进去的任何东西都会回来。这是对称操作。给定一个密钥和一些输入,您将得到某个输出。给定该输出和相同的密钥,则可以恢复原始输入。这是一个一对一的映射。

3
除了你无法轻易地证明一个汉堡完全来自一只特定的牛,这是散列的一个基本特性,因此这是一个有趣的想法,但是一个可怕的比喻。 - user467257
1
@caf lol 确实是一款经典的游戏。然而,奶牛很少被送到市场上去,它们的“男性伴侣”才会被送去;-) 奶牛:产奶。公牛:供肉。 - Funk Forty Niner

51

哈希和加密/解密技术的基本概述如下。

哈希:

如果您对任何纯文本进行哈希处理,则无法从哈希文本中再次获取相同的纯文本。 简单地说,这是一个单向过程。

hashing


加密和解密: 如果您使用密钥对任何明文进行加密,则可以使用相同(对称)/不同(非对称)密钥对加密文本进行解密,从而获得相同的明文。 encryption and decryption

更新:针对编辑后的问题提出的观点进行回答。

1. 何时使用哈希 vs 加密

哈希 在你想要发送文件但担心有人截取并更改文件时非常有用。这时你可以公开发布哈希值,接收者可以计算接收到的文件的哈希值并核对是否与公开的哈希值相同,以确保文件的完整性。

加密 在你需要发送消息时非常好用。你可以用密钥加密消息,接收者可以使用相同(或不同)的密钥解密以获得原始消息。credits


2. 哈希或加密算法与理论/数学层面的区别在于什么?即哈希如何成为不可逆的(没有彩虹树的情况下)。

Basically hashing is an operation that loses information but not encryption. Let's look at the difference in simple mathematical way for our easy understanding, of course both have much more complicated mathematical operations with repetitions involved in it

Encryption/Decryption (Reversible):

Addition:

4 + 3 = 7  

This can be reversed by taking the sum and subtracting one of the addends

7 - 3 = 4     

Multiplication:

4 * 5 = 20  

This can be reversed by taking the product and dividing by one of the factors

20 / 4 = 5    

So, here we could assume one of the addends/factors is a decryption key and result(7,20) is an encrypted text.


Hashing (Not Reversible):

Modulo division:

22 % 7 = 1   

This can not be reversed because there is no operation that you can do to the quotient and the dividend to reconstitute the divisor (or vice versa).

Can you find an operation to fill in where the '?' is?

1  ?  7 = 22  
1  ?  22 = 7

So hash functions have the same mathematical quality as modulo division and lose the information.

积分


非常简单易懂的例子。感谢您分享这个。 - Mayank Gupta

47

当您不想能够获取原始输入时,请使用哈希;当您需要时,请使用加密。

哈希将某些输入转换为一些位(通常被视为数字,如32位整数、64位整数等)。相同的输入始终会产生相同的哈希值,但在此过程中您主要会丢失信息,因此无法可靠地重现原始输入(然而有一些例外情况)。

加密主要保留您放入加密函数的所有信息,只是使其难以(理想情况下是不可能的)反向返回到原始输入,除非拥有特定的密钥。

哈希的简单示例

这是一个微不足道的示例,帮助您理解为什么哈希(一般情况下)无法获得原始输入。假设我正在创建一个 1 位哈希。我的哈希函数接受一个二进制字符串作为输入,并设置哈希值为 1,如果输入字符串中设置了偶数位,则设置为 0 如果是奇数位。

示例:

Input    Hash
0010     0
0011     1
0110     1
1000     0

请注意,有很多输入值的哈希结果为0,也有很多哈希结果为1。如果您知道哈希值为0,则无法确定原始输入是什么。

顺便说一句,这个1位哈希并不是编造出来的…看看奇偶校验位

加密的简单示例

您可以通过使用简单的字母替换来加密文本,例如如果输入是A,则写入B。如果输入是B,则写入C。一直到字母表的末尾,如果输入是Z,则再次写入A。

Input   Encrypted
CAT     DBU
ZOO     APP

就像简单哈希示例一样,这种类型的加密方式在历史上被广泛使用


值得注意的是,“加密”在口语中通常指强加密,不应与弱加密(例如上面的凯撒密码)混淆。 - Fax
@Fax 是的,但是什么构成强加密一直是一个不断变化的标准。德国二战时期的恩尼格玛机器几乎无法破解(有一部很棒的电影讲述了它)。今天,你的智能手表可以轻松破解它。DES曾经被认为是强大的,MD5也是如此。如今的强加密技术在可预见的未来可能会受到量子计算技术的威胁。 - Eric J.
当然,检查提供有关密码学建议的帖子和文章的日期总是一个好主意。话虽如此,我相信甚至在2011年凯撒密码也被认为是不安全的。 - Fax
在这些答案中看到了一些非常好的例子(奇偶校验、模运算、CRC)。哈希在分区和平衡方案中经常被使用,例如用于队列,但这经常被忽略。 - mckenzm

29

我的两行代码... 一般面试官希望得到以下回答。

哈希是单向的。你不能从哈希码中转换出你的数据/ 字符串。

加密是双向的 - 如果你拥有密钥,你可以再次解密加密后的字符串。


20

哈希函数可以将任意长度的文本转换为固定长度的文本。

Hash

来源:https://en.wikipedia.org/wiki/Hash_function


PHP中的哈希函数

哈希将一个字符串转换为散列字符串。请参见下面的示例。

HASH:

$str = 'My age is 29';
$hash = hash('sha1', $str);
echo $hash; // OUTPUT: 4d675d9fbefc74a38c89e005f9d776c75d92623e

通常密码以散列的形式存储,而不是明文。当最终用户想要访问受密码保护的应用程序时,必须在认证期间提供密码。当用户提交密码时,有效的认证系统会接收并散列此密码,并将其与系统已知的散列进行比较。如果相等,则授予访问权限。

反散列:

SHA1是单向散列。这意味着您无法对散列进行反散列。

但是,您可以使用暴力攻击来破解散列。请参见:https://hashkiller.co.uk/sha1-decrypter.aspx

MD5是另一种散列。此网站上可以找到MD5反散列器:https://www.md5online.org/

为了防止散列的暴力攻击,可以提供随机盐。在php中,您可以使用password_hash()创建一个密码散列。该函数password_hash()会自动创建一个盐。要验证带有盐的密码散列,请使用password_verify()

// Invoke this little script 3 times, and it will give you everytime a new hash
$password = '1234';  
$hash = password_hash($password, PASSWORD_DEFAULT);  

echo $hash; 
// OUTPUT 

$2y$10$ADxKiJW/Jn2DZNwpigWZ1ePwQ4il7V0ZB4iPeKj11n.iaDtLrC8bu 

$2y$10$H8jRnHDOMsHFMEZdT4Mk4uI4DCW7/YRKjfdcmV3MiA/WdzEvou71u 

$2y$10$qhyfIT25jpR63vCGvRbEoewACQZXQJ5glttlb01DmR4ota4L25jaW

一个密码可以被表示为多个哈希值。 当您使用password_verify()函数使用不同的密码哈希值验证密码时,只要其中任意一个哈希值匹配,则该密码将被接受为有效密码。

$password = '1234';  

$hash = '$2y$10$ADxKiJW/Jn2DZNwpigWZ1ePwQ4il7V0ZB4iPeKj11n.iaDtLrC8bu';  
var_dump( password_verify($password, $hash) );  

$hash = '$2y$10$H8jRnHDOMsHFMEZdT4Mk4uI4DCW7/YRKjfdcmV3MiA/WdzEvou71u';  
var_dump( password_verify($password, $hash) );  

$hash = '$2y$10$qhyfIT25jpR63vCGvRbEoewACQZXQJ5glttlb01DmR4ota4L25jaW';  
var_dump( password_verify($password, $hash) );

// OUTPUT 

boolean true 

boolean true 

boolean true




一个加密函数使用加密密钥将文本转换为无意义的密文,反之亦然。 enter image description here

来源:https://en.wikipedia.org/wiki/Encryption


PHP中的加密

让我们深入了解一些处理加密的 PHP 代码。

--- The Mcrypt扩展 ---

加密:

$cipher = MCRYPT_RIJNDAEL_128;
$key = 'A_KEY';
$data = 'My age is 29';
$mode = MCRYPT_MODE_ECB;

$encryptedData = mcrypt_encrypt($cipher, $key , $data , $mode);
var_dump($encryptedData);

//OUTPUT:
string '„Ùòyªq³¿ì¼üÀpå' (length=16)

解密:

$decryptedData = mcrypt_decrypt($cipher, $key , $encryptedData, $mode);
$decryptedData = rtrim($decryptedData, "\0\4"); // Remove the nulls and EOTs at the END
var_dump($decryptedData);

//OUTPUT:
string 'My age is 29' (length=12)

--- OpenSSL扩展 ---

Mcrypt扩展在7.1版中已被弃用,并在php 7.2中移除。应在php 7中使用OpenSSL扩展。请参阅以下代码片段:

$key = 'A_KEY';
$data = 'My age is 29';

// ENCRYPT
$encryptedData = openssl_encrypt($data , 'AES-128-CBC', $key, 0, 'IV_init_vector01');
var_dump($encryptedData);

// DECRYPT    
$decryptedData = openssl_decrypt($encryptedData, 'AES-128-CBC', $key, 0, 'IV_init_vector01');
var_dump($decryptedData);

//OUTPUT
string '4RJ8+18YkEd7Xk+tAMLz5Q==' (length=24)
string 'My age is 29' (length=12)

请注意,现在PHP mcrypt已被弃用(我可能与此有关),SHA-1、MD5和ECB都被认为是不安全的。 A_KEY不是AES/Rijndael-128密钥;它是一个密码,而不是一个密钥。 - Maarten Bodewes
@MaartenBodewes 是的,没错。OpenSSL现在很流行。http://php.net/manual/en/book.openssl.php - Julian

13

对称加密:

对称加密也被称为共享密钥或共享秘密加密。在对称加密中,使用单个密钥来加密和解密流量。

输入图像描述

非对称加密:

非对称加密也称为公钥加密。非对称加密与对称加密的主要区别在于使用两个密钥:一个用于加密,另一个用于解密。最常见的非对称加密算法是 RSA。

与对称加密相比,非对称加密会产生很高的计算负担,并且倾向于慢得多。因此,它通常不用来保护有效载荷数据。相反,其主要优势在于能够在不安全的媒介(例如互联网)上建立安全通道。这是通过交换公钥实现的,这些公钥只能用于加密数据。永远不会共享的补充私钥用于解密。

输入图像描述

哈希:

最后,哈希是一种与加密不同的加密安全形式。而加密是用于对消息进行首先加密然后再解密的两个步骤过程,哈希将消息压缩成一个不可逆的固定长度值或散列。在网络中常见的两种哈希算法是 MD5 和 SHA-1。

输入图像描述

了解更多信息:http://packetlife.net/blog/2010/nov/23/symmetric-asymmetric-encryption-hashing/

Symmetric Encryption 和 Asymmetric Encryption 都是加密数据的方法。 前者使用相同的密钥来加密和解密数据,后者则使用一对密钥(公钥和私钥)。 Hashing 是另一种加密技术,它将数据转换为固定长度的不可逆字符串,也称为哈希值。 这些技术都可以帮助保护您的数据免受未经授权的访问。

抱歉,我是一个安全新手,但您能进一步解释“通常用于保护有效载荷数据”的含义吗? - Honinbo Shusaku
2
@Abdul 非对称加密具有高计算负担,因此不用于保护通过网络作为数据包(有效载荷)发送的数据。相反,它用于使用公钥交换建立安全的网络连接以保护数据。 - Lucky

6
  1. 当你只需要单向操作时,请使用哈希函数。例如,在系统中处理密码时,使用哈希函数是因为您仅需验证用户输入的值(经过哈希处理)是否与存储库中的值匹配。而加密可以进行双向操作。

  2. 哈希算法和加密算法只是数学算法。在这方面,它们没有区别 - 它们都只是数学公式。但从语义上讲,哈希(单向)和加密(双向)之间有非常大的区别。为什么哈希是不可逆的?因为它们被设计成这样,因为有时您需要一种单向操作。


5
当涉及到数据传输的安全问题,即双向通信时,您会使用加密。所有加密都需要一个密钥。
当涉及到授权时,您会使用哈希。哈希中没有密钥。
哈希可以接受任意数量的数据(二进制或文本),并创建代表数据校验和的恒定长度哈希。例如,哈希可能是16字节。不同的哈希算法产生不同大小的哈希值。显然,您无法从哈希中重新创建原始数据,但可以再次哈希数据以查看是否生成相同的哈希值。单向基于Unix的密码工作方式就是这样。密码存储为哈希值,要登录系统,您输入的密码被哈希,然后将哈希值与真实密码的哈希进行比较。如果它们匹配,则必须已输入正确的密码。
为什么哈希是不可逆的:
哈希不可逆是因为输入到哈希映射不是一对一的。两个输入映射到相同的哈希值通常称为“哈希冲突”。出于安全考虑,“好”的哈希函数的一个属性是在实际使用中很少发生碰撞。

1
哈希不可逆,因为输入到哈希映射不是一对一的。谢谢,我认为这是区分哈希和加密的一个非常重要的因素! :) - Kenny Cason
这并没有清楚地区分普通哈希函数、加密哈希函数和密码哈希函数。它们都具有不同的属性。 - Maarten Bodewes

5
加密和哈希算法的工作方式相似。在每种情况下,都需要在位之间创建混淆和扩散。简而言之,混淆是在密钥和密文之间创建复杂关系,而扩散是将每个位的信息传播开来。
许多哈希函数实际上使用加密算法(或加密算法的原语)。例如,SHA-3候选者Skein使用Threefish作为处理每个块的基础方法。不同之处在于,它们不保留每个块的密文,而是以破坏性、确定性地合并到固定长度。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接