在 「Dropbox 做到資料加密又避免重複儲存的秘密」 這篇文章中,有提到使用 AES-256 對稱式加密,以及 SHA-256 雜湊演算法, 本文山姆鍋提供這兩者在 Python 的應用範例作為補充。
本文的範例使用 Python 2.7。 山姆鍋假設讀者對密碼學以及雜湊演算法已經有基本概念:至少知道什麼是對稱式加密(Symmetric Encryption)、 什麼是雜湊函式(Hash function)等等。
相依套件
針對 AES-256 加密,我們需要用到 PyCrypto 這個套件,由於雜湊值常常需要輸出成字串且被使用在 URL 網址,因此山姆鍋習慣使用 Base58 的來將雜湊值編碼。
使用下列指令安裝相依套件:
pip install pycrypto
pip install base58
或者安裝本文使用的特定版本:
pip install pycrypto==2.6.1
pip install base58==0.2.1
AES-256 CBC 範例
# -*- coding: utf-8 -*-
"""
Routines for convergent encryption.
"""
from __future__ import absolute_import, division, unicode_literals
from Crypto.Cipher import AES
_IV = 16 * '\x00'
def aes_encrypt(data, key):
cryptor = AES.new(key, AES.MODE_CBC, _IV)
return cryptor.encrypt(data)
def aes_decrypt(data, key):
cryptor = AES.new(key, AES.MODE_CBC, _IV)
return cryptor.decrypt(data)
範例提供 aes_encrypt
以及 aes_decrypt
分別作為加密跟解密的用途。其中的 IV
是 initial vector value, 通常應該是隨機值,這裡因為是用在 Convergent encryption, 且使用的金鑰(secret key)應該已經足夠安全,所以採用固定值。
SHA-256 範例
如果對於產生出的字串長度沒特別要求的話,山姆鍋建議使用 base58 的檢查模式,這在驗證輸入正確性上很有用。
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
import os
import hashlib
import base58
def gen_sha_value(data):
""" Gets the Base58-encoded SHA value of the given data.
:param data:
:return:
"""
shavalue = hashlib.sha256()
shavalue.update(data)
return base58.b58encode_check(shavalue.digest())
def gen_binsha(data):
""" Gets the binary SHA value of the given data.
:param data:
:return:
"""
shavalue = hashlib.sha256()
shavalue.update(data)
return shavalue.digest()
使用 base58.b58encode_check
編碼的字串需要使用 base58.b58decode_check
來解碼。 讀者也許會納悶:為什麼要使用檢查碼而不直接查詢資料庫來確認? 在 Convergent encryption 的應用中, 雜湊值也用來作為定位器(locator),可以簡單地解釋成資料區塊位址。使用檢查碼可以過濾掉假造的位址, 如果每次都要用資料庫檢查,會造成資料庫太大的負擔。當然最終還是需要讀取資料庫,這時也會做最後驗證。
測試程式
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals
import os
import random
import unittest
from ava.vault import utils # 包含 SHA-256 以及 AES-256 的公用函式。
class TestUtils(unittest.TestCase):
def test_gen_binsha(self):
data = os.urandom(100)
sha = utils.gen_binsha(data)
self.assertEqual(32, len(sha)) # SHA-256 的長度是 256 bits = 32 bytes
def test_gen_sha_value(self):
data = os.urandom(100)
sha = utils.gen_sha_value(data)
assert len(sha) == 50 or len(sha) == 49 # 使用 Base58 編碼,長度有兩種可能
def test_convergent_encryption(self):
plain_data = os.urandom(1024)
key = utils.gen_binsha(plain_data) # 加密的 key 使用 bytes,需要是256-bits
encrypted = utils.aes_encrypt(plain_data, key)
decrypted = utils.aes_decrypt(encrypted, key)
self.assertEqual(plain_data, decrypted) # 確認可以正常解密。
encrypted2 = utils.aes_encrypt(plain_data, key)
self.assertEqual(encrypted, encrypted2) # 確認相同的資料跟 key, 編碼必須相同。
其中,test_convergent_encryption
示範從資料產生加密金鑰然後完成加解密動作。 Note: 上述的程式片段是經過手動修改,跟山姆鍋實際的程式有所不同。
結語
密碼學是一門複雜的科學,背後理論不是多數人可以完全理解。但在網路時代,我們卻又不得不越來越重視密碼學的應用。 了解密碼學的基本概念以及如何應用,山姆鍋相信是軟體開發者所必須具備的知識跟能力。
參考資料
-
AES: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard ⎘
-
PyCrypto: https://pypi.python.org/pypi/pycrypto ⎘ : Python 常用的加解密套件。
-
Python base58 套件: https://pypi.python.org/pypi/base58 ⎘
-
Base58: https://en.wikipedia.org/wiki/Base58 ⎘ : Base58 的說明。