前言

「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: 上述的程式片段是經過手動修改 , 跟山姆鍋實際的程式有所不同 。

結語

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