Python之路(第十九篇)hashlib模块

一、hashlib模块

 

HASH

Hash,一般翻译做“散列”,也有直接音译为”哈希”的,就是把任意长度的输入(又叫做预映射,pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。

简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

HASH主要用于信息安全领域中加密算法,他把一些不同长度的信息转化成杂乱的128位的编码里,叫做HASH值.

 

Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等。

什么是摘要算法呢?摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。

摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。

摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。

 

hash值的特点是:

  • 只要传入的内容一样,得到的hash值必然一样:要用明文传输密码文件完整性校验

  • 不能由hash值返解成内容:把密码做成hash值,不应该在网络传输明文密码

  • 只要使用的hash算法不变,无论校验的内容有多大,得到的hash值长度是固定的

 

 

MD5(消息摘要算法第五版)

什么是MD5算法

MD5讯息摘要演算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码杂凑函数,可以产生出一个128位的散列值(hash value),用于确保信息传输完整一致。MD5的前身有MD2、MD3和MD4。

 

MD5功能

输入任意长度的信息,经过处理,输出为128位的信息(数字指纹);不同的输入得到的不同的结果(唯一性);

 

MD5算法的特点

  1. 压缩性:任意长度的数据,算出的MD5值的长度都是固定的

  2. 容易计算:从原数据计算出MD5值很容易

  3. 抗修改性:对原数据进行任何改动,修改一个字节生成的MD5值区别也会很大

  4. 强抗碰撞:已知原数据和MD5,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。

 

MD5算法是否可逆?

MD5不可逆的原因是其是一种散列函数,使用的是hash算法,在计算过程中原文的部分信息是丢失了的。

 

MD5用途

  1. 防止被篡改:

  2. 防止直接看到明文:

  3. 防止抵赖(数字签名):

 

SHA-1(安全哈希算法)

安全哈希算法(Secure Hash Algorithm)主要适用于数字签名标准(Digital Signature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA)。对于长度小于2^64位的消息,SHA1会产生一个160位的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。

SHA是美国国家安全局设计的,由美国国家标准和技术研究院发布的一系列密码散列函数。

科学家们又推出了SHA224, SHA256, SHA384, SHA512,当然位数越长,破解难度越大,但同时生成加密的消息摘要所耗时间也更长。目前最流行的是加密算法是SHA-256 .

 

通常应用

  1. 密码加密(很常用的一种用法)

    将用户的密码或者账户名密码转为密文进行存储(密文是加了密的的文字,明文是加密之前的文字。),防止用户资料信息被泄露带来各种不安全的情况

  2. 文件校验

在网上下载大尺寸文件的时候常见到网站同时会提供这个文件的MD5的值,它的作用是用户下载后可以在下载文件基础上计算MD5的值,如果和网站提供的MD5是相同的说明文件在下载过程中没有损坏或者说文件没有被恶意网站修改。

         3.工作量证明(Proof ofWork)

         详见https://www.zhihu.com/question/22369364/answer/23600737

 

 

MD5算法例子

import hashlib
  ​
  m = hashlib.md5()        #实例化创建加密对象
  m.update(b"nicholas")    #传入待加密的字符串,注意要转换为bytes二进制形式,以下两种写法也是可以的
  # m.update("nicholas".encode("utf-8"))
  # m.update(bytes("nicholas",encoding="utf-8"))
  ​
  v1 = m.hexdigest()
  print(type(v1))
  print(v1)    #返回产生的十六进制的字符串类型数值
  #输出结果532ab4d2bbcc461398d494905db10c95
  """注意这里是十六进制的数,共32个数,由于一位十六进制的数要用4位二进制的数来表示
  这里是产生了32*4=128位二进制数,所以我们经常听到MD5是128位的校验,这里的128位是代表128位的二进制数
  """
  ​
  v2 = m.digest()
  print(v2)          #返回二进制形式的数值,可以转换为十六进制形式,与hexdigest结果一致
  print(type(v2))
  import binascii   #这里为了方便,直接在这里导入binascii ,将二进制转为十六进制
  print(binascii.b2a_hex(v2))  #b'532ab4d2bbcc461398d494905db10c95'

  

输出结果

  
  <class 'str'>
  532ab4d2bbcc461398d494905db10c95
  b'S*\xb4\xd2\xbb\xccF\x13\x98\xd4\x94\x90]\xb1\x0c\x95'
  <class 'bytes'>
  b'532ab4d2bbcc461398d494905db10c95'

  

分析:可以看到digest()和hexdigest()产生的数值其实是一样的,只不过一个是作为二进制数据字符串值(bytes类型),一个是十六进制数据字符串值(字符串类型)。

 

 

注意:如果m.update(a)之后再次 m.update(b),那么就相当于m.update(a+b)

例子

  
  import hashlib
  ​
  m1 = hashlib.md5()        #实例化创建加密对象
  m1.update(b"nicholas")    #传入待加密的字符串
  m1.update(b"123")
  print(m1.hexdigest())     #12962a329e66f22a0dfbeec2f2498d87
  ​
  m2 = hashlib.md5()
  m2.update(b"nicholas123")
  print(m2.hexdigest())     #12962a329e66f22a0dfbeec2f2498d87

  

输出结果

  
  12962a329e66f22a0dfbeec2f2498d87
  12962a329e66f22a0dfbeec2f2498d87

  


分析:可以看到m1传入2次bytes之后产生的结果和m2结果一样的。这种特性表示如果处理同一个大量数据可以分块传入,与一次性传入处理结果是一样的。

 

SHA算法例子

例子

  
  import hashlib
  ​
  m1 = hashlib.sha1()        #实例化创建加密对象
  m1.update(b"nicholas")    #传入待加密的字符串
  m1.update(b"123")
  print(m1.hexdigest())     
  ​
  m2 = hashlib.sha1()
  m2.update(b"nicholas123")
  print(m2.hexdigest())     

  

输出结果

  
  ef3f18a2b33e1f5366c25161a4869707503225f2
  ef3f18a2b33e1f5366c25161a4869707503225f2

  

分析;可以看到,SHA算法的用法和MD5用法类似,也有分批次传入和一次性传入结果一致的特性。

 

加盐

上述各种算法比明文存储密码确实要安全不少。但在有些场景中,用户通常会将密码设置的尤为简单。这样如果数据库泄露,黑客可以通过简单的密码尝试来完成对加密字串的匹配。即:通过撞库可以反解。为了解决这种方法,我们通常需要对密码做“加盐”处理,即有必要对加密算法中添加自定义key再来做加密。

所谓加盐就是在m = hashlib.md5()这里设置参数,如果没有参数,所以md5遵守一个规则,生成同一个对应关系,如果加了参数,就是在原先加密的基础上再加密一层,这样的话参数只有自己知道,防止被撞库,因为别人永远拿不到这个参数。

 

例子


 import hashlib
  ​
  m1 = hashlib.md5()        #md5对象,md5不能反解,但是加密是固定的,就是关系是一一对应,所以有缺陷,可以被对撞出来
  m1.update(b"nicholas")    #传入待加密的字符串
  print(m1.hexdigest())
  # 输出结果  532ab4d2bbcc461398d494905db10c95
  """这个结果可以别人也可以直接对简单字符串的进行MD5取值生成一个数据库,拿数据库中的MD5值与这个MD5值进行比对,
  一致则反向找到了原字符串,因此现在网站注册用户一般会提示密码加上字母、数字、特殊字符多种组合
  """
  ​
  m2 = hashlib.md5(b"salt")   #这里加盐设置的参数自己确定,如果没有参数,所以md5遵守一个规则,
  # 生成同一个对应关系,如果加了参数, 就是在原先加密的基础上再加密一层,别人不知道这个对应关系,就加大了撞库破解的难度。
  m2.update(b"nicholas")
  print(m2.hexdigest())
  # 输出结果317bf09f6da0f56f86b896fac6663443

  

tips:sha加盐用法与md5类似。

 

摘要算法应用场景

 

(1)大文件md5校验

  
  import hashlib
  def  file_md5(filename):
      md5_value = hashlib.md5()
      with open(filename, 'rb') as f:
          while True:
              data = f.read(2048) # 每次读取2048个字节数据
              if not data:
                  break
              md5_value.update(data)# 计算md5值
      return md5_value.hexdigest()
  ​
  file = input("请输入文件完整路径:")
  v = file_md5(file)
  print(v)

  

输出结果


 32a9d0ad680bcdcb712ec802be9971be

  

 

 

(2)网站用户注册登录

  
  import hashlib
  import json
  import os
  ​
  ​
  def handle_md5(msg):
      "对关键信息进行MD5处理"
      m = hashlib.md5(bytes("salt",encoding="utf-8"))  #加盐
      m.update(bytes(msg,encoding="utf-8"))
      res = m.hexdigest()
      return res
  ​
  ​
  def write_data(filename,data):
      with open(filename,"a+",encoding="utf-8") as f:
          json.dump(data,f)
  ​
  ​
  def read_data(filename):
      with open(filename,"r",encoding="utf-8") as f:
          data = json.load(f)
      return data
  ​
  def login():
      #用户登录模块
      username = input("请输入账户名:").strip()
      userpasswd = input("请输入密码:").strip()
      username_file = "%s.json"%username
      start_file_path = os.path.dirname(os.path.abspath(__file__))
      username_file_path = os.path.join(start_file_path,username_file)
      if os.path.exists(username_file_path):   #判断用户信息文件是否存在
          data = read_data(username_file_path)
          account = data["name"]
          passwd_md5 = data["passwd"]
          userpasswd_md5 = handle_md5(userpasswd)
          if username == account and userpasswd_md5 == passwd_md5:
              print("恭喜%s,登录成功!"%username)
          else:
              print("账号或者密码错误,请重新登录。")
          return "ok"
      else:
          print("账户不存在,请注册!")
          return "no"
  ​
  ​
  def register():
      #用户注册模块
      username = input("请输入要注册的用户名:").strip()
      userpasswd = input("请输入要注册的密码:").strip()
      user_filename = "%s.json"%username
      user_passwd_md5 = handle_md5(userpasswd)
      user_data ={"name":username,"passwd":user_passwd_md5}
      write_data(user_filename,user_data)
      print("注册成功")
  ​
  ​
  def main():
      print("欢迎登录XX")
      while True:
          res = login()
          if res == "no":
              register()
          elif res == "ok":
              print("进入了XX网站")
              break
  ​
  ​
  if __name__ == "__main__":
      main()
 

  

posted on 2018-05-29 22:46  Nicholas--  阅读(1294)  评论(0编辑  收藏  举报

导航