1. 程式人生 > >python3__標準庫__hashlib模組__摘要演算法防篡改

python3__標準庫__hashlib模組__摘要演算法防篡改

1.hashlib基礎知識點

(1)hashlib提供了常用的摘要演算法:SHA1, SHA224, SHA256, SHA384, SHA512, MD5

(2)摘要演算法:摘要演算法又稱為雜湊演算法、雜湊演算法。他通過一個函式,把任意長度的資料轉換成一個固定長度的資料串,通常來說是16進位制的字串來進行表示。

(3)摘要演算法的目的:通過f()將任意長度data轉換成固定長度digest,為了發現原始資料是否被人篡改過

(4)摘要演算法並不是加密演算法,不能用於加密(因為無法通過摘要反推明文),只能用於防篡改。

(5)摘要演算法的單向計算特性決定了可以在不儲存明文口令的情況下驗證使用者口令。

(6)兩個不同的資料通過同一個摘要演算法完全有可能得到相同的digest, 因為任何摘要演算法都是把無限多的資料集合對映到一個有限的集合中。

2.MD5演算法

(1)MD5演算法是最常用的摘要演算法,速度快,生成的是固定的128bit位元組,通常用32位的16進位制字串表示。

(2)待摘要的資料量非常大,可多次update。

(3)常用方法:hashlib.md5(), hashlib.md5().update(string), hashlib.md5().digest()

import hashlib


m = hashlib.md5()                             # 建立md5物件

m.update(b"hello")
print(len(m.hexdigest()), m.hexdigest())      # 32 5d41402abc4b2a76b9719d911017c592

m.update("你好".encode(encoding="utf-8"))
print(len(m.digest()), m.digest())            # 16 b',\xfe6\x11f\x07\x8cYs\x0c\x07\\\x96k\xfe\x91'

# ------------------------------------------------------------------------------------------------------- #
# update(string): Update this hash object's state(雜湊物件的狀態) with the provided string.
# digest(): Return the digest value as a string of binary data(二進位制資料字串).
# hexdigest(): Return the digest value as a string of hexadecimal digits(十六進位制資料字串).
# ------------------------------------------------------------------------------------------------------- #

3.SHA系列演算法

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

(2)SHA256SHA512相比於SHA1更加的安全,不過越安全的演算法越慢,而且摘要長度更長。

import hashlib


# SHA1
s1 = hashlib.sha1()
s1.update(b"hello")
print(len(s1.digest()), s1.digest())             # 20 b'\xaa\xf4\xc6\x1d\xdc\xc5\xe8\xa2\xda\xbe\xde\x0f;H,\xd9\xae\xa9CM'
print(len(s1.hexdigest()), s1.hexdigest())       # 40 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d

s1.update("你好".encode(encoding="utf-8"))
print(len(s1.digest()), s1.digest())             # 20 b'AO>\xb5\xcfS[\xaa)N\xe4$.\xcf\xe4`\x1b!Rl'
print(len(s1.hexdigest()), s1.hexdigest())       # 40 414f3eb5cf535baa294ee4242ecfe4601b21526c

# SHA256:比較常用的
s256 = hashlib.sha256()
s256.update(b"hello")
print(len(s256.digest()), s256.digest())         # 32 b',\xf2M\xba_\xb0\xa3\x0e&\xe8;*\xc5\xb9\xe2\x9e\x1b\x16\x1e\\\x1f\xa7B^s\x043b\x93\x8b\x98$'
print(len(s256.hexdigest()), s256.hexdigest())   # 64 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

4.摘要演算法應用

        任何允許使用者登入的網站都會儲存使用者登入的使用者名稱和口令,一般採用的方法是將使用者名稱和對應的口令儲存到資料庫的一張二維表中:

name passwd
tom 123456
marry abc123
admin admin_maxin

        如果以明文儲存使用者口令,如果資料庫洩露,所有使用者的口令就落入駭客的手裡。此外,網站運維人員是可以訪問資料庫的,也就是能獲取到所有使用者的口令。所以,正確的儲存口令的方式是儲存口令的摘要

name passwd
tom e10adc3949ba59abbe56e057f20f883e
marry e99a18c428cb38d5f260853678922e03
admin 20973b101799904f6e234f0c17969610

        當用戶登入時,首先計算使用者輸入的明文口令的MD5,然後和資料庫儲存的MD5對比,如果一致,說明口令輸入正確,如果不一致,口令肯定錯誤。

        考慮這麼個情況,很多使用者喜歡用123456888888password這些簡單的口令,於是,駭客可以事先計算出這些常用口令的MD5值,得到一個反推表。因此,駭客只需要對比資料庫的MD5,就獲得了使用常用口令的使用者賬號。

        由於常用口令的MD5值很容易被計算出來,所以,要確保儲存的使用者口令不是那些已經被計算出來的常用口令的MD5,這一方法通過對原始口令加一個複雜字串來實現,俗稱“加鹽”。

def calc_md5(password):
    return get_md5(passwd + "the-Salt")

        經過Salt處理的MD5口令,只要Salt不被黑客知道,即使使用者輸入簡單口令,也很難通過MD5反推明文口令。但是如果有兩個使用者都使用了相同的簡單口令比如123456,在資料庫中,將儲存兩條相同的MD5值,這說明這兩個使用者的口令是一樣的。

        為了讓使用相同口令的人在資料庫二維表中儲存不同的MD5,則可通過:

def register(username, passwd):
    db[username] = get_md5(passwd + username + "the-Salt")
# ! /usr/bin/env python
# coding:utf-8
# python interpreter:3.6.2
# author: admin_maxin
import hashlib
import datetime
import shelve
import time


# 設定登入超時時間
LOGIN_TIME_OUT = 0.60

# 設定臨時儲存使用者“name”和“password”的shelve檔案;允許寫回
db = shelve.open("user_shelve", writeback=True)


# 新使用者註冊
def register():
    name = None

    # 對使用者名稱的合法性進行檢驗
    while True:
        name = input("register name:").strip()
        if name in db:
            print("Name already exits!Please try again!")
            continue
        elif None == name:
            print("The name cannot be empty!")
            continue
        elif "Q" == name:
            return
        else:
            break

    pwd = input("register passwd:").strip()
    # 防止相同pwd的人儲存相同的md5
    db[name] = {"passwd": md5_digest(pwd + name), "last_login_time": time.time()}


# 判斷當前使用者“是否合法”和“是否超時”
def olduser():
    name = None
    pwd = None
    passwd = None

    while True:
        name = input("name:").strip()
        passwd = input("passwd:").strip()

        # 判斷當前使用者“是否註冊”
        try:
            pwd = db[name]["passwd"]
            break
        except AttributeError as ae:
            print("\003[1;31;40mUsername '%s' doesn't existed\033[0m" % name)
            break
        except IndexError as ie:
            print("\003[1;31;40mUsername '%s' doesn't existed\033[0m" % name)
            break

    # 判斷密碼摘要是否準確
    if md5_digest(passwd + name) == pwd:
        login_time = time.time()
        last_login_time = db[name]["last_login_time"]

        # 判斷當前使用者是否超過了登入時間
        if login_time - last_login_time < LOGIN_TIME_OUT:
            print("\033[1;31;40mYou already logged in at: <%s>\033[0m" % datetime.datetime.fromtimestamp(last_login_time).isoformat())

        # 更新最近登入時間
        db[name]["last_login_time"] = login_time
        print("\033[1;32;40mwelcome back\033[0m", name)
    else:
        print("\033[1;31;40mlogin incorrect\033[0m")


# md5摘要傳輸近來的明文
def md5_digest(message):
    m5 = hashlib.md5()
    m5.update(message.encode(encoding="utf-8"))
    return m5.hexdigest()


# 主介面
def menu():
    prompt = """
    (N)ew User Login
    (E)xisting User Login
    (Q)uit
    Enter choice: """

    # 設定程式退出標誌
    flg = False
    while not flg:
        choice = None
        while True:
            try:
                choice = input(prompt).strip()[0].lower()
            # 捕獲異常選擇直接變成選q退出程式
            except (EOFError, KeyboardInterrupt):
                print("\033[1;31;40m Error!\033[0m")
                return

            print("\nYou picked: [%s]" % choice)  # 提示你的選擇是什麼
            if choice not in "neq":
                print("invalid option, try again")
                continue
            else:
                break

        if choice == "q":
            flg = True
        if choice == "n":
            register()
        if choice == "e":
            olduser()

    # 操作完成之後關閉檔案控制代碼
    db.close()


# 測試模組
if "__main__" == __name__:
    menu()

5.彩虹表破解

        彩虹表(rainbow table)是一個用於加密雜湊函式逆運算的預先計算好的表, 為破解密碼的雜湊值(或稱雜湊值、微縮圖、摘要、指紋、雜湊密文)而準備。一般主流的彩虹表都在100G以上;這樣的表常常用於恢復由有限集字元組成的固定長度的純文字密碼;這是空間 / 時間替換的典型實踐, 比每一次嘗試都計算雜湊的暴力破解處理時間少而儲存空間多,但卻比簡單的對每條輸入雜湊翻查表的破解方式儲存空間少而處理時間多;使用加salt的KDF函式可以使這種攻擊難以實現。

6.hmac模組

        python 還有一個 hmac 模組,它內部對我們建立 key 和 內容 再進行處理然後再加密。雜湊訊息鑑別碼,簡稱HMAC,是一種基於訊息鑑別碼MAC(Message Authentication Code)的鑑別機制。使用HMAC時,訊息通訊的雙方,通過驗證訊息中加入的鑑別金鑰K來鑑別訊息的真偽;一般用於網路通訊中訊息加密,前提是雙方先要約定好key,就像接頭暗號一樣,傳送方:key將訊息加密,接收方:key + 訊息明文再加密。拿加密後的值跟傳送者的相對比是否相等,這樣就能驗證訊息的真實性,及傳送者的合法性了。

        使用hmac演算法比標準的hash演算法更加的安全,因為針對相同的message,不同的key會產生不同的hash.

import hmac


key1 = "天王蓋地虎"
msg = "你是250"
msg2 = "都是250"
send = hmac.new(key1.encode(encoding="utf-8"), msg.encode(encoding="utf-8"), digestmod="md5")
print(len(send.digest()), send.digest())           # 16 b'\x01\xa0m\r5EL\x9fp\\\xfe\xc6S\xb8Co'
print(len(send.hexdigest()), send.hexdigest())     # 32 01a06d0d35454c9f705cfec653b8436f

receive = hmac.new(key1.encode(encoding="utf-8"), msg2.encode(encoding="utf-8"), digestmod="md5")
print(receive == send)                             # False: 原始資訊發生變化
# ------------------------------------------------------------------------------------------------------- #
# hmac.new():Create a new hashing object and return it
#   key: The starting key for the hash.
#   msg: if available, will immediately be hashed into the object's starting state.
# ------------------------------------------------------------------------------------------------------- #