Python3標准庫:hmac密碼信息簽名與驗證


1. hmac密碼信息簽名與驗證

HMAC算法可以用於驗證信息的完整性,這些信息可能在應用之間傳遞,或者存儲在一個可能有安全威脅的地方。基本思想是生成實際數據的一個密碼散列,並提供一個共享的秘密密鑰。然后使用得到的散列檢查所傳輸或存儲的信息,以確定一個信任級別,而不是傳輸秘密密鑰。

1.1 消息簽名

new()函數會創建一個新對象來計算消息簽名。下面這個例子使用了默認的MD5散列算法。

import hmac

digest_maker = hmac.new(b'secret-shared-key-goes-here')

with open('lorem.txt', 'rb') as f:
    while True:
        block = f.read(1024)
        if not block:
            break
        digest_maker.update(block)

digest = digest_maker.hexdigest()
print(digest)

運行這段代碼時,會讀取一個數據文件,並為它計算一個HMAC簽名。

1.2 候選摘要類型

盡管hmac的默認密碼算法是MD5,但這並不是最安全的方法。MD5散列有一些缺點,如沖突(兩個不同的消息生成相同的散列)。一般認為SHA1算法更健壯,更建議使用。 

import hmac
import hashlib

digest_maker = hmac.new(
    b'secret-shared-key-goes-here',
    b'',
    hashlib.sha1,
)

with open('demo.py', 'rb') as f:
    while True:
        block = f.read(1024)
        if not block:
            break
        digest_maker.update(block)

digest = digest_maker.hexdigest()
print(digest)

new()函數有3個參數。第1個參數是秘密密鑰,這個密鑰會在通信雙方之間共享,使兩端都可以使用相同的值。第2個值是一個初始消息。如果需要認證的消息內容很小,如一個時間戳或一個HTTP POST,則把整個消息體都傳遞到new()而不是使用update()方法。最后一個參數是要使用的摘要模塊。默認為hashlib.md5,不過這個例子傳入了'sha1',其會讓hmac使用hashlib.sha1。

1.3 二進制摘要

前面的例子使用hexdigest()方法來生成可打印的摘要。hexdigest是digest()方法計算的值的一個不同表示,這是一個二進制值,可以包括不可打印的字符(包括NUL)。有些Web服務(Google checkout、Amazon S3)會使用base64編碼版本的二進制摘要而不是hexdigest。

import base64
import hmac
import hashlib

with open('lorem.txt', 'rb') as f:
    body = f.read()

hash = hmac.new(
    b'secret-shared-key-goes-here',
    body,
    hashlib.sha1,
)

digest = hash.digest()
print(base64.encodebytes(digest))

base64編碼串以一個換行符結束,在HTTP首部或其他格式敏感的上下文中嵌入這個串時,通常需要去除這個換行符。

 

1.4 消息簽名的應用

對於所有公共網絡服務,在安全性要求很高的地方存儲數據,就應當使用HMAC認證。例如,通過一個管道或套接字發送數據時,應當對數據進行簽名,然后在使用這個數據之前要檢查這個簽名。文件hmac_pickle.py中給出了一個擴展例子。

第一步是建立一個函數,計算一個串的摘要,另外實例化一個簡單的類,並通過一個通信通道傳遞。 

import hashlib
import hmac
import io
import pickle
import pprint

def make_digest(message):
    "Return a digest for the message."
    hash = hmac.new(
        b'secret-shared-key-goes-here',
        message,
        hashlib.sha1,
    )
    return hash.hexdigest().encode('utf-8')

class SimpleObject:
    """Demonstrate checking digests before unpickling.
    """

    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name

接下來,創建一個ByteIO緩沖區表示這個套接字或管道。這個例子對數據流使用了一種易於解析的原生格式。首先寫出摘要以及數據長度,后面是一個換行符。接下來是對象的串行化表示(由pickle生成)。實際的系統可能不希望依賴於一個長度值,畢竟如果摘要不正確,這個長度可能也是錯誤的。更適合的做法是使用真實數據中不太可能出現的某個終止符序列。

然后示例程序向流寫兩個對象。寫第一個對象時使用了正確的摘要值。

# Simulate a writable socket or pipe with a buffer
out_s = io.BytesIO()

# Write a valid object to the stream:
#  digest\nlength\npickle
o = SimpleObject('digest matches')
pickled_data = pickle.dumps(o)
digest = make_digest(pickled_data)
header = b'%s %d\n' % (digest, len(pickled_data))
print('WRITING: {}'.format(header))
out_s.write(header)
out_s.write(pickled_data)

再用一個不正確的摘要將第二個對象寫入流,這個摘要是為其他數據計算的,而並非由pickle生成。

# Write an invalid object to the stream
o = SimpleObject('digest does not match')
pickled_data = pickle.dumps(o)
digest = make_digest(b'not the pickled data at all')
header = b'%s %d\n' % (digest, len(pickled_data))
print('\nWRITING: {}'.format(header))
out_s.write(header)
out_s.write(pickled_data)

out_s.flush()

既然數據在BytesIO緩沖區中,那么可以將它再次讀出。首先讀取包含摘要和數據長度的數據行,然后使用得到的長度值讀取其余的數據。pickle.load()可以直接從流讀數據,不過這種策略有一個假設,認為它是一個可信的數據流,但這個數據還不能保證足夠可信到可以解除pickled。可以將pickle作為一個串從流讀取,而不是真正將對象解除pickled,這樣會更為安全。

# Simulate a readable socket or pipe with a buffer
in_s = io.BytesIO(out_s.getvalue())

# Read the data
while True:
    first_line = in_s.readline()
    if not first_line:
        break
    incoming_digest, incoming_length = first_line.split(b' ')
    incoming_length = int(incoming_length.decode('utf-8'))
    print('\nREAD:', incoming_digest, incoming_length)

一旦pickled數據在內存中,那么可以重新計算摘要值,並使用compare_digest()與所讀取的數據進行比較。如果摘要匹配,就可以信任這個數據,並對其解除pickled。

    incoming_pickled_data = in_s.read(incoming_length)

    actual_digest = make_digest(incoming_pickled_data)
    print('ACTUAL:', actual_digest)

    if hmac.compare_digest(actual_digest, incoming_digest):
        obj = pickle.loads(incoming_pickled_data)
        print('OK:', obj)
    else:
        print('WARNING: Data corruption')

輸出顯示第一個對象通過驗證,不出所料,認為第二個對象“已被破壞”。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM