认识Hash 函数及其重要性

【数据结构与算法】哈希算法的原理和应用详解! 在程序员的实际开发中,哈希算法常常能用得到,本文以哈希算法的原理和应用为核心,和大家详细讲解一下哈希算法的概念、常见算法以及原理、在信息安全的应用等等。 一、概念 哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值。哈希的思路很简单,如果所有的键都是整数,那么就可以使用一个简单的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任意键的值。这是对于简单的键的情况,我们将其扩展到可以处理更加复杂的类型的键.. 阅读详情

不时会爆出网站的服务器和数据库被盗取,考虑到这点,就要确保用户一些敏感数据(例如密码)的安全性。今天,我们要学的是 hash 背后的基础知识,以及如何用它来保护你的 web 应用的密码。

申明

密码学是非常复杂的一门学科,我不是这方面的专家,在很多大学和安全机构,在这个领域都有长期的研究。

本文我试图使事情简单化,呈现给大家的是一个 web 应用中安全存储密码的合理方法。

“Hashing” 做的是什么?

Hashing 将一段数据(无论长还是短)转成相对较短的一段数据,例如一个字符串或者一个整数。

这是通过使用单向哈希函数来完成的。“单向” 意味着逆转它是困难的,或者实际上是不可能的。

加密可以保证信息的安全性,避免被拦截到被破解。Python 的加密支持包括使用 hashlib 的标准算法(例如 MD5 和 SHA),根据信息的内容生成签名,HMAC 用来验证信息在传送过程中没有被篡改。

一个通常使用的 hash 函数的例子是 md5(),这也是当前在很多不同语言和系统中比较流行的:

import hashlib

data = "Hello World"

h = hashlib.md5()
h.update(data)
print(h.hexdigest())
# b10a8db164e0754105b7a99be72e3fe5

为了计算一个数据块(这儿是 ASCII 字符串)的 MD5 哈希值或摘要,首先需要创建一个 hash 对象,然后添加数据,再调用 digest() 或者 hexdigest() 函数。本例使用的是 hexdigest() 方法来代替 digest() ,是因为为了更清晰地输出而对结果进行了格式化。如果你能接受输出二进制的摘要值,那么就用 digest()

使用 Hash 函数来存储密码

用户注册的过程通常是这样的:

  • 用户填写注册表单,包括密码这一项
  • web 脚本将所有的信息存储在数据库中
  • 然而,密码在存储之前需要通过 hash 函数进行转化
  • 最原始版本的密码并没有保存在任何地方,因此从技术上讲它消失了

用户登录的过程:

  • 用户输入用户名和密码
  • 脚本用同样的 hash 函数来转化密码
  • 脚本找到记录在数据库中的用户信息,读取保存 hash 之后的密码
  • 比较两者的值,如果匹配了就完成了登录

注意原始密码不会存储在任何地方!那么如果数据库被盗,那么用户的登录信息不会被盗,是吗?答案是“根据情况来定”。让我们看看一些潜在的问题:

问题1:Hash 冲突

当两个不同的输入数据产生相同的 Hash 结果时,这就发生了 Hash 冲突。发生的概率依赖于你所使用的函数。

如果利用呢?

作为例子,我使用一些老的脚本,它们使用 crc32() 来 Hash 密码。这个函数会产生 32 位整数的结果,这意味着仅仅有2^32 (i.e. 4,294,967,296) 种结果。

让我们来 hash 一个密码:

import binascii
result = binascii.crc32('supersecretpassword')
print(result) #323322056

现在,我们假设有人盗取了数据库,有了 hash 值。我们也许并不能将 323322056 转成 supersecretpassword,然而我们能用一个简单的脚本,来找到另一个密码可以转化为相同的 hash 值:

import binascii,base64

i = 0
while True:
    if binascii.crc32(base64.encodestring(bytes(i,))) == 323322056:
        print(base64.encodestring(i))
        i += 1

这可能需要运行好一会,但最终会返回一个字符串。我们可以用返回的字符串来代替 supersecretpassword,也同样能登录进入那个用户的账户。

举例来说,在我电脑运行那个脚本一会之后,得到字符串 MTIxMjY5MTAwNg==,让我们测试一下:

import binascii

print(binascii.crc32("supersecretpassword"))
#323322056

print(binascii.crc32("MTIxMjY5MTAwNg=="))
#323322056

如何避免呢?

现在,一个强大的家庭 PC 机就可以用来每秒钟运行一个哈希函数十亿次之多,那么我们需要一个产生非常大范围数的哈希函数。

举例来说,md5() 可能就比较适合,因为它产生 128 位哈希值,也就是 340,282,366,920,938,463,463,374,607,431,768,211,456 可能的结果。通过遍历找到冲突不可能的,然而有些人仍然这样做(参考这里)。

Sha1

Sha1() 是一个更好的替代方案,它会产生甚至长达 160 位的 hash 值。

问题2:彩虹表

甚至我们解决了冲突的问题,我们还不能确保安全。

彩虹表(rainbow table)是通过计算一些常用的单词和它们的组合的 hash 值而创建的。这些表有多达上百万或上亿项。

举例来说,你可以遍历一个字典,为每个单词产生一个 hash 值。你也可以将它们进行组合,也为组合的单词产生 hash 值。这还没完,你甚至可以以数字插入单词的开始、结尾、中间,将它们也存入表中。

考虑到现在存储系统非常廉价,可以产生和使用上 G 量级的彩虹表。

如何利用呢?

让我们想象一下,一个大的数据库被盗,里面有一千万的密码哈希值。在彩虹表中搜索与数据库中密码哈希值的匹配是件相当简单的事,不是所有密码都能找到,但也不是都找不到!它们中的一些肯定可以找到!

如何避免呢?

我们尝试添加 “salt” 来解决,下面是个例子:

import hashlib

password = "EasyPassword"

print(hashlib.sha1(password).hexdigest())
# ff166c2477f864d609ca8111680bfa387eb4e509

salt = "f#@V)Hu^%Hgfds"

print(hashlib.sha1(salt + password).hexdigest())
# 3e7edaceb96becaf69ae7e73073812ea136188e2

我们做的很简单,在 hash 密码之前将“盐化”字符串与用户密码连接,这样很显然 hash 的结果和之前建立的彩虹表没有一个匹配。但是,我们还不够安全!

问题3:彩虹表问题(续)

记住,在数据库被盗之后,还可以重新开始构建彩虹表。

如何利用呢?

即使使用了盐化技术,仍然有可能随着数据库被盗而破解。他们所要做的是重新产生彩虹表,但这次他们会连接盐化字符串到每个密码上。

举例来说,通常彩虹表中 easypassword 可能存在,但在新的彩虹表中,也存在 f#@V)Hu^%Hgfdseasypassword 这样的密码。当他们将上千万条盗来的经过 salted 的哈希值与这张新彩虹表比较时,他们也会能找到一些相同的匹配。

如何避免呢?

我们使用唯一的盐化字符串替代,每个用户都不一样。

一种备选盐化字符串是从数据库中取得用户的 ID:

hashlib.sha1(userid + password).hexdigest()

基于的假设是用户的 ID 号永远不会改变,一般这都是成立的。

我们也可以为每个用户产生一个随机字符串,把它作为这个唯一的 salt。但是我们需要保证要将这个唯一的盐化字符串保存在用户记录的某个位置。

import hashlib, os

def unique_salt():
    return hashlib.sha1(os.urandom(10)).hexdigest()[:22]

salt = unique_salt()
password = "" # str or int
hash = hashlib.sha1(salt + str(password)).hexdigest()
print(hash)
# 37dec03d2761122819f8708e6d5c8392ee02b40d

这种方法有效预防了使用彩虹表破解,因为现在每一个密码都经过不同的值盐化过,攻击者需要生成一千万个独立的彩虹表,这实际上是不现实的。

问题4:Hash 速度

大多 Hash 函数在设计时都注重速度,因为它们常用于计算大数据集和文件的 checksum 值,来检查数据的完整性。

如何利用呢?

就像我之前提到的,一个现代 PC 机带有强大的 GPU(或者显卡)可以完成每秒钟上千次的 hash 运算。这种方法,他们可以使用暴力攻击法,尝试每个可能的密码。

你可能认为需要不少于 8 位长度的密码可能避免暴力破解,让我们看下面的分析来决定是否真的能避免:

  • 如果密码包含小写字母、大写字母以及数字,也就是有 62 (26+26+10) 种可能的字符。
  • 一个 8 位长的字符串就有 62^8 可能的组合,比 218 万亿略小一点。
  • 以每秒十亿的 hash 速率,大约在 60 个小时内就可以破解。

如果是 6 位长的密码,这也相当普遍,只需要在 1 分钟之内就可以破解。

如果要求 9 位或 10 位长度的密码,这样就会让你的用户体验非常不好。

如何避免呢?

使用一个低速的 hash 函数。

假设你是用一个 hash 函数,在同样的硬件下,每秒钟只能进行 100 万次 hash 运算,而不是 10 亿次,暴力破解将会花费比以前多出 1000 倍的时间。那么 60 个小时将会变成将近 7 年!

一种你可以实现的方法是:

import hashlib

def my_hash(password, salt):
    hash = hashlib.sha1(salt + password).hexdigest()

    for i in range(1000):
        hash = hashlib.sha1(hash).hexdigest()
    return hash

print(my_hash("12345", "f#@V)Hu^%Hgfds"))

或者,你可以使用支持 “开销参数”(例如 BLOWFISH)的算法。在 Python 中,可以利用 py-crypt 库。

import bcrypt

def my_hash(password):
    return bcrypt.hashpw(password, bcrypt.gensalt(10))

print(my_hash("atdk"))
#$2a$10$WNhGOdVhoZrrKgwxGa2VIuzfAvm9oFWZF9PIVtLIoU5LQOVGLuLrq

注意输出:

  1. 第一个值是 $2a,表明我们使用的是 BLOWFISH 算法。
  2. 这种情形下第二个值是 $10,是“开销参数”。是执行迭代的次数以 2 为对数的结果,它将会迭代(10 => 2^10 = 1024) 次。这个数值可以在 4 到 31 范围内变化。

让我们运行例子:

import bcrypt, os, hashlib

def my_hash(password, unique_salt):
    return bcrypt.hashpw(password, bcrypt.gensalt(10) + unique_salt)

def unique_salt():
    return hashlib.sha1(os.urandom(10)).hexdigest()[:22]

password = "verysecret"

print(my_hash(password, unique_salt()))
# $2a$10$aHx0q.FE/tGvGWzlm6yePemYx9SAsBP2iSiy/uFx7pyjpy980Hita

结果包括算法($2a),开销参数($10),使用的 22 位 salt,剩下的是计算的 hash 值。让我们测试一下:

import bcrypt, os, hashlib

# assume this was pulled from the database
hash = "$2a$10$6XDaX/3kNby0jI9Ih/Re7.478DOMZK9OnA2mTxKUP0My.39N.jdky"

# assume this is the password the user entered to log back in
password = "verysecret"

def check_password(hash, password):
    salt = hash[:29]
    new_hash = bcrypt.hashpw(password, salt)
    return hash == new_hash

if check_password(hash, password):
    print("Access Granted")
else:
    print("Access Denied")

当我们运行时,我们看到输出 "Access Granted!"。

整合所有的问题

如果考虑到上面的所有问题,根据我们目前所学的,写一个实用类:

import bcrypt, os, hashlib

class PassHash():
    def unique_salt(self):
        return hashlib.sha1(os.urandom(10)).hexdigest()[:22]

    def hash(self, password):
        return bcrypt.hashpw(password, bcrypt.gensalt(10) + self.unique_salt())

    def check_password(self, hash, password):
        full_salt = hash[:29]
        new_hash = bcrypt.hashpw(password, full_salt)
        return hash == new_hash

obj = PassHash()

a = obj.hash("12345")
print(a) # $2a$10$gBSbmXKanQJOTSabtX4wfOE2RT2mKDFbCY6r7cqCJSk2YPGjIDrou

b = obj.check_password(a, "12345")
print(b) # True

现在,我们可以在我们的表单中使用该类来 hash 我们密码,确保安全性。

结论

这种 hash 密码的方法对大多 web 应用已经足够了。别忘记你还可以要求你的用户使用更强的密码,通过强制最小密码长度,组合字符、数字和特殊字符等方法。


国密哈希hash算法SM3原理分析 无限制512bit32bit4128bit。 阅读详情

相关推荐

散列函数的安全与应用:自问自答详解

本文深入探讨了散列技术及其在信息安全中的应用。首先介绍了散列函数的基本概念和常见算法(如MD5、SHA-1、SHA-2、SHA-3)的特点,分析了它们在不同场景下的适用性。接着,讨论了散列函数的安全性问题,重点阐述了如何防止散列冲突、哈希碰撞和彩虹表攻击等常见风险,并提出了加盐和使用安全算法等防护措施。此外,本文还介绍了散列技术在密码存储、数据完整性校验、数字签名等方面的实际应用,并讨论了如何选择合适的散列算法及参数。最后,文章总结了散列技术在现代信息安全系统中的重要性,并强调了在安全性与效率之间的平衡。

曾经“等你生日那天”都遥远得像未来,如今却可欢愉的挥手说“下个十年见” 5万+

Hash 函数及其重要性

不时会爆出网站的服务器和数据库被盗取,考虑到这点,就要确保用户一些敏感数据(例如密码)的安全性。今天,我们要学的是 hash 背后的基础知识,以及如何用它来保护你的 web 应用的密码。 申明 密码学是非常复杂的一门学科,我不是这方面的专家,在很多大学和安全机构,在这个领域都有长期的研究。 本文我试图使事情简单化,呈现给大家的是一个 web 应用中安全存储密码的合理方法。

海风林影 7599

深入解析Panfrost驱动:Mali-GPU开源驱动的架构与实现

本文深入解析了Panfrost驱动作为Mali-GPU开源驱动的架构与实现。从诞生背景到硬件架构演进,详细介绍了Panfrost如何突破ARM闭源策略,实现全栈支持。文章还探讨了驱动设计中的关键技术挑战与性能优化策略,为开发者提供了实用的编译部署指南和性能调优方法。

weixin_30790841的博客 528

哈希表计算机领域应用,Hash算法在信息安全领域中的应用

内容介绍Hash算法在信息安全领域中的应用摘要:Hash算法在信息安全领域中具有加密、数字签名、文件鉴别等用途;其算法是将任意长度的输入变换成固定长度的输出,常见的算法有MD5、Sha1等;可以利用生日攻击法等对其进行攻击。关键词:Hash;算法;应用;攻击中图分类号: 文献标识码:A 文章编号:Simply analyes the application of Hash algorithmin...

weixin_35854529的博客 1001

Python3中digest()hexdigest()的区别

一、介绍 Python3中可以使用 hashlib库 实现加密,hashlib提供了多个不同的加密算法接口,如SHA1、SHA224、SHA256、SHA384、SHA512、MD5等 二、digest()hexdigest()区别 hash.digest() :返回摘要,作为二进制数据字符串值 hash.hexdigest():返回摘要,作为十六进制数据字符串值 import hashlib md5 = hashlib.md5() md5.update("a".encode('utf-8')) i

sinat_34241861的博客 1万+

密码为什么要用Hash值储存?了解Hash算法原理

Hash算法(哈希算法,Hash算式,散列算法,消息摘要算法)将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的,所以数据的哈希值可以检验数据的完整性。一般用于快速查找和加密算法。简单来说就是,哈希(Hash)算法,即散列函数。...

m0_69916115的博客 2845

python中将hexdigest函数返回的值变成digest函数返回的值

hexdigest实际上返回的是16进制的str形式,digest返回的是bytes,用人眼看起来相等。 例如: h = hashlib.new(‘ripemd160’) h.update(b’12345’) h.hexdigest() ‘e9cbd2ea8015a084ce9cf83a3c65b51f8fa10a39’ ...

DerekRay的博客 5005

Hash函数及其重要性

不时会爆出网站的服务器和数据库被盗取,考虑到这点,就要确保用户一些敏感数据(例如密码)的安全性。今天,我们要学的是 hash 背后的基础知识,以及如何用它来保护你的 web 应用的密码。 申明 密码学是非常复杂的一门学科,我不是这方面的专家,在很多大学和安全机构,在这个领域都有长期的研究。 本文我试图使事情简单化,呈现给大家的是一个 web 应用中安全存储密码的合理方法。 “Hashing” 做...

Iversonx的博客 3741

(转) hash 函数及其重要性

hash 函数及其重要性不时会爆出网站的服务器和数据库被盗取,考虑到这点,就要确保用户一些敏感数据(例如密码)的安全性。今天,我们要学的是 hash 背后的基础知识,以及如何用它来保护你的 web 应用的密码。

代码过客 786

哈希算法

转自:http://clevertanglei900126.wap.blog.163.com/w2/blogDetail.do?blogId=fks_095075081082083068081082085095085087080064081084084070093&p=1&hostID=clevertanglei900@126 哈希(Hash)算法就是单向散列算法,它把某个较大的

Busquets5的博客 720

​​​【收录 Hello 算法】6.3 哈希算法

不难发现,以上介绍的简单哈希算法都比较“脆弱”,远远没有达到哈希算法的设计目标。例如,由于加法和异或满足交换律,因此加法哈希和异或哈希无法区分内容相同但顺序不同的字符串,这可能会加剧哈希冲突,并引起一些安全问题。总而言之,我们通常选取质数作为模数,并且这个质数最好足够大,以尽可能消除周期性模式,提升哈希算法的稳健性。,当列表的内容发生变化时,它的哈希值也随之改变,我们就无法在哈希表中查询到原先的。然而该哈希算法过于简单,所有后两位相等的。,即使对象的内容发生了变化,但它的内存地址不变,哈希值仍然是不变的。

让学习成为一种习惯 893

哈希算法(上):如何防止数据库中的用户信息被脱库?

哈希算法(上):如何防止数据库中的用户信息被脱库? 如何存储用户密码重要的数据?仅仅MD5加密一下存储就够了吗? 什么是哈希算法? 将任意长度的二进制值串映射为固定长度的二进制值串,这个映射的规则就是哈希算法,通过原始数据映射之后得到的二进制值串就是哈希值 为了设计一个优秀的哈希算法并不容易,需要满足几点要求: 从哈希值不能反向推导出原始数据,即单向哈希算法 对输入数据敏感,哪怕只修改一个Bit...

ywangjiyl的博客 255

哈希表(Java实现)

哈希表(Java实现)

WYSCODER的博客 7920

python 哈希(hash

python 哈希(hash) 散列表(Hash table)–哈希表 基于高度存取 ,一种典型的“空间换时间” 可以理解为一个线性表,其中元素不是紧密排列,可能存在空隙 散列表,依据关键码值(key value)而进行访问的数据结构,即,它通过把关键码值 映射到表中一个位置来访问记录,以加快查找速度,这个映射函数叫做散列函数, 存放记录的数组叫做散列表 eg 记录的存储位置 = f(关键字) 这...

gly的博客 8637

hash思想的理解以及hash表的实现

新的问题总是导致了新的理论和新技术的诞生,其中在计算机领域的有一件事情却不得不提出来让我们讨论一下,那边是java编程语言中的一种数据结构hash集合。java中hash集合最常用的有三种,分别是:hashSet,hashMap及其hashTable。 ---[url=http://www.qinaide520.com]中国大姨夫[/url] 一:哈希算法诞生的背景 ...

nanxia1999的专栏 366

c++如何设置相互独立的哈希函数

https://segmentfault.com/a/1190000016221926https://blog.csdn.net/dawancha2020/article/details/123606164

sidneyyy的博客 277

hash hashcode变化_HashHashCode深入理解

目录介绍1.Hash的作用介绍1.1 Hash的定义1.2 Hash函数特性1.3 Hash的使用场景2.如何判断两个对象相等2.1 判断两个字符串2.2 判断两个int数值2.3 其他基本类型3.HashCode深入分析3.0 HashCode是什么3.1 为什么要重写HashCode3.2 HashCode源代码分析3.3 HashCode带来的疑问3.4 HashCode的作用3.5 Has...

weixin_28748867的博客 313
上一篇: 怎样判断自己掌握了学到的新知识
下一篇: 什么是 Web 框架?
tham_
博客等级 码龄12年 233粉丝 105原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值