ricmoo/pyaes
GitHub: ricmoo/pyaes
纯 Python 实现的 AES 分组加密算法,支持多种操作模式且无外部依赖。
Stars: 470 | Forks: 148
# pyaes
一个纯 Python 实现的 AES 分组密码算法及其通用操作模式(CBC, CFB, CTR, ECB 和 OFB)。
## 功能特性
* 支持所有 AES 密钥长度
* 支持所有 AES 常用模式
* 纯 Python 实现(无外部依赖)
* BlockFeeder API 允许轻松地对流进行加密和解密
* 支持 Python 2.x 和 3.x(请确保在 Python 3 中传入 bytes() 而不是字符串)
## API
所有密钥的长度可以是 128 位(16 字节)、192 位(24 字节)或 256 位(32 字节)。
要生成随机密钥,请使用:
```
import os
# 128 bit, 192 bit and 256 bit keys
key_128 = os.urandom(16)
key_192 = os.urandom(24)
key_256 = os.urandom(32)
```
要通过易于记忆的密码生成密钥,请考虑使用*基于密码的密钥派生函数*,例如 [scrypt](https://github.com/ricmoo/pyscrypt)。
### 常用操作模式
操作模式有很多种,各有优缺点。但通常建议使用 **CBC** 和 **CTR** 模式。**不建议使用 ECB**,包含它主要是为了完整性。
以下每个示例均假设使用以下密钥:
```
import pyaes
# A 256 bit (32 byte) key
key = "This_key_for_demo_purposes_only!"
# For some modes of operation we need a random initialization vector
# of 16 bytes
iv = "InitializationVe"
```
#### 计数器模式 (推荐)
```
aes = pyaes.AESModeOfOperationCTR(key)
plaintext = "Text may be any length you wish, no padding is required"
ciphertext = aes.encrypt(plaintext)
# '''\xb6\x99\x10=\xa4\x96\x88\xd1\x89\x1co\xe6\x1d\xef;\x11\x03\xe3\xee
# \xa9V?wY\xbfe\xcdO\xe3\xdf\x9dV\x19\xe5\x8dk\x9fh\xb87>\xdb\xa3\xd6
# \x86\xf4\xbd\xb0\x97\xf1\t\x02\xe9 \xed'''
print repr(ciphertext)
# The counter mode of operation maintains state, so decryption requires
# a new instance be created
aes = pyaes.AESModeOfOperationCTR(key)
decrypted = aes.decrypt(ciphertext)
# True
print decrypted == plaintext
# To use a custom initial value
counter = pyaes.Counter(initial_value = 100)
aes = pyaes.AESModeOfOperationCTR(key, counter = counter)
ciphertext = aes.encrypt(plaintext)
# '''WZ\x844\x02\xbfoY\x1f\x12\xa6\xce\x03\x82Ei)\xf6\x97mX\x86\xe3\x9d
# _1\xdd\xbd\x87\xb5\xccEM_4\x01$\xa6\x81\x0b\xd5\x04\xd7Al\x07\xe5
# \xb2\x0e\\\x0f\x00\x13,\x07'''
print repr(ciphertext)
```
#### 密码分组链接模式 (推荐)
```
aes = pyaes.AESModeOfOperationCBC(key, iv = iv)
plaintext = "TextMustBe16Byte"
ciphertext = aes.encrypt(plaintext)
# '\xd6:\x18\xe6\xb1\xb3\xc3\xdc\x87\xdf\xa7|\x08{k\xb6'
print repr(ciphertext)
# The cipher-block chaining mode of operation maintains state, so
# decryption requires a new instance be created
aes = pyaes.AESModeOfOperationCBC(key, iv = iv)
decrypted = aes.decrypt(ciphertext)
# True
print decrypted == plaintext
```
#### 密码反馈模式
```
# Each block into the mode of operation must be a multiple of the segment
# size. For this example we choose 8 bytes.
aes = pyaes.AESModeOfOperationCFB(key, iv = iv, segment_size = 8)
plaintext = "TextMustBeAMultipleOfSegmentSize"
ciphertext = aes.encrypt(plaintext)
# '''v\xa9\xc1w"\x8aL\x93\xcb\xdf\xa0/\xf8Y\x0b\x8d\x88i\xcb\x85rmp
# \x85\xfe\xafM\x0c)\xd5\xeb\xaf'''
print repr(ciphertext)
# The cipher-block chaining mode of operation maintains state, so
# decryption requires a new instance be created
aes = pyaes.AESModeOfOperationCFB(key, iv = iv, segment_size = 8)
decrypted = aes.decrypt(ciphertext)
# True
print decrypted == plaintext
```
#### 输出反馈模式
```
aes = pyaes.AESModeOfOperationOFB(key, iv = iv)
plaintext = "Text may be any length you wish, no padding is required"
ciphertext = aes.encrypt(plaintext)
# '''v\xa9\xc1wO\x92^\x9e\rR\x1e\xf7\xb1\xa2\x9d"l1\xc7\xe7\x9d\x87(\xc26s
# \xdd8\xc8@\xb6\xd9!\xf5\x0cM\xaa\x9b\xc4\xedLD\xe4\xb9\xd8\xdf\x9e\xac
# \xa1\xb8\xea\x0f\x8ev\xb5'''
print repr(ciphertext)
# The counter mode of operation maintains state, so decryption requires
# a new instance be created
aes = pyaes.AESModeOfOperationOFB(key, iv = iv)
decrypted = aes.decrypt(ciphertext)
# True
print decrypted == plaintext
```
#### 电子密码本模式 (不推荐)
```
aes = pyaes.AESModeOfOperationECB(key)
plaintext = "TextMustBe16Byte"
ciphertext = aes.encrypt(plaintext)
# 'L6\x95\x85\xe4\xd9\xf1\x8a\xfb\xe5\x94X\x80|\x19\xc3'
print repr(ciphertext)
# Since there is no state stored in this mode of operation, it
# is not necessary to create a new aes object for decryption.
#aes = pyaes.AESModeOfOperationECB(key)
decrypted = aes.decrypt(ciphertext)
# True
print decrypted == plaintext
```
### BlockFeeder
由于大多数操作模式要求数据必须是特定的分组大小或段大小,因此在处理大型任意流或数据字符串时可能会很困难。
BlockFeeder 类旨在为您简化这一过程,它通过缓冲多次调用中的字节并在可用时返回字节,以及在完成时根据需要填充或剥离输出。
```
import pyaes
# Any mode of operation can be used; for this example CBC
key = "This_key_for_demo_purposes_only!"
iv = "InitializationVe"
ciphertext = ''
# We can encrypt one line at a time, regardles of length
encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(key, iv))
for line in file('/etc/passwd'):
ciphertext += encrypter.feed(line)
# Make a final call to flush any remaining bytes and add paddin
ciphertext += encrypter.feed()
# We can decrypt the cipher text in chunks (here we split it in half)
decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(key, iv))
decrypted = decrypter.feed(ciphertext[:len(ciphertext) / 2])
decrypted += decrypter.feed(ciphertext[len(ciphertext) / 2:])
# Again, make a final call to flush any remaining bytes and strip padding
decrypted += decrypter.feed()
print file('/etc/passwd').read() == decrypted
```
### 流输入器 (Stream Feeder)
这旨在让流和大型文件的加密和解密变得更加容易。
```
import pyaes
# Any mode of operation can be used; for this example CTR
key = "This_key_for_demo_purposes_only!"
# Create the mode of operation to encrypt with
mode = pyaes.AESModeOfOperationCTR(key)
# The input and output files
file_in = file('/etc/passwd')
file_out = file('/tmp/encrypted.bin', 'wb')
# Encrypt the data as a stream, the file is read in 8kb chunks, be default
pyaes.encrypt_stream(mode, file_in, file_out)
# Close the files
file_in.close()
file_out.close()
```
解密过程类似,只是您需要使用 `pyaes.decrypt_stream`,并且加密文件将作为 `file_in`,解密目标作为 `file_out`。
### AES 分组密码
通常您应该使用上述操作模式之一。但这可能对试验自定义操作模式或处理加密块很有用。
分组密码需要且仅需要一个数据块来进行加密或解密,并且每个块都应该是一个数组,其中每个元素都是一个字节的整数表示。
```
import pyaes
# 16 byte block of plain text
plaintext = "Hello World!!!!!"
plaintext_bytes = [ ord(c) for c in plaintext ]
# 32 byte key (256 bit)
key = "This_key_for_demo_purposes_only!"
# Our AES instance
aes = pyaes.AES(key)
# Encrypt!
ciphertext = aes.encrypt(plaintext_bytes)
# [55, 250, 182, 25, 185, 208, 186, 95, 206, 115, 50, 115, 108, 58, 174, 115]
print repr(ciphertext)
# Decrypt!
decrypted = aes.decrypt(ciphertext)
# True
print decrypted == plaintext_bytes
```
## 什么是密钥?
这似乎是许多加密新手感到困惑的一点。您可以将密钥视为*“密码”*。然而,这些算法要求*“密码”*具有特定的长度。
对于 AES,有三种可能的密钥长度:16 字节、24 字节或 32 字节。当您创建 AES 对象时,密钥大小会被自动检测,因此传入正确长度的密钥非常重要。
通常,您希望提供任意长度的密码,例如易于记忆或写下的内容。在这些情况下,您必须想出一种方法将密码转换为特定长度的密钥。**基于密码的密钥派生函数** (PBKDF) 正是为此目的而设计的算法。
这是一个使用流行的(可能已过时?)*crypt* PBKDF 的示例:
```
# See: https://www.dlitz.net/software/python-pbkdf2/
import pbkdf2
password = "HelloWorld"
# The crypt PBKDF returns a 48-byte string
key = pbkdf2.crypt(password)
# A 16-byte, 24-byte and 32-byte key, respectively
key_16 = key[:16]
key_24 = key[:24]
key_32 = key[:32]
```
[scrypt](https://github.com/ricmoo/pyscrypt) PBKDF 是故意设计得很慢的,以增加暴力破解密码的难度:
```
# See: https://github.com/ricmoo/pyscrypt
import pyscrypt
password = "HelloWorld"
# Salt is required, and prevents Rainbow Table attacks
salt = "SeaSalt"
# N, r, and p are parameters to specify how difficult it should be to
# generate a key; bigger numbers take longer and more memory
N = 1024
r = 1
p = 1
# A 16-byte, 24-byte and 32-byte key, respectively; the scrypt algorithm takes
# a 6-th parameter, indicating key length
key_16 = pyscrypt.hash(password, salt, N, r, p, 16)
key_24 = pyscrypt.hash(password, salt, N, r, p, 24)
key_32 = pyscrypt.hash(password, salt, N, r, p, 32)
```
另一种可能性是使用哈希函数(例如 SHA256)对密码进行哈希处理,但除非您使用 [salt(盐值)](http://en.wikipedia.org/wiki/Salt_(cryptography),否则此方法可能容易受到 [彩虹攻击](http://en.wikipedia.org/wiki/Rainbow_table))。
```
import hashlib
password = "HelloWorld"
# The SHA256 hash algorithm returns a 32-byte string
hashed = hashlib.sha256(password).digest()
# A 16-byte, 24-byte and 32-byte key, respectively
key_16 = hashed[:16]
key_24 = hashed[:24]
key_32 = hashed
```
## 性能
_/tests/test-aes.py_ 中提供了一个测试用例,用于进行一些基本的性能测试(其主要目的更多是作为回归测试)。
根据该测试,在 **CPython** 中,对于 CBC、ECB 和 OFB 模式,该库比 [PyCrypto](https://www.dlitz.net/software/pycrypto/) 慢约 30 倍;对于 CFB 模式慢约 80 倍;对于 CTR 模式慢约 300 倍。
根据同一测试,在 **Pypy** 中,对于 CBC、ECB 和 OFB 模式,该库比 [PyCrypto](https://www.dlitz.net/software/pycrypto/) 慢约 4 倍;对于 CFB 模式慢约 12 倍;对于 CTR 模式慢约 19 倍。
PyCrypto 文档提到,计数器调用是导致计数器 (CTR) 操作模式速度问题的原因,这就是为什么他们使用了经过特殊优化的计数器。我将在未来进一步研究这个问题。
## 常见问题解答
#### 为什么要这样做?
简短的回答是,*为什么不呢?*
更长的回答是,这是为了我的 [pyscrypt](https://github.com/ricmoo/pyscrypt) 库。我需要一个支持 256 位密钥和计数器 (CTR) 操作模式的纯 Python AES 实现。经过搜索,我发现了几个实现,但都缺少 CTR 或仅支持 128 位密钥。在深入学习了 AES 并实现了该库之后,将其封装为一个更通用的解决方案只需额外付出少量工作。所以,*为什么不呢?*
#### 如何添加我的问题?
如有任何问题、建议、评论等,请发送电子邮件至 pyaes@ricmoo.com。
#### 我可以给你钱吗?
嗯……好吧?:-)
_Bitcoin_ - `18UDs4qV1shu2CgTS2tKojhCtM69kpnWg9`
标签:AES, BlockFeeder, CBC, CFB, CTR, DNS 反向解析, ECB, HTTP工具, meg, OFB, PyPI, Python, 信息安全, 分组密码, 加密, 加密库, 子域名变形, 密码学, 对称加密, 手动系统调用, 无依赖, 无后门, 流加密, 漏洞扫描器, 纯Python实现, 自动化审计, 解密, 逆向工具