模組學習之hashlib模組

Ligo6發表於2024-05-09

【一】什麼是摘要演算法

  • Python的hashlib提供了常見的摘要演算法,如MD5、SHA1等等
  • 摘要演算法又稱雜湊演算法、雜湊演算法
  • 它透過一個函式,把任意長度的資料轉換為一個長度固定的資料串(通常用16進位制的字串表示)
  • 摘要演算法就是透過摘要函式f()對任意長度的資料data計算出固定長度的摘要digest
    • 目的是為了發現原始資料是否被人篡改過
  • 摘要演算法之所以能指出資料是否被篡改過,就是因為摘要函式是一個單向函式
    • 計算f(data)很容易,但透過digest反推data卻非常困難
    • 而且,對原始資料做一個bit的修改,都會導致計算出的摘要完全不同

【二】摘要演算法(md5)

【1】加密資料

(1)一次性加密(同一段資料)

# 計算出一個字串的MD5值
import hashlib

md5 = hashlib.md5()
md5.update(b'how to use md5 in python hashlib?')
print(md5.hexdigest())

# 計算結果如下:
# d26a53750bc40b38b65a520292f69306

(2)分次加密(同一段資料)

# 資料量很大的情況下,分塊多次呼叫update()
import hashlib

md5 = hashlib.md5()
md5.update(b'how to use md5 in ')
md5.update(b'python hashlib?')
print(md5.hexdigest())

# 計算結果如下:
# d26a53750bc40b38b65a520292f69306

# md5結果是固定的128 bit位元組,通常用一個32位的16進位制字串表示

【2】獲取加密資料

# md5加密模組
from hashlib import md5
# 如果需要十六進位制的結果與二進位制的結果之間的轉換,需要的模組
import binascii

# 【1】準備資料

# 這裡是字串型別
data = '你好'

# 字串轉二進位制資料方式一
encode_data = data.encode()
# 字串轉二進位制資料方式二
# encode_data = b'你好'

# 【2】資料加密
# 構建md5物件
md5_obj = md5()
# 將資料更新到md5演算法中進行資料加密 (引數為二進位制資料的明文資料)
# (方法一):直接在加密演算法中進行轉碼
# md5_obj.update("你好".encode("utf-8"))
md5_obj.update(data.encode("utf-8"))
# (方法二):先將明文資料進行轉碼,再傳入到加密演算法中
# md5_obj.update(encode_data)

# 【3】資料提取
# 拿到加密字串 # 十六進位制的結果
result_16 = md5_obj.hexdigest()
print(result_16)
# 7eca689f0d3389d9dea66ae112e5cfd7

# 拿到加密字串 # 二進位制的結果
result_2 = md5_obj.digest()
print(result_2)
# b'~\xcah\x9f\r3\x89\xd9\xde\xa6j\xe1\x12\xe5\xcf\xd7'

# 拿到加密字串 # 十六進位制的結果與二進位制的結果之間的轉換 (引數為result_16 或 result_2)
result_change = binascii.unhexlify(result_16)
print(result_change)
# b'~\xcah\x9f\r3\x89\xd9\xde\xa6j\xe1\x12\xe5\xcf\xd7'

【三】摘要演算法升級之加鹽

  • 任何允許使用者登入的網站都會儲存使用者登入的使用者名稱和口令。
  • 如何儲存使用者名稱和口令呢?
    • 方法是存到資料庫表中:
name    | password
--------+----------
michael | 123456
bob     | abc999
alice   | alice2008
  • 如果以明文儲存使用者口令,如果資料庫洩露,所有使用者的口令就落入駭客的手裡。
  • 此外,網站運維人員是可以訪問資料庫的,也就是能獲取到所有使用者的口令。
  • 正確的儲存口令的方式是不儲存使用者的明文口令,而是儲存使用者口令的摘要,比如MD5:
username | password
---------+---------------------------------
michael  | e10adc3949ba59abbe56e057f20f883e
bob      | 878ef96e86145580c38c87f0410ad153
alice    | 99b1c2188db85afee403b1536010c2c9
  • 考慮這麼個情況,很多使用者喜歡用123456,888888,password這些簡單的口令
  • 於是,駭客可以事先計算出這些常用口令的MD5值,得到一個反推表:
'e10adc3949ba59abbe56e057f20f883e': '123456'
'21218cca77804d2ba1922c33e0151105': '888888'
'5f4dcc3b5aa765d61d8327deb882cf99': 'password'
  • 這樣,無需破解,只需要對比資料庫的MD5,駭客就獲得了使用常用口令的使用者賬號。
  • 對於使用者來講,當然不要使用過於簡單的口令。
    • 但是,我們能否在程式設計上對簡單口令加強保護呢?
  • 由於常用口令的MD5值很容易被計算出來
    • 所以,要確儲存儲的使用者口令不是那些已經被計算出來的常用口令的MD5
    • 這一方法透過對原始口令加一個複雜字串來實現,俗稱“加鹽”:
hashlib.md5("salt".encode("utf8"))
  • 經過Salt處理的MD5口令,只要Salt不被駭客知道,即使使用者輸入簡單口令,也很難透過MD5反推明文口令。
  • 但是如果有兩個使用者都使用了相同的簡單口令比如123456,在資料庫中,將儲存兩條相同的MD5值,這說明這兩個使用者的口令是一樣的。
    • 有沒有辦法讓使用相同口令的使用者儲存不同的MD5呢?
  • 如果假定使用者無法修改登入名,就可以透過把登入名作為Salt的一部分來計算MD5,從而實現相同口令的使用者也儲存不同的MD5。
  • 摘要演算法在很多地方都有廣泛的應用。
    • 要注意摘要演算法不是加密演算法,不能用於加密(因為無法透過摘要反推明文),只能用於防篡改,但是它的單向計算特性決定了可以在不儲存明文口令的情況下驗證使用者口令。

【四】摘要演算法模版

import hashlib


def get_md5_digest(salt,data):
    data = salt + data
    b_data = data.encode('utf-8')
    encrypted_data = hashlib.md5(b_data)

    return encrypted_data.hexdigest()

【五】SHA1

# 呼叫SHA1和呼叫MD5完全類似
import hashlib

sha1 = hashlib.sha1()
sha1.update(b'how to use sha1 in ')
sha1.update(b'python hashlib?')
print(sha1.hexdigest())
# 2c76b57293ce30acef38d98f6046927161b46a44

# SHA1的結果是160 bit位元組,通常用一個40位的16進位制字串表示

【五】MD5加密在驗證登入中的應用

  • main.py
import os
import hashlib
from verify_code import get_verify_code

# 宣告資料庫位置
file_path = 'Infos' + '\\' + 'user_pwd.txt'
if not os.path.exists(file_path):
    with open(file_path, 'a') as f:
        f.write('')


def encrypt_decrypt(data):
    # 轉為二進位制資料
    data = data.encode('utf-8')
    # 建立md5物件
    md5 = hashlib.md5()
    # md5進行加密
    md5.update(data)
    # 取出md5加密後的雜湊值
    encrypt_result = md5.hexdigest()
    return encrypt_result


def write_read_data(data=None, cmd=0):
    if cmd == 0:
        with open(file_path, 'a+') as f:
            f.write(data)
    else:
        user_list = []
        user_info_data = []
        with open(file_path, 'r') as f:
            for line in f:
                user_data = {}
                line = line.strip().split('|')
                username, password, salt_code = line[0], line[1], line[2]
                user_data['username'] = username
                user_data['password'] = password
                user_data['salt_code'] = salt_code
                user_list.append(username)
                user_info_data.append(user_data)
        return [user_list, user_info_data]


def register(username, password):
    # 獲得六位數的鹽
    salt_code = get_verify_code(6)
    # 原始密碼加鹽
    password_str = password + salt_code
    # 加鹽密碼加密
    password_encrypted = encrypt_decrypt(password_str)
    # 拼接儲存資料格式
    user_pwd_data = f'{username}|{password_encrypted}|{salt_code}\n'
    # 寫入檔案儲存資料
    write_read_data(user_pwd_data, cmd=0)
    print(f'{username}註冊成功,註冊結束!')


def login():
    # 拿到使用者名稱列表,使用者名稱和密碼及加鹽後的列表
    user_list, user_data = write_read_data(data=None, cmd=1)
    username_input = input('Username:>>>')
    password_input = input('Password:>>>')
    # 判斷使用者名稱是否存在於使用者名稱列表中
    # 存在則繼續登入
    if username_input in user_list:
        # 迴圈所有使用者名稱及資訊
        for info in user_data:
            # 取使用者名稱和加密後的密碼
            username = info['username']
            password = info['password']
            # 取加鹽後的密碼
            salt_code = info['salt_code']
            # 當前密碼加鹽
            password_str = password_input + salt_code
            # 當前加鹽密碼加密
            password_encrypted = encrypt_decrypt(password_str)
            if username == username_input and password == password_encrypted:
                print('登陸成功!')
                return True
            else:
                print('使用者名稱或密碼錯誤,登陸失敗!')
                main()
    else:
        print('使用者名稱不存在,請註冊')
        main()


def main():
    # 先校驗使用者名稱和密碼是否存在
    username = input('Username:>>>')
    # 獲取使用者列表
    user_list = write_read_data(cmd=1)[0]
    # 不存在使用者資訊則進行註冊
    if username not in user_list:
        print('當前使用者未註冊註冊,註冊操作開始!')
        # 註冊函式
        password = input('Password:>>>')
        register(username, password)
        # 註冊完成進行二次驗證校驗登陸
        main()
    else:
        password = input('Password:>>>')
        # 使用者存在進行登陸校驗
        print('進行登陸操作')
        # 拿到成功的結果
        res = login()
        # 成功則退出
        if res:
            print('歡迎使用')
            pass
        else:
            # 不成功二次校驗
            login()


if __name__ == '__main__':
    main()
  • verify_code.py
import random

'''生成六位隨機 (數字 + 大小寫) 驗證碼'''


def get_verify_code(n):
    code = ''
    for i in range(n):
        random_int = str(random.randint(0, 9))  # 0-9之間的整數
        random_upper = chr(random.randint(65, 90))  # A-Z之間的字母
        random_lower = chr(random.randint(97, 122))  # a-z之間的字母
        temp = random.choice([random_int, random_upper, random_lower])
        code += temp
    return code


if __name__ == "__main__":
    res = get_verify_code(6)
    print(res)

相關文章