Files
kefu/wechat_rpa/.venv/Lib/site-packages/qcloud_cos/crypto.py
T
2026-09-21 10:34:06 +08:00

430 lines
16 KiB
Python

# -*- coding: utf-8 -*-
import os
import random
import logging
import copy
import base64
import struct
from .cos_comm import *
from Crypto.Cipher import AES
from Crypto.PublicKey import RSA
from Crypto import Random
from Crypto.Util import Counter
from Crypto.Cipher import PKCS1_OAEP, PKCS1_v1_5
from .cos_exception import CosClientError
from .streambody import StreamBody
from abc import ABCMeta, abstractmethod
logger = logging.getLogger(__name__)
_AES_CTR_COUNTER_BITS_LENGTH = 8 * 16
_AES_256_KEY_SIZE = 32
def random_key(key_len):
return Random.new().read(key_len)
def random_iv():
iv = Random.new().read(16)
return iv
def iv_to_big_int(iv):
iv_pair = struct.unpack(">QQ", iv)
iv_int = iv_pair[0] << 64 | iv_pair[1]
return iv_int
class AESCTRCipher(object):
"""数据加密类,用于加密用户数据"""
def __init__(self):
"""初始化"""
self.__block_size_len = AES.block_size
self.__cipher = None
self.__key_len = _AES_256_KEY_SIZE
def new_cipher(self, key, start, offset=0):
"""初始化AES加解密对象
:param key(string): 对称密钥
:param start(int): 对称加密初始随机值
:param offset(int): 主要用于解密,想要解密文本的偏移,必须为16的整数倍
"""
block_index_offset = self.__calc_offset(offset)
new_start = start + block_index_offset
my_counter = Counter.new(_AES_CTR_COUNTER_BITS_LENGTH, initial_value=new_start)
self.__cipher = AES.new(key, AES.MODE_CTR, counter=my_counter)
def encrypt(self, plaintext):
"""加密数据
:param plaintext(string): 需要加密的数据
"""
if self.__cipher is None:
raise CosClientError('cipher is not initialized')
return self.__cipher.encrypt(plaintext)
def decrypt(self, plaintext):
"""解密数据
:param plaintext(string): 需要解密的数据,数据的起始位置必须为16的整数倍
"""
if self.__cipher is None:
raise CosClientError('cipher is not initialized')
return self.__cipher.decrypt(plaintext)
# offset 必须为block_size的整数倍
def __calc_offset(self, offset):
"""通过文本偏移计算counter的偏移
:param offset(int): 文本偏移
"""
if not self.__is_block_aligned(offset):
raise CosClientError('offset is not align to encrypt block')
return offset // self.__block_size_len
def __is_block_aligned(self, offset):
"""判断文本偏移是否是block_size对齐
:param offset(int): 文本偏移
"""
if offset is None:
offset = 0
return 0 == offset % self.__block_size_len
def adjust_read_offset(self, offset):
"""用于调整读取的offset为block_size对齐"""
if offset:
offset = (offset // self.__block_size_len) * self.__block_size_len
return offset
def get_key(self):
"""获取随机密钥"""
return random_key(self.__key_len)
def get_counter_iv(self):
"""获取对称加密初始随机值"""
return random_iv()
class RSAKeyPair:
"""封装有公钥和私钥"""
def __init__(self, public_key, private_key):
self.publick_key = public_key
self.private_key = private_key
class RSAKeyPairPath:
"""封装有公钥和私钥的路径"""
def __init__(self, public_key_path, private_key_path):
self.public_key_path = public_key_path
self.private_key_path = private_key_path
class BaseProvider(object):
"""客户端主密钥加密基类"""
def __init__(self, cipher):
"""初始化
:param cipher(an AES object): 数据加解密类
"""
self.data_cipher = cipher
def get_data_key(self):
"""随机获取数据加解密密钥"""
return self.data_cipher.get_key()
@abstractmethod
def init_data_cipher(self):
"""初始化cipher"""
pass
@abstractmethod
def init_data_cipter_by_user(self, encrypt_key, encrypt_iv, offset=0):
"""根据密钥初始化cipher"""
pass
def adjust_read_offset(self, start):
"""用于调整读取的offset为block_size对齐"""
return self.data_cipher.adjust_read_offset(start)
def make_data_encrypt_adapter(self, stream):
"""创建数据流加密适配器"""
size = 0
if hasattr(stream, '__len__'):
size = len(stream)
elif hasattr(stream, 'tell') and hasattr(stream, 'seek'):
current = stream.tell()
stream.seek(0, os.SEEK_END)
size = stream.tell()
stream.seek(current, os.SEEK_SET)
else:
return None
return DataEncryptAdapter(stream, size, copy.copy(self.data_cipher))
def make_data_decrypt_adapter(self, rt, offset):
"""创建数据流解密适配器"""
return DataDecryptAdapter(rt, copy.copy(self.data_cipher), offset)
class RSAProvider(BaseProvider):
"""客户端非对称主密钥加密类"""
def __init__(self, key_pair_info=None, cipher=AESCTRCipher(), passphrase=None):
"""初始化"""
super(RSAProvider, self).__init__(cipher=cipher)
default_rsa_dir = os.path.expanduser('~/.cos_local_rsa')
default_public_key_path = os.path.join(default_rsa_dir, '.public_key.pem')
default_private_key_path = os.path.join(default_rsa_dir, '.private_key.pem')
self.__encrypt_obj = None
self.__decrypt_obj = None
self.__data_key = None
self.__data_iv = None
if isinstance(key_pair_info, RSAKeyPair):
self.__encrypt_obj = PKCS1_v1_5.new(RSA.importKey(key_pair_info.publick_key, passphrase=passphrase))
self.__decrypt_obj = PKCS1_v1_5.new(RSA.importKey(key_pair_info.private_key, passphrase=passphrase))
elif isinstance(key_pair_info, RSAKeyPairPath):
self.__encrypt_obj, self.__decrypt_obj = self.__get_key_by_path(key_pair_info.public_key_path,
key_pair_info.private_key_path, passphrase)
else:
logger.info('key_pair_info is None, try to get key from default path')
if os.path.exists(default_private_key_path) and os.path.exists(default_public_key_path):
self.__encrypt_obj, self.__decrypt_obj = self.__get_key_by_path(default_public_key_path,
default_private_key_path, passphrase)
# 为用户自动创建rsa
if self.__encrypt_obj is None and self.__decrypt_obj is None:
logger.warning('fail to get rsa key, will generate key')
private_key = RSA.generate(2048)
public_key = private_key.publickey()
self.__encrypt_obj = PKCS1_OAEP.new(public_key)
self.__decrypt_obj = PKCS1_OAEP.new(private_key)
if not os.path.exists(default_rsa_dir):
os.makedirs(default_rsa_dir)
with open(default_private_key_path, 'wb') as f:
f.write(private_key.exportKey(passphrase=passphrase))
with open(default_public_key_path, 'wb') as f:
f.write(public_key.exportKey(passphrase=passphrase))
@staticmethod
def get_rsa_key_pair(public_key, private_key):
"""开放给用户,用于生成RSAKeyPair"""
if public_key is None or private_key is None:
raise CosClientError('public_key or private_key is not allowed to be None !!!')
return RSAKeyPair(public_key, private_key)
@staticmethod
def get_rsa_key_pair_path(public_key_path, private_key_path):
"""开放给用户,用于生成RSAKeyPairPath"""
if public_key_path is None or private_key_path is None:
raise CosClientError('public_key or private_key is not allowed to be None !!!')
return RSAKeyPairPath(public_key_path, private_key_path)
def __get_key_by_path(self, public_path=None, private_path=None, passphrase=None):
"""用于从提供的路径中获取公钥和私钥"""
if public_path is None or private_path is None:
return None, None
encrypt_obj, decrypt_obj = None, None
if os.path.exists(public_path) and os.path.exists(private_path):
with open(public_path, 'rb') as f:
encrypt_obj = PKCS1_OAEP.new(RSA.importKey(f.read(), passphrase=passphrase))
with open(private_path, 'rb') as f:
decrypt_obj = PKCS1_OAEP.new(RSA.importKey(f.read(), passphrase=passphrase))
return encrypt_obj, decrypt_obj
def init_data_cipher(self):
"""初始化cipher"""
encrypt_key = None
encrypt_iv = None
self.__data_key = self.get_data_key()
self.__data_iv = self.data_cipher.get_counter_iv()
start = iv_to_big_int(self.__data_iv)
self.data_cipher.new_cipher(self.__data_key, start)
encrypt_key = self.__encrypt_obj.encrypt(self.__data_key)
encrypt_iv = self.__encrypt_obj.encrypt(self.__data_iv)
return encrypt_key, encrypt_iv
def init_data_cipter_by_user(self, encrypt_key, encrypt_iv, offset=0):
"""根据密钥初始化cipher"""
self.__data_key = self.__decrypt_obj.decrypt(encrypt_key)
self.__data_iv = self.__decrypt_obj.decrypt(encrypt_iv)
start = iv_to_big_int(self.__data_iv)
self.data_cipher.new_cipher(self.__data_key, start, offset)
class AESProvider(BaseProvider):
"""客户端对称主密钥加密类"""
def __init__(self, aes_key=None, aes_key_path=None, cipher=AESCTRCipher()):
"""初始化"""
super(AESProvider, self).__init__(cipher=cipher)
self.__ed_obj = None
self.__data_key = None
self.__data_iv = None
self.__my_counter = Counter.new(_AES_CTR_COUNTER_BITS_LENGTH, initial_value=0)
self.__aes_key = aes_key
self.__aes_key_path = aes_key_path
def init_ed_obj(self):
default_aes_dir = os.path.expanduser('~/.cos_local_aes')
default_key_path = os.path.join(default_aes_dir, '.aes_key.pem')
if self.__aes_key:
aes_key = to_bytes(base64.b64decode(to_bytes(self.__aes_key)))
self.__ed_obj = AES.new(aes_key, AES.MODE_CTR, counter=self.__my_counter)
elif self.__aes_key_path:
if os.path.exists(self.__aes_key_path):
with open(self.__aes_key_path, 'rb') as f:
aes_key = f.read()
aes_key = to_bytes(base64.b64decode(to_bytes(aes_key)))
self.__ed_obj = AES.new(aes_key, AES.MODE_CTR, counter=self.__my_counter)
else:
logger.info('aes_key and aes_key_path is None, try to get key from default path')
if os.path.exists(default_key_path):
with open(default_key_path, 'rb') as f:
aes_key = f.read()
aes_key = to_bytes(base64.b64decode(to_bytes(aes_key)))
self.__ed_obj = AES.new(aes_key, AES.MODE_CTR, counter=self.__my_counter)
if self.__ed_obj is None:
logger.warning('fail to get aes key, will generate key')
aes_key = random_key(_AES_256_KEY_SIZE)
self.__ed_obj = AES.new(aes_key, AES.MODE_CTR, counter=self.__my_counter)
if not os.path.exists(default_aes_dir):
os.makedirs(default_aes_dir)
with open(default_key_path, 'wb') as f:
aes_key = to_bytes(base64.b64encode(to_bytes(aes_key)))
f.write(aes_key)
def init_data_cipher(self):
"""初始化cipher"""
encrypt_key = None
encrypt_iv = None
self.__data_key = self.get_data_key()
self.__data_iv = self.data_cipher.get_counter_iv()
start = iv_to_big_int(self.__data_iv)
self.data_cipher.new_cipher(self.__data_key, start)
self.init_ed_obj()
encrypt_key = self.__ed_obj.encrypt(self.__data_key)
encrypt_iv = self.__ed_obj.encrypt(self.__data_iv)
return encrypt_key, encrypt_iv
def init_data_cipter_by_user(self, encrypt_key, encrypt_iv, offset=0):
"""根据密钥初始化cipher"""
self.init_ed_obj()
self.__data_key = self.__ed_obj.decrypt(encrypt_key)
self.__data_iv = self.__ed_obj.decrypt(encrypt_iv)
start = iv_to_big_int(self.__data_iv)
self.data_cipher.new_cipher(self.__data_key, start, offset)
class MetaHandle(object):
"""用于获取/生成加密的元信息"""
def __init__(self, encrypt_key=None, encrypt_iv=None):
"""初始化
:param encrypt_key(string): 加密的数据密钥
:param encrypt_iv(bytes): 加密counter的初始值
"""
self.__encrypt_key = encrypt_key
self.__encrypt_iv = encrypt_iv
def set_object_meta(self, headers):
"""设置加密元信息到object的头部"""
meta_data = dict()
meta_data['x-cos-meta-client-side-encryption-key'] = to_bytes(base64.b64encode(to_bytes(self.__encrypt_key)))
meta_data['x-cos-meta-client-side-encryption-iv'] = to_bytes(base64.b64encode(to_bytes(self.__encrypt_iv)))
headers['Metadata'] = meta_data
return headers
def get_object_meta(self, headers):
"""从object的头部获取加密元信息"""
if 'x-cos-meta-client-side-encryption-key' in headers and 'x-cos-meta-client-side-encryption-iv' in headers:
self.__encrypt_key = base64.b64decode(to_bytes(headers['x-cos-meta-client-side-encryption-key']))
self.__encrypt_iv = base64.b64decode(to_bytes(headers['x-cos-meta-client-side-encryption-iv']))
return self.__encrypt_key, self.__encrypt_iv
class DataEncryptAdapter(object):
"""用于读取经过加密后的的数据"""
def __init__(self, data, content_len, data_cipher):
self._data = to_bytes(data)
self._data_cipher = data_cipher
self._content_len = content_len
self._read_len = 0
@property
def len(self):
return self._content_len
def read(self, length):
"""读取加密后的数据"""
if self._read_len >= self._content_len:
return ''
if length is None or length < 0:
bytes_to_read = self._content_len - self._read_len
else:
bytes_to_read = min(length, self._content_len - self._read_len)
if isinstance(self._data, bytes):
content = self._data[self._read_len:self._read_len + bytes_to_read]
else:
content = self._data.read(bytes_to_read)
self._read_len += bytes_to_read
content = self._data_cipher.encrypt(content)
return content
class DataDecryptAdapter(StreamBody):
"""用于读取经过解密后的数据"""
def __init__(self, rt, data_cipher, offset=0):
"""初始化
:param rt(request object): request请求返回的对象
:param data_cipher(an AES object): 数据加解密类
:param offset(int): 数据读取的偏移量
"""
super(DataDecryptAdapter, self).__init__(rt)
self._data_cipher = data_cipher
self._offset = offset
def read(self, length, auto_decompress=False):
"""读取解密后的数据"""
if self._read_len >= self._content_len:
return ''
if self._use_encoding and not auto_decompress:
content = self._rt.raw.read(length)
else:
try:
content = next(self._rt.iter_content(length))
except StopIteration:
return ''
content = self._data_cipher.decrypt(content)
if self._read_len < self._offset and self._read_len + len(content) > self._offset:
content = content[self._offset:]
self._read_len = self._offset
return content