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实现, 自动化审计, 解密, 逆向工具