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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| import io import base64 from os import urandom from hashlib import md5 from Crypto.Cipher import AES
"""pip3 install pycryptodome"""
class AESCipher: """ NOTE 这种方法现在已经不是很安全,只是为了与 `openssl` 命令相兼容,方便脚本解密 使用内存 BytesIO 写入加密、解密内容,模拟文件进行加密、解密,主要用到 AES-CBC 算法,加 salt 参考: https://stackoverflow.com/questions/16761458/how-to-decrypt-openssl-aes-encrypted-files-in-python """ block_size = AES.block_size
def __init__(self, content, password, salt_header='Salted__', key_length=32): """ 注意: 若需要支持 `openssl` 命令,必须指定 salt_header='Salted__' 如果只是 python 内部使用 salt_header 可以为空 """ self.content = content self.password = password self.salt_header = salt_header self.key_length = key_length
def derive_key_and_iv(self, salt): """计算得到 `key` 和 `iv` """ d = d_i = b'' while len(d) < self.key_length + self.block_size: d_i = md5(d_i + str.encode(self.password) + salt).digest() d += d_i return d[:self.key_length], d[self.key_length:self.key_length + self.block_size]
def encrypt(self) -> bytes: """ 返回的是加密后的 `base64` 二进制字符串,bytes 对应 `openssl` 命令解法: 从文件:openssl aes-256-cbc -salt -in secret.txt -d -a -k 'password' 从输入:echo "U2FsdGVkX1/5sFe6z6+H4CfQvnTZgCEV4yget0PI8XM=" | openssl aes-256-cbc -salt -d -a -k 'password' """ content = self.content.encode() if not isinstance(self.content, bytes) else self.content in_file = io.BytesIO(content) out_file = io.BytesIO() salt = urandom(self.block_size - len(self.salt_header)) key, iv = self.derive_key_and_iv(salt) cipher = AES.new(key, AES.MODE_CBC, iv) out_file.write(str.encode(self.salt_header) + salt) finished = False while not finished: chunk = in_file.read(1024 * self.block_size) if len(chunk) == 0 or len(chunk) % self.block_size != 0: padding_length = (self.block_size - len(chunk) % self.block_size) or self.block_size chunk += str.encode( padding_length * chr(padding_length)) finished = True out_file.write(cipher.encrypt(chunk)) return base64.b64encode(out_file.getvalue())
def decrypt(self, content=None) -> bytes: """ 密文 content 可以传入,使用实例对象的密码,返回解密后的明文 bytes 错误的密码、salt_header、key_size解密将报错 openssl 加密命令 从文件:openssl aes-256-cbc -salt -in text.txt -a -k 'password' 从输入:echo "abc" | openssl aes-256-cbc -salt -a -k 'password' """ content = content if content else self.content content = content.encode() if not isinstance(content, bytes) else content text = base64.b64decode(content) in_file = io.BytesIO(text) out_file = io.BytesIO() salt = in_file.read(self.block_size)[len(self.salt_header):] key, iv = self.derive_key_and_iv(salt) cipher = AES.new(key, AES.MODE_CBC, iv) next_chunk = b'' finished = False while not finished: chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * self.block_size)) if len(next_chunk) == 0: padding_length = chunk[-1] if padding_length < 1 or padding_length > self.block_size: raise ValueError("bad decrypt pad (%d)" % padding_length) chunk = chunk[:-padding_length] finished = True out_file.write(chunk) return out_file.getvalue()
if __name__ == '__main__': print("++++++++++++++++++++++++++++++++++++++++++") aes = AESCipher("hello world2223", '111111') secret = aes.encrypt() raw = aes.decrypt(secret) print("密文:", secret) print("明文:", raw) print("++++++++++++++++++++++++++++++++++++++++++") try: aes2 = AESCipher("", '222', salt_header='') res = aes2.decrypt(secret) print(res) except ValueError: print("密码错了!")
|