430 lines
16 KiB
Python
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
|