Python 3 加密簡介

Python 3 加密簡介Python 3 加密簡介

 

哈希python

若是須要用到安全哈希算法或是消息摘要算法,那麼你可使用標準庫中的 hashlib 模塊。這個模塊包含了符合 FIPS(美國聯邦信息處理標準)的安全哈希算法,包括 SHA1,SHA224,SHA256,SHA384,SHA512 以及 RSA 的 MD5 算法。Python 也支持 adler32 以及 crc32 哈希函數,不過它們在 zlib 模塊中。linux

哈希的一個最多見的用法是,存儲密碼的哈希值而非密碼自己。固然了,使用的哈希函數須要穩健一點,不然容易被破解。另外一個常見的用法是,計算一個文件的哈希值,而後將這個文件和它的哈希值分別發送。接收到文件的人能夠計算文件的哈希值,檢驗是否與接受到的哈希值相符。若是二者相符,就說明文件在傳送的過程當中未經篡改。算法

讓咱們試着建立一個 md5 哈希:shell

>>> import hashlib
>>> md5 = hashlib.md5()
>>> md5.update('Python rocks!')
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    md5.update('Python rocks!')
TypeError: Unicode-objects must be encoded before hashing
>>> md5.update(b'Python rocks!')
>>> md5.digest()
b'/x14/x82/xec/x1b#d/xf6N}/x16*+[/x16/xf4w'

讓咱們花點時間一行一行來說解。首先,咱們導入 hashlib ,而後建立一個 md5 哈希對象的實例。接着,咱們向這個實例中添加一個字符串後,卻獲得了報錯信息。原來,計算 md5 哈希時,須要使用字節形式的字符串而非普通字符串。正確添加字符串後,咱們調用它的 digest 函數來獲得哈希值。若是你想要十六進制的哈希值,也能夠用如下方法:安全

>>> md5.hexdigest()
'1482ec1b2364f64e7d162a2b5b16f477'

實際上,有一種精簡的方法來建立哈希,下面咱們看一下用這種方法建立一個 sha1 哈希:session

>>> sha = hashlib.sha1(b'Hello Python').hexdigest()
>>> sha
'422fbfbc67fe17c86642c5eaaa48f8b670cbed1b'

能夠看到,咱們能夠同時建立一個哈希實例而且調用其 digest 函數。而後,咱們打印出這個哈希值看一下。這裏我使用 sha1 哈希函數做爲例子,但它不是特別安全,讀者能夠隨意嘗試其餘的哈希函數。dom

密鑰導出編輯器

Python 的標準庫對密鑰導出支持較弱。實際上,hashlib 函數庫提供的惟一方法就是 pbkdf2_hmac 函數。它是 PKCS#5 的基於口令的第二個密鑰導出函數,並使用 HMAC 做爲僞隨機函數。由於它支持「加鹽」和迭代操做,你可使用相似的方法來哈希你的密碼。例如,若是你打算使用 SHA-256 加密方法,你將須要至少 16 個字節的「鹽」,以及最少 100000 次的迭代操做。函數

簡單來講,「鹽」就是隨機的數據,被用來加入到哈希的過程當中,以加大破解的難度。這基本能夠保護你的密碼免受字典和彩虹表的攻擊。學習

讓咱們看一個簡單的例子:

>>> import binascii
>>> dk = hashlib.pbkdf2_hmac(hash_name='sha256',
        password=b'bad_password34', 
        salt=b'bad_salt', 
        iterations=100000)
>>> binascii.hexlify(dk)
b'6e97bad21f6200f9087036a71e7ca9fa01a59e1d697f7e0284cd7f9b897d7c02'

這裏,咱們用 SHA256 對一個密碼進行哈希,使用了一個糟糕的鹽,但通過了 100000 次迭代操做。固然,SHA 實際上並不被推薦用來建立密碼的密鑰。你應該使用相似 scrypt 的算法來替代。另外一個不錯的選擇是使用一個叫 bcrypt 的第三方庫,它是被專門設計出來哈希密碼的。

PyCryptodome

PyCrypto 多是 Python 中密碼學方面最有名的第三方軟件包。惋惜的是,它的開發工做於 2012 年就已中止。其餘人還在繼續發佈最新版本的 PyCrypto,若是你不介意使用第三方的二進制包,仍能夠取得 Python 3.5 的相應版本。好比,我在 Github 上找到了對應 Python 3.5 的 PyCrypto 二進制包。

幸運的是,有一個該項目的分支 PyCrytodome 取代了 PyCrypto 。爲了在 Linux 上安裝它,你可使用如下 pip 命令:

pip install pycryptodome

在 Windows 系統上安裝則稍有不一樣:

pip install pycryptodomex

若是你遇到了問題,多是由於你沒有安裝正確的依賴包(LCTT 譯註:如 python-devel),或者你的 Windows 系統須要一個編譯器。若是你須要安裝上的幫助或技術支持,能夠訪問 PyCryptodome 的網站。

還值得注意的是,PyCryptodome 在 PyCrypto 最後版本的基礎上有不少改進。很是值得去訪問它們的主頁,看看有什麼新的特性。

加密字符串

訪問了他們的主頁以後,咱們能夠看一些例子。在第一個例子中,咱們將使用 DES 算法來加密一個字符串:

>>> from Crypto.Cipher import DES
>>> key = 'abcdefgh'
>>> def pad(text):
        while len(text) % 8 != 0:
            text += ' '
        return text
>>> des = DES.new(key, DES.MODE_ECB)
>>> text = 'Python rocks!'
>>> padded_text = pad(text)
>>> encrypted_text = des.encrypt(text)
Traceback (most recent call last):
  File "<pyshell#35>", line 1, in <module>
    encrypted_text = des.encrypt(text)
  File "C:/Programs/Python/Python35-32/lib/site-packages/Crypto/Cipher/blockalgo.py", line 244, in encrypt
    return self._cipher.encrypt(plaintext)
ValueError: Input strings must be a multiple of 8 in length
>>> encrypted_text = des.encrypt(padded_text)
>>> encrypted_text
b'>/xfc/x1f/x16x/x87/xb2/x93/x0e/xfcH/x02/xd59VQ'

這段代碼稍有些複雜,讓咱們一點點來看。首先須要注意的是,DES 加密使用的密鑰長度爲 8 個字節,這也是咱們將密鑰變量設置爲 8 個字符的緣由。而咱們須要加密的字符串的長度必須是 8 的倍數,因此咱們建立了一個名爲 pad 的函數,來給一個字符串末尾填充空格,直到它的長度是 8 的倍數。而後,咱們建立了一個 DES 的實例,以及咱們須要加密的文本。咱們還建立了一個通過填充處理的文本。咱們嘗試着對未經填充處理的文本進行加密,啊歐,報了一個 ValueError 錯誤!咱們須要對通過填充處理的文本進行加密,而後獲得加密的字符串。代碼爲:

encrypted_text = des.encrypt(padded_text.encode('utf-8'))

知道了如何加密,還要知道如何解密:

>>> des.decrypt(encrypted_text)
b'Python rocks!   '

幸運的是,解密很是容易,咱們只須要調用 des 對象的 decrypt 方法就能夠獲得咱們原來的 byte 類型字符串了。下一個任務是學習如何用 RSA 算法加密和解密一個文件。首先,咱們須要建立一些 RSA 密鑰。

建立 RSA 密鑰

若是你但願使用 RSA 算法加密數據,那麼你須要擁有訪問 RAS 公鑰和私鑰的權限,不然你須要生成一組本身的密鑰對。在這個例子中,咱們將生成本身的密鑰對。建立 RSA 密鑰很是容易,因此咱們將在 Python 解釋器中完成。

>>> from Crypto.PublicKey import RSA
>>> code = 'nooneknows'
>>> key = RSA.generate(2048)
>>> encrypted_key = key.exportKey(passphrase=code, pkcs=8, 
        protection="scryptAndAES128-CBC")
>>> with open('/path_to_private_key/my_private_rsa_key.bin', 'wb') as f:
        f.write(encrypted_key)
>>> with open('/path_to_public_key/my_rsa_public.pem', 'wb') as f:
        f.write(key.publickey().exportKey())

首先咱們從 Crypto.PublicKey 包中導入 RSA,而後建立一個傻傻的密碼。接着咱們生成 2048 位的 RSA 密鑰。如今咱們到了關鍵的部分。爲了生成私鑰,咱們須要調用 RSA 密鑰實例的 exportKey 方法,而後傳入密碼,使用的 PKCS 標準,以及加密方案這三個參數。以後,咱們把私鑰寫入磁盤的文件中。

接下來,咱們經過 RSA 密鑰實例的 publickey 方法建立咱們的公鑰。咱們使用方法鏈調用 publickey 和 exportKey 方法生成公鑰,一樣將它寫入磁盤上的文件。

加密文件

有了私鑰和公鑰以後,咱們就能夠加密一些數據,並寫入文件了。這裏有個比較標準的例子:

from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES, PKCS1_OAEP

with open('/path/to/encrypted_data.bin', 'wb') as out_file:
    recipient_key = RSA.import_key(
        open('/path_to_public_key/my_rsa_public.pem').read())
    session_key = get_random_bytes(16)

    cipher_rsa = PKCS1_OAEP.new(recipient_key)
    out_file.write(cipher_rsa.encrypt(session_key))

    cipher_aes = AES.new(session_key, AES.MODE_EAX)
    data = b'blah blah blah Python blah blah'
    ciphertext, tag = cipher_aes.encrypt_and_digest(data)

    out_file.write(cipher_aes.nonce)
    out_file.write(tag)
    out_file.write(ciphertext)

代碼的前三行導入 PyCryptodome 包。而後咱們打開一個文件用於寫入數據。接着咱們導入公鑰賦給一個變量,建立一個 16 字節的會話密鑰。在這個例子中,咱們將使用混合加密方法,即 PKCS#1 OAEP ,也就是最優非對稱加密填充。這容許咱們向文件中寫入任意長度的數據。接着咱們建立 AES 加密,要加密的數據,而後加密數據。咱們將獲得加密的文本和消息認證碼。最後,咱們將隨機數,消息認證碼和加密的文本寫入文件。

順便提一下,隨機數一般是真隨機或僞隨機數,只是用來進行密碼通訊的。對於 AES 加密,其密鑰長度最少是 16 個字節。隨意用一個你喜歡的編輯器試着打開這個被加密的文件,你應該只能看到亂碼。

如今讓咱們學習如何解密咱們的數據。

from Crypto.PublicKey import RSA
from Crypto.Cipher import AES, PKCS1_OAEP

code = 'nooneknows'

with open('/path/to/encrypted_data.bin', 'rb') as fobj:
    private_key = RSA.import_key(
        open('/path_to_private_key/my_rsa_key.pem').read(),
        passphrase=code)

    enc_session_key, nonce, tag, ciphertext = [ fobj.read(x) 
                                                for x in (private_key.size_in_bytes(), 
                                                16, 16, -1) ]

    cipher_rsa = PKCS1_OAEP.new(private_key)
    session_key = cipher_rsa.decrypt(enc_session_key)

    cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce)
    data = cipher_aes.decrypt_and_verify(ciphertext, tag)

print(data)

若是你認真看了上一個例子,這段代碼應該很容易解析。在這裏,咱們先以二進制模式讀取咱們的加密文件,而後導入私鑰。注意,當你導入私鑰時,須要提供一個密碼,不然會出現錯誤。而後,咱們文件中讀取數據,首先是加密的會話密鑰,而後是 16 字節的隨機數和 16 字節的消息認證碼,最後是剩下的加密的數據。

接下來咱們須要解密出會話密鑰,從新建立 AES 密鑰,而後解密出數據。

你還能夠用 PyCryptodome 庫作更多的事。不過咱們要接着討論在 Python 中還能夠用什麼來知足咱們加密解密的需求。

Cryptography 包

Cryptography 的目標是成爲「人類易於使用的密碼學包,就像 requests 是「人類易於使用的 HTTP 庫」同樣。這個想法使你可以建立簡單安全、易於使用的加密方案。若是有須要的話,你也可使用一些底層的密碼學基元,但這也須要你知道更多的細節,不然建立的東西將是不安全的。

若是你使用的 Python 版本是 3.5, 你可使用 pip 安裝,以下:

pip install cryptography

你會看到 cryptography 包還安裝了一些依賴包,若是安裝都順利,咱們就能夠試着加密一些文本了。讓咱們使用 Fernet 對稱加密算法,它保證了你加密的任何信息在不知道密碼的狀況下不能被篡改或讀取。Fernet 還經過 MultiFernet 支持密鑰輪換。下面讓咱們看一個簡單的例子:

>>> from cryptography.fernet import Fernet
>>> cipher_key = Fernet.generate_key()
>>> cipher_key
b'APM1JDVgT8WDGOWBgQv6EIhvxl4vDYvUnVdg-Vjdt0o='
>>> cipher = Fernet(cipher_key)
>>> text = b'My super secret message'
>>> encrypted_text = cipher.encrypt(text)
>>> encrypted_text
(b'gAAAAABXOnV86aeUGADA6mTe9xEL92y_m0_TlC9vcqaF6NzHqRKkjEqh4d21PInEP3C9HuiUkS9f'
 b'6bdHsSlRiCNWbSkPuRd_62zfEv3eaZjJvLAm3omnya8=')
>>> decrypted_text = cipher.decrypt(encrypted_text)
>>> decrypted_text
b'My super secret message'

首先咱們須要導入 Fernet,而後生成一個密鑰。咱們輸出密鑰看看它是什麼樣兒。如你所見,它是一個隨機的字節串。若是你願意的話,能夠試着多運行 generate_key 方法幾回,生成的密鑰會是不一樣的。而後咱們使用這個密鑰生成 Fernet 密碼實例。

如今咱們有了用來加密和解密消息的密碼。下一步是建立一個須要加密的消息,而後使用 encrypt 方法對它加密。我打印出加密的文本,而後你能夠看到你再也讀不懂它了。爲了解密出咱們的祕密消息,咱們只需調用 decrypt 方法,並傳入加密的文本做爲參數。結果就是咱們獲得了消息字節串形式的純文本。

小結

這一章僅僅淺顯地介紹了 PyCryptodome 和 cryptography 這兩個包的使用。不過這也確實給了你一個關於如何加密解密字符串和文件的簡述。請務必閱讀文檔,作作實驗,看看還能作些什麼!

免費提供最新Linux技術教程書籍,爲開源技術愛好者努力作得更多更好:http://www.linuxprobe.com/

相關文章
相關標籤/搜索