「Dropbox 做到資料加密又避免重複儲存的秘密」 這篇文章中,有提到使用 AES-256 對稱式加密,以及 SHA-256 雜湊演算法, 本文山姆鍋提供這兩者在 Python 的應用範例作為補充。

本文的範例使用 Python 2.7。 山姆鍋假設讀者對密碼學以及雜湊演算法已經有基本概念:至少知道什麼是對稱式加密 (Symmetric Encryption)、 什麼是雜湊函式(Hash function) 等等。

相依套件

針對 AES-256 加密,我們需要用到 PyCrypto 這個套件,由於雜湊值常常需要輸出成字串且被使用在 URL 網址,因此山姆鍋習慣使用 Base58 的來將雜湊值編碼。

使用下列指令安裝相依套件:

1
2
pip install pycrypto
pip install base58

或者安裝本文使用的特定版本:

1
2
pip install pycrypto==2.6.1
pip install base58==0.2.1

AES-256 CBC 範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- 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 的檢查模式,這在驗證輸入正確性上很有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# -*- 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),可以簡單地解釋成資料區塊位址。使用檢查碼可以過濾掉假造的位址, 如果每次都要用資料庫檢查,會造成資料庫太大的負擔。當然最終還是需要讀取資料庫,這時也會做最後驗證。

測試程式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# -*- 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: 上述的程式片段是經過手動修改,跟山姆鍋實際的程式有所不同。

結語

密碼學是一門複雜的科學,背後理論不是多數人可以完全理解。但在網路時代,我們卻又不得不越來越重視密碼學的應用。 了解密碼學的基本概念以及如何應用,山姆鍋相信是軟體開發者所必須具備的知識跟能力。

參考資料

  1. AES: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard

  2. SHA-256: https://en.wikipedia.org/wiki/SHA-2

  3. PyCrypto: https://pypi.python.org/pypi/pycrypto
    Python 常用的加解密套件。
  4. Python base58 套件: https://pypi.python.org/pypi/base58

  5. Base58: https://en.wikipedia.org/wiki/Base58
    Base58 的說明。