You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

403 lines
15KB

  1. # -*- coding: utf-8 -*-
  2. """
  3. oss2.encryption
  4. ~~~~~~~~~~~~~~
  5. 该模块包含了客户端加解密相关的函数和类。
  6. """
  7. import abc
  8. import json
  9. import os
  10. import copy
  11. import logging
  12. from functools import partial
  13. import six
  14. from Crypto.Cipher import PKCS1_OAEP, PKCS1_v1_5
  15. from Crypto.PublicKey import RSA
  16. from aliyunsdkcore import client
  17. from aliyunsdkcore.acs_exception.exceptions import ServerException, ClientException
  18. from aliyunsdkcore.http import format_type, method_type
  19. from aliyunsdkkms.request.v20160120 import GenerateDataKeyRequest, DecryptRequest, EncryptRequest
  20. from loguru import logger
  21. from . import models
  22. from . import headers
  23. from . import utils
  24. from .utils import b64decode_from_string, b64encode_as_string
  25. from .compat import to_unicode
  26. from .exceptions import ClientError, OpenApiFormatError, OpenApiServerError
  27. class EncryptionMaterials(object):
  28. def __init__(self, desc, key_pair=None, custom_master_key_id=None, passphrase=None):
  29. self.desc = {}
  30. if desc:
  31. if isinstance(desc, dict):
  32. self.desc = desc
  33. else:
  34. raise ClientError('Invalid type, the type of mat_desc must be dict!')
  35. if key_pair and custom_master_key_id:
  36. raise ClientError('Both key_pair and custom_master_key_id are not none')
  37. if key_pair and not isinstance(key_pair, dict):
  38. raise ClientError('Invalid type, the type of key_pair must be dict!')
  39. self.key_pair = key_pair
  40. self.custom_master_key_id = custom_master_key_id
  41. self.passphrase = passphrase
  42. def add_description(self, key, value):
  43. self.desc[key] = value
  44. def add_descriptions(self, descriptions):
  45. for key in descriptions:
  46. self.desc[key] = descriptions[key]
  47. @six.add_metaclass(abc.ABCMeta)
  48. class BaseCryptoProvider(object):
  49. """CryptoProvider 基类,提供基础的数据加密解密adapter
  50. """
  51. def __init__(self, cipher, mat_desc=None):
  52. if not cipher:
  53. raise ClientError('Please initialize the value of cipher!')
  54. self.cipher = cipher
  55. self.cek_alg = None
  56. self.wrap_alg = None
  57. self.mat_desc = None
  58. self.encryption_materials_dict = {}
  59. if mat_desc:
  60. if isinstance(mat_desc, dict):
  61. self.mat_desc = mat_desc
  62. else:
  63. raise ClientError('Invalid type, the type of mat_desc must be dict!')
  64. @abc.abstractmethod
  65. def get_key(self):
  66. pass
  67. def get_iv(self):
  68. return self.cipher.get_iv()
  69. @staticmethod
  70. def make_encrypt_adapter(stream, cipher):
  71. return utils.make_cipher_adapter(stream, partial(cipher.encrypt))
  72. @staticmethod
  73. def make_decrypt_adapter(stream, cipher, discard=0):
  74. return utils.make_cipher_adapter(stream, partial(cipher.decrypt), discard)
  75. @abc.abstractmethod
  76. def decrypt_encrypted_key(self, encrypted_key):
  77. pass
  78. @abc.abstractmethod
  79. def decrypt_encrypted_iv(self, encrypted_iv):
  80. pass
  81. @abc.abstractmethod
  82. def reset_encryption_materials(self, encryption_materials):
  83. pass
  84. def adjust_range(self, start, end):
  85. return self.cipher.adjust_range(start, end)
  86. @abc.abstractmethod
  87. def create_content_material(self):
  88. pass
  89. def add_encryption_materials(self, encryption_materials):
  90. if encryption_materials.desc:
  91. key = frozenset(encryption_materials.desc.items())
  92. self.encryption_materials_dict[key] = encryption_materials
  93. def get_encryption_materials(self, desc):
  94. if desc:
  95. key = frozenset(desc.items())
  96. if key in self.encryption_materials_dict.keys():
  97. return self.encryption_materials_dict[key]
  98. _LOCAL_RSA_TMP_DIR = '.oss-local-rsa'
  99. @six.add_metaclass(abc.ABCMeta)
  100. class LocalRsaProvider(BaseCryptoProvider):
  101. """使用本地RSA加密数据密钥。
  102. :param str dir: 本地RSA公钥私钥存储路径
  103. :param str key: 本地RSA公钥私钥名称前缀
  104. :param str passphrase: 本地RSA公钥私钥密码
  105. :param class cipher: 数据加密,默认aes256,用户可自行实现对称加密算法,需符合AESCipher注释规则
  106. """
  107. DEFAULT_PUB_KEY_SUFFIX = '.public_key.pem'
  108. DEFAULT_PRIV_KEY_SUFFIX = '.private_key.pem'
  109. def __init__(self, dir=None, key='', passphrase=None, cipher=utils.AESCTRCipher(),
  110. pub_key_suffix=DEFAULT_PUB_KEY_SUFFIX, private_key_suffix=DEFAULT_PRIV_KEY_SUFFIX):
  111. super(LocalRsaProvider, self).__init__(cipher=cipher)
  112. self.wrap_alg = headers.RSA_NONE_OAEPWithSHA1AndMGF1Padding
  113. keys_dir = dir or os.path.join(os.path.expanduser('~'), _LOCAL_RSA_TMP_DIR)
  114. priv_key_path = os.path.join(keys_dir, key + private_key_suffix)
  115. pub_key_path = os.path.join(keys_dir, key + pub_key_suffix)
  116. try:
  117. if os.path.exists(priv_key_path) and os.path.exists(pub_key_path):
  118. with open(priv_key_path, 'rb') as f:
  119. self.__decrypt_obj = PKCS1_OAEP.new(RSA.importKey(f.read(), passphrase=passphrase))
  120. with open(pub_key_path, 'rb') as f:
  121. self.__encrypt_obj = PKCS1_OAEP.new(RSA.importKey(f.read(), passphrase=passphrase))
  122. else:
  123. logger.warning('The file path of private key or public key is not exist, will generate key pair')
  124. private_key = RSA.generate(2048)
  125. public_key = private_key.publickey()
  126. self.__encrypt_obj = PKCS1_OAEP.new(public_key)
  127. self.__decrypt_obj = PKCS1_OAEP.new(private_key)
  128. utils.makedir_p(keys_dir)
  129. with open(priv_key_path, 'wb') as f:
  130. f.write(private_key.exportKey(passphrase=passphrase))
  131. with open(pub_key_path, 'wb') as f:
  132. f.write(public_key.exportKey(passphrase=passphrase))
  133. except (ValueError, TypeError, IndexError) as e:
  134. raise ClientError(str(e))
  135. def get_key(self):
  136. return self.cipher.get_key()
  137. def decrypt_encrypted_key(self, encrypted_key):
  138. try:
  139. return self.__decrypt_data(encrypted_key)
  140. except (TypeError, ValueError) as e:
  141. raise ClientError(str(e))
  142. def decrypt_encrypted_iv(self, encrypted_iv):
  143. try:
  144. return self.__decrypt_data(encrypted_iv)
  145. except (TypeError, ValueError) as e:
  146. raise ClientError(str(e))
  147. def reset_encryption_materials(self, encryption_materials):
  148. raise ClientError("do not support reset_encryption_materials!")
  149. def create_content_material(self):
  150. plain_key = self.get_key()
  151. encrypted_key = self.__encrypt_data(plain_key)
  152. plain_iv = self.get_iv()
  153. encrypted_iv = self.__encrypt_data(plain_iv)
  154. cipher = copy.copy(self.cipher)
  155. wrap_alg = self.wrap_alg
  156. mat_desc = self.mat_desc
  157. cipher.initialize(plain_key, plain_iv)
  158. content_crypto_material = models.ContentCryptoMaterial(cipher, wrap_alg, encrypted_key, encrypted_iv,
  159. mat_desc)
  160. return content_crypto_material
  161. def __encrypt_data(self, data):
  162. return self.__encrypt_obj.encrypt(data)
  163. def __decrypt_data(self, data):
  164. return self.__decrypt_obj.decrypt(data)
  165. @six.add_metaclass(abc.ABCMeta)
  166. class RsaProvider(BaseCryptoProvider):
  167. """使用本地RSA加密数据密钥。
  168. :param str dir: 本地RSA公钥私钥存储路径
  169. :param str key: 本地RSA公钥私钥名称前缀
  170. :param str passphrase: 本地RSA公钥私钥密码
  171. :param class cipher: 数据加密,默认aes256,用户可自行实现对称加密算法,需符合AESCipher注释规则
  172. """
  173. def __init__(self, key_pair, passphrase=None, cipher=utils.AESCTRCipher(), mat_desc=None):
  174. super(RsaProvider, self).__init__(cipher=cipher, mat_desc=mat_desc)
  175. self.wrap_alg = headers.RSA_NONE_PKCS1Padding_WRAP_ALGORITHM
  176. if key_pair and not isinstance(key_pair, dict):
  177. raise ClientError('Invalid type, the type of key_pair must be dict!')
  178. try:
  179. if 'public_key' in key_pair:
  180. self.__encrypt_obj = PKCS1_v1_5.new(RSA.importKey(key_pair['public_key'], passphrase=passphrase))
  181. if 'private_key' in key_pair:
  182. self.__decrypt_obj = PKCS1_v1_5.new(RSA.importKey(key_pair['private_key'], passphrase=passphrase))
  183. except (ValueError, TypeError) as e:
  184. raise ClientError(str(e))
  185. def get_key(self):
  186. return self.cipher.get_key()
  187. def decrypt_encrypted_key(self, encrypted_key):
  188. try:
  189. return self.__decrypt_data(encrypted_key)
  190. except (TypeError, ValueError) as e:
  191. raise ClientError(str(e))
  192. def decrypt_encrypted_iv(self, encrypted_iv):
  193. try:
  194. return self.__decrypt_data(encrypted_iv)
  195. except (TypeError, ValueError) as e:
  196. raise ClientError(str(e))
  197. def reset_encryption_materials(self, encryption_materials):
  198. return RsaProvider(encryption_materials.key_pair, encryption_materials.passphrase, self.cipher,
  199. encryption_materials.desc)
  200. def create_content_material(self):
  201. plain_key = self.get_key()
  202. encrypted_key = self.__encrypt_data(plain_key)
  203. plain_iv = self.get_iv()
  204. encrypted_iv = self.__encrypt_data(plain_iv)
  205. cipher = copy.copy(self.cipher)
  206. wrap_alg = self.wrap_alg
  207. mat_desc = self.mat_desc
  208. cipher.initialize(plain_key, plain_iv)
  209. content_crypto_material = models.ContentCryptoMaterial(cipher, wrap_alg, encrypted_key, encrypted_iv,
  210. mat_desc)
  211. return content_crypto_material
  212. def __encrypt_data(self, data):
  213. return self.__encrypt_obj.encrypt(data)
  214. def __decrypt_data(self, data):
  215. decrypted_data = self.__decrypt_obj.decrypt(data, object)
  216. if decrypted_data == object:
  217. raise ClientError('Decrypted data error, please check you key pair!')
  218. return decrypted_data
  219. class AliKMSProvider(BaseCryptoProvider):
  220. """使用aliyun kms服务加密数据密钥。kms的详细说明参见
  221. https://help.aliyun.com/product/28933.html?spm=a2c4g.11186623.3.1.jlYT4v
  222. 此接口在py3.3下暂时不可用,详见
  223. https://github.com/aliyun/aliyun-openapi-python-sdk/issues/61
  224. :param str access_key_id: 可以访问kms密钥服务的access_key_id
  225. :param str access_key_secret: 可以访问kms密钥服务的access_key_secret
  226. :param str region: kms密钥服务地区
  227. :param str cmkey: 用户主密钥
  228. :param str sts_token: security token,如果使用的是临时AK需提供
  229. :param str passphrase: kms密钥服务密码
  230. :param class cipher: 数据加密,默认aes256,当前仅支持默认实现
  231. """
  232. def __init__(self, access_key_id, access_key_secret, region, cmk_id, sts_token=None, passphrase=None,
  233. cipher=utils.AESCTRCipher(), mat_desc=None):
  234. super(AliKMSProvider, self).__init__(cipher=cipher, mat_desc=mat_desc)
  235. if not isinstance(cipher, utils.AESCTRCipher):
  236. raise ClientError('AliKMSProvider only support AES256 cipher now')
  237. self.wrap_alg = headers.KMS_ALI_WRAP_ALGORITHM
  238. self.custom_master_key_id = cmk_id
  239. self.sts_token = sts_token
  240. self.context = '{"x-passphrase":"' + passphrase + '"}' if passphrase else ''
  241. self.kms_client = client.AcsClient(access_key_id, access_key_secret, region)
  242. def get_key(self):
  243. plain_key, encrypted_key = self.__generate_data_key()
  244. return plain_key, encrypted_key
  245. def decrypt_encrypted_key(self, encrypted_key):
  246. return b64decode_from_string(self.__decrypt_data(encrypted_key))
  247. def decrypt_encrypted_iv(self, encrypted_iv, deprecated=False):
  248. if deprecated:
  249. return self.__decrypt_data(encrypted_iv)
  250. return b64decode_from_string(self.__decrypt_data(encrypted_iv))
  251. def reset_encryption_materials(self, encryption_materials):
  252. provider = copy.copy(self)
  253. provider.custom_master_key_id = encryption_materials.custom_master_key_id
  254. provider.context = '{"x-passphrase":"' + encryption_materials.passphrase + '"}' if encryption_materials.passphrase else ''
  255. provider.mat_desc = encryption_materials.desc
  256. return provider
  257. def create_content_material(self):
  258. plain_key, encrypted_key = self.get_key()
  259. plain_iv = self.get_iv()
  260. encrypted_iv = self.__encrypt_data(b64encode_as_string(plain_iv))
  261. cipher = copy.copy(self.cipher)
  262. wrap_alg = self.wrap_alg
  263. mat_desc = self.mat_desc
  264. cipher.initialize(plain_key, plain_iv)
  265. content_crypto_material = models.ContentCryptoMaterial(cipher, wrap_alg, encrypted_key, encrypted_iv,
  266. mat_desc)
  267. return content_crypto_material
  268. def __generate_data_key(self):
  269. req = GenerateDataKeyRequest.GenerateDataKeyRequest()
  270. req.set_accept_format(format_type.JSON)
  271. req.set_method(method_type.POST)
  272. req.set_KeyId(self.custom_master_key_id)
  273. req.set_KeySpec('AES_256')
  274. req.set_NumberOfBytes(32)
  275. req.set_EncryptionContext(self.context)
  276. if self.sts_token:
  277. req.set_STSToken(self.sts_token)
  278. resp = self.__do(req)
  279. return b64decode_from_string(resp['Plaintext']), resp['CiphertextBlob']
  280. def __encrypt_data(self, data):
  281. req = EncryptRequest.EncryptRequest()
  282. req.set_accept_format(format_type.JSON)
  283. req.set_method(method_type.POST)
  284. req.set_KeyId(self.custom_master_key_id)
  285. req.set_Plaintext(data)
  286. req.set_EncryptionContext(self.context)
  287. if self.sts_token:
  288. req.set_STSToken(self.sts_token)
  289. resp = self.__do(req)
  290. return resp['CiphertextBlob']
  291. def __decrypt_data(self, data):
  292. req = DecryptRequest.DecryptRequest()
  293. req.set_accept_format(format_type.JSON)
  294. req.set_method(method_type.POST)
  295. req.set_CiphertextBlob(data)
  296. req.set_EncryptionContext(self.context)
  297. if self.sts_token:
  298. req.set_STSToken(self.sts_token)
  299. resp = self.__do(req)
  300. return resp['Plaintext']
  301. def __do(self, req):
  302. try:
  303. body = self.kms_client.do_action_with_exception(req)
  304. return json.loads(to_unicode(body))
  305. except ServerException as e:
  306. raise OpenApiServerError(e.http_status, e.request_id, e.message, e.error_code)
  307. except ClientException as e:
  308. raise ClientError(e.message)
  309. except (KeyError, ValueError, TypeError) as e:
  310. raise OpenApiFormatError('Json Error: ' + str(e))