我看到很多人对哈希和加密算法感到困惑,我想听一些更专业的建议:
何时使用哈希与加密
哈希或加密算法与理论/数学水平上的不同之处是什么,即什么使得哈希不可逆(没有彩虹树的帮助)?
以下是一些类似的SO问题,但并没有像我想要的那样详细说明:
我看到很多人对哈希和加密算法感到困惑,我想听一些更专业的建议:
何时使用哈希与加密
哈希或加密算法与理论/数学水平上的不同之处是什么,即什么使得哈希不可逆(没有彩虹树的帮助)?
以下是一些类似的SO问题,但并没有像我想要的那样详细说明:
好的,你可以在Wikipedia上查找...但由于你想要解释,我会尽力在这里做出解释:
它们提供了一个将任意长度输入映射到(通常是)固定长度(或更小长度)输出的方法。它可以是从简单的crc32到完整的加密哈希函数,如MD5或SHA1 / 2/256/512。重点是存在一种单向映射。它始终是多对一映射(意味着总会有冲突),因为每个函数产生的输出比其能够输入的更小(如果您将每个可能的1mb文件输入到MD5中,您将得到大量冲突)。
它们难以(或实际上不可能)反转的原因是因为它们内部的工作方式。大多数加密哈希函数会多次迭代输入集以产生输出。因此,如果我们查看输入的每个固定长度块(这取决于算法),哈希函数将调用该当前状态。然后,它将迭代状态并将其更改为新状态,并将其作为反馈输入到自身中(MD5对每个512位数据块进行64次操作)。然后,它以某种方式将所有这些迭代产生的结果状态组合在一起,形成最终的哈希值。
现在,如果你想解码哈希值,首先需要弄清如何将给定的哈希值分割成其迭代状态(对于小于数据块大小的输入,有1种可能性,而对于较大的输入,则有许多种可能性)。然后你需要为每个状态反向迭代。现在,要解释这为什么非常困难,请想象试图从以下公式中推断出a和b:10 = a + b
。有10个正组合的a
和b
可以使用。现在重复多次:tmp = a + b; a = b; b = tmp
。对于64次迭代,你将有超过10^64种可能性可供尝试。而这只是一个简单的加法,其中一些状态被保留从迭代到迭代。真正的哈希函数执行的操作不止一个(MD5在4个状态变量上执行约15个操作)。由于下一次迭代取决于前一次的状态,并且前一次在创建当前状态时被销毁,因此几乎不可能确定导致给定输出状态的输入状态(更不用说每次迭代了)。再加上涉及的大量可能性,即使是解码MD5也需要几乎无限(但不是无限)的资源。所需的资源太多了,以至于如果你知道输入的大小(对于较小的输入),暴力破解哈希值比尝试解码哈希值要便宜得多。
加密哈希函数的一个重要特征是它们应该非常快速地创建,并且非常难/缓慢地反转(以至于实际上几乎不可能)。这对密码构成了问题。如果您存储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);
}
还有其他更标准的实现,例如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
的所有碰撞都会自动成为 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
函数碰撞的机会相同。这正是您想要的。请使用它。哈希和加密/解密技术的基本概述如下。
哈希:
如果您对任何纯文本进行哈希处理,则无法从哈希文本中再次获取相同的纯文本。 简单地说,这是一个单向过程。
更新:针对编辑后的问题提出的观点进行回答。
1. 何时使用哈希 vs 加密
哈希 在你想要发送文件但担心有人截取并更改文件时非常有用。这时你可以公开发布哈希值,接收者可以计算接收到的文件的哈希值并核对是否与公开的哈希值相同,以确保文件的完整性。
加密 在你需要发送消息时非常好用。你可以用密钥加密消息,接收者可以使用相同(或不同)的密钥解密以获得原始消息。credits
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.
当您不想能够获取原始输入时,请使用哈希;当您需要时,请使用加密。
哈希将某些输入转换为一些位(通常被视为数字,如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
就像简单哈希示例一样,这种类型的加密方式在历史上被广泛使用。
我的两行代码... 一般面试官希望得到以下回答。
哈希是单向的。你不能从哈希码中转换出你的数据/ 字符串。
加密是双向的 - 如果你拥有密钥,你可以再次解密加密后的字符串。
哈希函数可以将任意长度的文本转换为固定长度的文本。
来源: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
一个加密函数使用加密密钥将文本转换为无意义的密文,反之亦然。
来源: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)
A_KEY
不是AES/Rijndael-128密钥;它是一个密码,而不是一个密钥。 - Maarten Bodewes对称加密:
对称加密也被称为共享密钥或共享秘密加密。在对称加密中,使用单个密钥来加密和解密流量。
非对称加密:
非对称加密也称为公钥加密。非对称加密与对称加密的主要区别在于使用两个密钥:一个用于加密,另一个用于解密。最常见的非对称加密算法是 RSA。
与对称加密相比,非对称加密会产生很高的计算负担,并且倾向于慢得多。因此,它通常不用来保护有效载荷数据。相反,其主要优势在于能够在不安全的媒介(例如互联网)上建立安全通道。这是通过交换公钥实现的,这些公钥只能用于加密数据。永远不会共享的补充私钥用于解密。
哈希:
最后,哈希是一种与加密不同的加密安全形式。而加密是用于对消息进行首先加密然后再解密的两个步骤过程,哈希将消息压缩成一个不可逆的固定长度值或散列。在网络中常见的两种哈希算法是 MD5 和 SHA-1。
了解更多信息:http://packetlife.net/blog/2010/nov/23/symmetric-asymmetric-encryption-hashing/
Symmetric Encryption 和 Asymmetric Encryption 都是加密数据的方法。 前者使用相同的密钥来加密和解密数据,后者则使用一对密钥(公钥和私钥)。 Hashing 是另一种加密技术,它将数据转换为固定长度的不可逆字符串,也称为哈希值。 这些技术都可以帮助保护您的数据免受未经授权的访问。当你只需要单向操作时,请使用哈希函数。例如,在系统中处理密码时,使用哈希函数是因为您仅需验证用户输入的值(经过哈希处理)是否与存储库中的值匹配。而加密可以进行双向操作。
哈希算法和加密算法只是数学算法。在这方面,它们没有区别 - 它们都只是数学公式。但从语义上讲,哈希(单向)和加密(双向)之间有非常大的区别。为什么哈希是不可逆的?因为它们被设计成这样,因为有时您需要一种单向操作。