加密哈希函数介绍

261 阅读6分钟

什么是散列函数?

散列函数是一个单向函数,它接受一些输入并返回一个确定性的输出。该输出通常被称为摘要、散列代码或简单的散列。

散列函数在软件中具有许多用途。从本质上讲,它们是一种工具,用于接收任意长度的输入,并产生一个固定长度的输出。它们被用于哈希表,以提供恒定的时间查询。在缓存中,它们经常被用来检测数据的变化。在密码学中,我们对几个属性最感兴趣。

  1. 确定性 - 相同的输入总是返回相同的输出。这很重要,因为我们需要知道我们可以信任函数的输出。
  2. 快速 - 它们可以被相对快速地计算(尽管在许多应用中,如密码散列,缓慢可能是一个理想的特征)。
  3. 单向的--鉴于输出,重现输入是不可能的。这维护了原始输入的隐私,使得不可能检索到,例如,传入函数的私人密钥。
  4. 混乱--输入的微小变化将产生一个非常不同的输出,以至于单看输出不可能将两者联系起来。这使得根据一系列的输出对输入进行推断变得困难--如果不是不可能的话。
  5. 独特--找到两个产生相同输出的输入是不可行的。

为什么我需要一个?

在密码学中,散列函数经常被用作消息认证码(MAC)。MAC用于检查消息的真实性(即消息来自何处)。由散列函数产生的MAC通常被称为HMAC。

让我们看一个使用MAC的例子。我们将发送以下信息。

{ "message": "Give $10 to Bob" }

如果一个攻击者截获该消息,他们可以在飞行中改变它。

{ "message": "Give $1000 to Bob" }
// or
{ "message": "Give $10 to Eve" }

为了防止这种情况,我们可以尝试附加一个我们用某种散列函数计算的MAC。

mac(message) = hash(message)

我们采取MAC函数的输出并将其附加到消息中。

{ "message": "Give $10 to Bob", mac: "12345" }

接收者可以使用相同的散列函数计算给定消息的MAC,并确认它与发送的内容相匹配。但现在,攻击者只需修改MAC,信息就会显得合法。

{ "message": "Give $10 to Eve", mac: "6789" }

为了防止这种情况,我们可以在计算MAC的时候增加一个秘密。增加一个共享秘密使这个函数成为HMAC。

hmac(secret, message) = hash(secret + message)

发送者将信息与秘密一起散列以产生HMAC,然后接收者将验证他们在收到信息时是否能使用相同的密钥计算出相同的HMAC。

// secret = "puppies"
{ "message": "Give $10 to Bob", hmac: "28573" }

除非攻击者知道秘密,否则要设计一个与给定的MAC相匹配的消息将是非常困难的(见属性5)。

// secret = ??? - idk let's try 'password'
{ "message": "Give $100 to Eve", hmac: "56184" }

这种使用HMAC验证消息的做法被用于难以确定传入的请求是否应该被信任的地方。值得注意的是,OAuth 1使用散列算法和预共享密钥来生成每个请求的散列值。

请注意,虽然这可能看起来是一个简单的安全方案,但有多种微妙的方式可以让天真的实现被利用。始终使用由安全专家构建和审核的密码代码。

许多语言的标准库包含HMAC算法的实现。例如,.Net框架在System.Security.Cryptography 名称空间下有一个名为HMACSHA256的类。这个算法使用SHA-2 256算法来计算HMAC。

散列算法的另一个用途是用于密码验证,但这是另一篇博文。

它们是如何工作的?

让我们想象一个简单的散列函数。

LENGTH_HASH(x) = LENGTH(x)

也就是说,给定一些输入x ,我们将返回输入的字节长度。考虑到属性5:唯一性。通过这个简单的散列函数,许多输入会产生相同的输出。

LENGTH_HASH('puppies') = 7
LENGTH_HASH('kittens') = 7

根据我们的用例,这可能是足够好的,但它不是真正的散列函数。让我们考虑更复杂一点的东西。

SUM_HASH(x) =
    LET sum = 0
    FOR c IN x
        sum += c
    RETURN sum

假设我们的输入被限制为大写字母字符,字符的赋值从1开始(即:A=1,B=2, ...)。使用SUM_HASH ,我们得到的输出比之前得到的更加多样。

SUM_HASH('ABC') = 1 + 2 + 3 = 6
SUM_HASH('BCD') = 2 + 3 + 4 = 9

我们的输出有了一些改进。但是,如果我们把一些字符换位呢?

SUM_HASH('ABC') = 6
SUM_HASH('CBA') = 6

哎呀,我们又有碰撞了。记住,我们要的是唯一的输出,所以这个稍微不那么天真的散列函数也是不行的。

我们还想让人很难从输出中找出输入的信息。虽然这并不复杂,但我们可以根据输入的总和推断出很多信息。如果我们假设所有字母的可能性相同,那么我们预计平均字符的值是13(范围1-26的中值)。如果我们有一个1300左右的值,我们就可以推断出原始输入大约有100个字符。如果你是手工操作,这可能看起来不是很多信息,但如果你能根据长度优先考虑你的破解算法要先尝试哪些字符串,你可能会节省很多时间。

这个解决方案也不是很混乱。如果我把我的输入从ABC 改为ABD ,输入就会从6变成7。如果我把ABE 哈希,就会得到8。我在这里看到了一个趋势....

因此,综上所述,我们的SUM_HASH 算法得到了2⁄5。它是确定性的,而且速度快。不幸的是,剩下的三个属性非常重要,以至于我们不能把它用于密码学。

显然,这是一个最好依靠专家的领域。设计一个加密散列函数是一个困难的过程,涉及的数学知识远远超出了普通软件工程师的能力。我再次提醒你,一定要使用由安全专家构建和审核的加密代码。

我应该使用哪种算法?

如果有疑问,请选择SHA-2系列的散列算法。SHA-2有六种不同的变体,但选择一种主要归结为你希望哈希值有多长。SHA-2 256产生256位摘要,而SHA-2 512产生 - 你猜对了 - 512位摘要。也有224位和384位的变种。SHA-2的优点是已经存在了一段时间(首次发表于2001年),所以它得到了广泛的支持,而且这个年龄意味着一定程度的战斗力。

SHA-3是新兴的散列算法。它发表于2015年,所以它没有得到广泛支持。然而,SHA-3是NIST的标准,所以它的安全特性被很好地理解,在生产中使用应该是安全的。

你应该避免将MD5和SHA-1用于加密用途。这些算法有众所周知的漏洞,使它们失去了在安全场景中使用的资格。