常用内置函数二

一、什么是hash

hash算法又称摘要算法,是一种单向的函数,把任意长度的数据(data)转换为一个长度固定的数据串(摘要digest),这个过程是不可逆的,而且只要输入发生改变,哪怕一个bit,输出的hash值也会有很大不同,这种特性刚好合适用来保存密码,因为我们希望使用一种不可逆的算法来加密保存的密码,同时又需要在用户登录的时候验证密码是否正确。

二、python的hashlib提供了常见的摘要算法,如md5,sha1等

1、计算出一个字符串的md5值
import hashlib
md5 = hashlib.md5()
md5.update('you know nothing,Jon Snow.'.encode('utf8'))
print(md5.hexdigest())

# 输出:
# 2e49854de2b90bcfd1e40d2454a2f69b

如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的:

md5.update('you know nothing,').encode('utf8')
md5.update('Jon Snow.').encode('utf8')

MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。

2、计算一个字符串的sha1值
sha1 = hashlib.sha1()
sha1.update('you know nothing,'.encode('utf8'))
sha1.update('Jon Snow.'.encode('utf-8'))
print(sha1.hexdigest())

# 输出:
# 32a3aed239ab89dbe162b32db5d0690f2fe7574a
# SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。

三、摘要算法应用

任何允许用户登录的网站都会存储用户登录的用户名和口令。如何存储用户名和口令呢?方法是存到数据库表中:

name password
michael 123456
bob abc999
alice alice2008

如果以明文保存用户口令,如果数据库泄露,所有用户的口令就落入黑客的手里。此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的口令。所以正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要,比如MD5:

username password
michael :e10adc3949ba59abbe56e057f20f883e
bob 99b1c2188db85afee403b1536010c2c9
alice 99b1c2188db85afee403b1536010c2c9

当用户登录时,首先计算用户输入的明文口令的MD5,然后和数据库存储的MD5对比,如果一致,说明口令输入正确,如果不一致,口令肯定错误。

四、练习

1、根据用户输入的口令,计算出存储在数据库中的MD5口令:
def calc_md5(password):
    md5 = hashlib.md5()
    md5.update(password.encode('utf8'))
    digest5 = md5.hexdigest()
    return digest5
2、设计一个验证用户登录的函数,根据用户输入的口令是否正确,返回True或False:
#假如这是保存在数据库的数据,并且口令是用md5计算后的摘要
db = {
    'michael':'e10adc3949ba59abbe56e057f20f883e',
    'bob':'878ef96e86145580c38c87f0410ad153',
    'alice':'99b1c2188db85afee403b1536010c2c9'
}

def login(user, password):
    if calc_md5(password) == db[user]:
        return True
    else:
        return False

# 测试:
assert login('michael', '123456')
assert login('bob', 'abc999')
assert login('alice', 'alice2008')
assert not login('michael', '1234567')
assert not login('bob', '123456')
assert not login('alice', 'Alice2008')
print('ok')

#如果你没指定要加密的字符串的字符编码会报错
# TypeError: Unicode-objects must be encoded before hashing
# 输出:
# ok

五、’加盐‘

采用md5并不一定安全,如果有些用户喜欢设置123456,8888888这些简单的口令,黑客就可以先计算出这些常用口令的md5值,然后对比数据库的md5,就可以获取常用口令的用户账号了所以一般网站都要求用户什么数字加字母做密码不少于8位啥的,但是我们也可以在程序上设计对简单口令的加强保护方法就是对原始口令加一个复杂字符串来实现,俗称’加盐‘,只要这个盐黑客不知道他就无法获取用户的信息。
还有一种需要处理的情况,如果有两个用户都使用了相同的口令,在数据库中,将存储两条相同的md5,这说明这两个用户的口令是一样的。有没有办法让使用相同口令的用户存储不同的MD5呢?如果假定用户无法修改登录名,就可以通过把登录名作为Salt的一部分来计算MD5,从而实现相同口令的用户也存储不同的MD5。

六、练习

根据用户输入的登录名和口令模拟用户注册,计算更安全的MD5:
然后根据修改后的MD5算法实现用户登录的验证
import hashlib, random

db = {}

def get_md5(s):
    return hashlib.md5(s.encode('utf-8')).hexdigest()

class User(object):
    def __init__(self, username, password):
        self.username = username
        self.salt = ''.join([chr(random.randint(48,122)) for i in range(20)])
        #random,randint(48,122)
        #随机生成大于等于48小于等于122的整数
        #chr(random,randint(48,122))
        #生成对应随机数的ascii码对应的字符
        # [chr(random,randint(48,122)) for i in range(20)]
        #列表生成器,其中有20个元素,每个元素字符都是ascii码48到122之间的随机字符
        # ''.join([chr(random.randint(48,122)) for i in range(20)])
        #返回一个以分隔符''连接各个元素后生成的字符串
        self.password = get_md5(password + self.salt)


def register(username, password):
    db[username] = User(username, password)


def login(username, password):
    if username in db:
        user = db[username]
        return user.password == get_md5(password + user.salt)
    else:
        return False

register('michael', '123456')
register('bob', 'abc999')
register('alice', 'alice2008')

# 测试:
assert login('michael', '123456')
assert login('bob', 'abc999')
assert login('alice', 'alice2008')
assert not login('michael', '1234567')
assert not login('bob', '123456')
assert not login('alice', 'Alice2008')
print('ok')

# 输出:
# ok

七、hmac

如果salt是我们自己随机生成的,通常我们计算MD5时采用md5(message + salt)。但实际上,把salt看做一个“口令”,加salt的哈希就是:计算一段message的哈希时,根据不通口令计算出不同的哈希。要验证哈希值,必须同时提供正确的口令。这实际上就是Hmac算法:Keyed-Hashing for Message Authentication。它通过一个标准算法,在计算哈希的过程中,把key混入计算过程中。和我们自定义的加salt算法不同,Hmac算法针对所有哈希算法都通用,无论是MD5还是SHA-1。采用Hmac替代我们自己的salt算法,可以使程序算法更标准化,也更安全。

八、Python自带的hmac模块实现了标准的Hmac算法。

我们来看看如何使用hmac实现带key的哈希。我们首先需要准备待计算的原始消息message,随机key,哈希算法,这里采用MD5,使用hmac的代码如下:

import hmac
message = b'Hello, world!'
key = b'secret'
h = hmac.new(key, message, digestmod='MD5')
# 如果消息很长,可以多次调用h.update(msg)
print(h.hexdigest())
#输出:
# fa4ee7d173f2d97ee79022d1a7355bcf

可见使用hmac和普通hash算法非常类似。hmac输出的长度和原始哈希算法的长度一致。需要注意传入的key和message都是bytes类型,str类型需要首先编码为bytes。

九、练习

将上一节的salt改为标准的hmac算法,验证用户口令:

import hmac, random

db = {}

def hmac_md5(key, s):
    return hmac.new(key.encode('utf8'), s.encode('utf8'), 'md5').hexdigest()
    # return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'MD5').hexdigest()

class User(object):
    def __init__(self, username, password):
        self.username = username
        self.key = ''.join([chr(random.randint(48,122)) for i in range(20)])
        self.password = hmac_md5(self.key, password)


def register(username, password):
    db[username] = User(username, password)


def login(username, password):
    user = db[username]
    return user.password == hmac_md5(user.key, password)


register('michael', '123456')
register('bob', 'abc999')
register('alice', 'alice2008')

# 测试:
assert login('michael', '123456')
assert login('bob', 'abc999')
assert login('alice', 'alice2008')
assert not login('michael', '1234567')
assert not login('bob', '123456')
assert not login('alice', 'Alice2008')
print('ok')


# 输出:
# C:\Users\ljs\Desktop>python use_hmac.py
# ok

最后附上一首歌,春娇与志明。

你可能感兴趣的:(常用内置函数二)