百度智能云

All Product Document

          Object Storage

          Client Encryption Practice

          Background Overview

          Client encryption refers to the process of encrypting and decrypting files by users themselves locally. Baidu AI Cloud object storage does not participate in the process of encrypting and decrypting, but only takes charge of the process of uploading, storing and downloading files. The plaintext key is kept locally by users themselves. Client encryption enhances file security. Even if the file is accidentally disclosed, no one else can decrypt the original data. However, for the user needs to keep the key himself, if the plaintext key is lost, the user will not be able to get the original file content. This document provides a client encryption scheme.

          Encryption Principle

          1.Locally use RSA algorithm to generate asymmetric keys (private_rsa_key and public_rsa_key); 2.When uploading files, use the CTR mode of AES 256 to generate a symmetric key (aes_key); 3.For ordinary files, use the symmetric key aes key to encrypt the original data; use the public key public_rsa_key to encrypt aes key to generate encrypted aes_key as the meta of the object, and upload the file to BOS. For large files, use the block upload of BOS; one block is taken each time for encryption and upload, and the block size is an integer multiple of 16 bytes. Similarly, use the public key public_rsa_key encryption aes_key to generate encrypted aes_key as the meta of the object. 4.When downloading a file, first get the meta information of the file and get encrypted_aes_key. Then use the local private key private_rsa_key to decrypt encrypted_aes_key to get aes_key. For ordinary files, download the encrypted file and decrypt the original data with aes_key. For large files, you can use block download. Use the Python SDK's function get_object(bucket_name, object_key,[range_start, range_end]) to download the bytes in the specified range [range_start,range_end] of large files, including the bytes at the end position range_end, where 0 ≤range_start≤range_end≤file size.

          Note: When AES 256 is used for encryption, the block size of AES algorithm is 128bits=16byte, so when a large file is uploaded in blocks, each block size should be an integer multiple of 16 bytes.

          Encryption Schematic

          Client_e55acc1.png

          Python Sample Code

          Preparation

          1.Users need to install python SDk, please see Python SDK Installation Package.

          2.Execute the following command to install the PyCrypto repository.

          pip install pycrypto 

          3.Modify the configuration. Modify the HOST, AK and SK in the sample code as the configuration items of this example.

          The Sample Code Is as Follows

          #coding=utf-8 
          
          ''' 
          The file aims to help client to encrypt data on python sdk with RSA algorithm  and symmetric encryption algorithm 
          ''' 
          
          import os 
          import shutil 
          import base64 
          import random 
          import string 
          
          #Introduce profiles and object storage module 
          from baidubce.bce_client_configuration import BceClientConfiguration 
          from baidubce.auth.bce_credentials import BceCredentials 
          from baidubce import exception 
          from baidubce.services import bos 
          from baidubce.services.bos import canned_acl 
          from baidubce.services.bos.bos_client import BosClient 
          
          #Introduce Crypto encryption module 
          from Crypto import Random 
          from Crypto.Cipher  import AES 
          from Crypto.Cipher  import PKCS1_OAEP 
          from Crypto.PublicKey import RSA 
          from Crypto.Util    import Counter 
          
          #Set the symmetric key length to 128bits 
          _AES256_KEY_SIZE = 32 
          #Set Counter length for AES CTR mode 
          _COUNTER_BITS_LENGTH_AES_CTR = 8*16 
          
          class CipherWithAES: 
              # start is the initial value of CTR counter 
              def __init__(self, key= None, start= None): 
                  if not key: 
                      key = Random.new().read(_AES256_KEY_SIZE) 
                  if not start: 
                      start = random.randint(1,100) 
                  self.key = key 
                  self.start = start 
                  #Generate counter 
                  my_counter = Counter.new(_COUNTER_BITS_LENGTH_AES_CTR, initial_value=self.start) 
                  #Generate AES object 
                  self.cipher = AES.new(self.key, AES.MODE_CTR, counter = my_counter) 
          
              #Encrypt data 
              def encrypt(self, plaintext): 
                  return self.cipher.encrypt(plaintext) 
          
              #Decrypt data 
              def decrypt(self, ciphertext): 
                  return self.cipher.decrypt(ciphertext) 
          
          class CipherWithRSA: 
              # Input parameters are public key file name and private key file name 
              def __init__(self, public_key_file_name = None, private_key_file_name = None): 
                  self.public_key_file_name=public_key_file_name 
                  self.private_key_file_name = private_key_file_name 
                  if not self.public_key_file_name: 
                      self.public_key_file_name = "rsa_public_key.pem" 
                  if not self.private_key_file_name: 
                      self.private_key_file_name = "rsa_private_key.pem" 
                  #If no public key and private key files are input, RSA key pair is generated locally 
                  if not (os.path.isfile(self.public_key_file_name) and os.path.isfile(self.private_key_file_name)): 
                      self._generate_rsa_key() 
                      return 
                  #Read the public key and private key from the file and generate the RSA object 
                  with open(self.public_key_file_name) as file_key: 
                      public_key_obj_rsa = RSA.importKey(file_key.read()) 
                      self.encrypt_obj = PKCS1_OAEP.new(public_key_obj_rsa) 
                  with open(self.private_key_file_name) as file_key: 
                      private_key_obj_rsa = RSA.importKey(file_key.read()) 
                      self.decrypt_obj = PKCS1_OAEP.new(private_key_obj_rsa) 
          
              #Generate RSA key pair locally 
              def _generate_rsa_key(self): 
                  private_key_obj_rsa = RSA.generate(2048) 
                  public_key_obj_rsa = private_key_obj_rsa.publickey() 
                  self.encrypt_obj = PKCS1_OAEP.new(public_key_obj_rsa) 
                  self.decrypt_obj = PKCS1_OAEP.new(private_key_obj_rsa) 
                  #Save the generated key pair locally 
                  with open(self.public_key_file_name,"w") as file_export: 
                      file_export.write(public_key_obj_rsa.exportKey()) 
                  with open(self.private_key_file_name,"w") as file_export: 
                      file_export.write(private_key_obj_rsa.exportKey()) 
          
              #Encrypt data 
              def encrypt(self,plaintext_key): 
                  return self.encrypt_obj.encrypt(plaintext_key) 
          
              #Decrypt data 
              def decrypt(self,ciphertext_key): 
                  return self.decrypt_obj.decrypt(ciphertext_key) 
          #Generate random file name 
          def _random_string(length): 
              return ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(length)) 
          
          #### #### #### #### put super file#### #### #### #### # 
          #Upload large files in blocks to Baidu object storage, "part_size" is the block size, which must be an integer multiple of 16 bytes 
          def put_super_file(bos_client, bucket_name, super_object_key, super_file, part_size,cipher_rsa): 
              """
              Put super file to baidu object storage by multipart uploading."part_size" must be multiple of 16 bytes 
          
              :param bos_client:bos client 
              :type bos_client:baidubce.services.bos.bos_client.BosClient 
          
              :param bucket_name:None 
              :type bucket_name:string 
          
              :param super_object_key: destion object key of super file 
              :type super_object_key: string 
          
              :param super_file: super file name 
              :type super_file:string 
          
              :param part_size: size of part to upload once,"part_size" must be multiple of 16 bytes and more than 5MB 
              :type: int 
          
              :param cipher_rsa: encrypt symmetric key 
              :type cipher_rsa: CipherWithRSA 
          
              :return :**Http Response** 
          
              """
              #1.initial 
              try: 
                  if not isinstance(bos_client,BosClient): 
                      raise Exception("bos client is None!") 
                  if not (bucket_name and super_object_key): 
                      raise Exception("bucket or object is invalid!") 
                  if not os.path.isfile(super_file): 
                      raise Exception("source file is invalid!") 
                  if not isinstance(cipher_rsa,CipherWithRSA): 
                      raise Exception("cipher_rsa is invalid!") 
              except Exception as e: 
                  print e 
                  exit() 
              if not part_size: 
                  part_size = 10*1024*1024
              #File block after temp_file encryption 
              temp_file = _random_string(20) 
              cipher_aes = CipherWithAES() 
              #2.Upload in blocks 
              #Upload in blocks consists of three steps. The first step is initialization to get the upload_id 
              upload_id = bos_client.initiate_multipart_upload( 
                  bucket_name = bucket_name,
                  key = super_object_key, 
                  ).upload_id 
              left_size = os.path.getsize(super_file) 
              offset = 0 
              part_number = 1
              part_list = [] 
              fs = open(super_file,"r") 
              while left_size > 0: 
                  if left_size< part_size: 
                      part_size = left_size
                  # Read block and encrypt 
                  part_content = fs.read(part_size) 
                  encrypted_part_content = cipher_aes.encrypt(part_content) 
                  #Re-write temp_file again after block encryption 
                  with open(temp_file,"w") as ft: 
                      ft.write(encrypted_part_content) 
                  #Upload in blocks step 2: upload in blocks 
                  response = bos_client.upload_part_from_file( 
                          bucket_name, super_object_key, upload_id, part_number, part_size, temp_file, offset) 
                  left_size -= part_size
                  #Save part number and etag to call complete_multipart_upload() 
                  part_list.append({ 
                      "partNumber": part_number, 
                      "eTag": response.metadata.etag 
                      }) 
                  part_number += 1
              os.remove(temp_file) 
              fs.close() 
              #Use public key to encrypt AES key as meta of object 
              user_metadata = { 
                  "key": base64.b64encode(cipher_rsa.encrypt(str(cipher_aes.key))), 
                  "start":base64.b64encode(cipher_rsa.encrypt(str(cipher_aes.start))) 
              } 
              #Step 3 of upload in blocks: complete upload in blocks 
              return bos_client.complete_multipart_upload(bucket_name, super_object_key, upload_id, part_list,user_metadata = user_metadata) 
          
          ####################### Get super file####################### 
          #Get the block of a large file, where range_start, range_end are start/stop positions of the block to get in the file, including the bytes of the technical position 
          def get_part_super_file(bos_client, bucket_name, super_object_key, range_start, range_end,cipher_rsa): 
          
              """
              Get part of  super file from baidu object storage 
          
              :param bos_client:bos client 
              :type bos_client:baidubce.services.bos.bos_client.BosClient 
          
              :param bucket_name:None 
              :type bucket_name:string 
          
              :param super_object_key: destion object key of super file 
              :type super_object_key: string 
          
              :param range_start: index of first bytes of part,minimum  value is 0 
              :type super_file:int 
          
              :param range_end: index of last bytes of part 
              :type: int 
          
              :param cipher_rsa: dencrypt symmetric key 
              :type cipher_rsa: CipherWithRSA 
          
              :return :**Http Response** 
          
              """
          
              try: 
                  if not isinstance(bos_client,BosClient): 
                      raise Exception("bos client is None!") 
                  if not (bucket_name and super_object_key): 
                      raise Exception("bucket or object is invalid!") 
                  if not (range_start and range_end): 
                      raise Exception("range is invalid!") 
                  if not isinstance(cipher_rsa,CipherWithRSA): 
                      raise Exception("cipher_rsa is invalid!") 
              except Exception as e: 
                  print e 
                  exit() 
              #1.Align block start position to integer multiple of 16 bytes 
              left_offset = range_start%16
              right_offset = 15 -range_end%16
              test_range = [range_start-left_offset,range_end+right_offset] 
              #2.Get the meta of the object 
              response = bos_client.get_object_meta_data(bucket_name, super_object_key) 
              #Use local public key to decrypt AES key ciphertext and get AES key plaintext 
              download_aes_key = base64.b64decode(getattr(response.metadata,"bce_meta_key")) 
              download_aes_start = base64.b64decode(str(getattr(response.metadata,"bce_meta_start"))) 
              aes_key = cipher_rsa.decrypt(download_aes_key) 
              aes_start = cipher_rsa.decrypt(download_aes_start) 
              #Adjust the initial value of the counter according to the starting position of the block to be acquired 
              offset_start = int(aes_start)+range_start/16 
              cipher_aes = CipherWithAES(aes_key,int(offset_start)) 
              #3.Download block ciphertext data and decrypt with AES key 
              response = bos_client.get_object(bucket_name, super_object_key,test_range) 
              download_content = response.data.read() 
              plaintext_content = cipher_aes.decrypt(download_content) 
              #Intercept the clear text data of the user specified section and return 
              return plaintext_content[left_offset:range_end-range_start+left_offset+1] 
          
          #### #### #### #  put common file #### #### #### #### #### 
          #Encrypt and upload common files 
          def put_common_file(bos_client, bucket_name, object_key, file_name, cipher_rsa): 
              """
              Put file to baidu object storage 
          
              :param bos_client:bos client 
              :type bos_client:baidubce.services.bos.bos_client.BosClient 
          
              :param bucket_name:None 
              :type bucket_name:string 
          
              :param object_key: destion object key of file 
              :type object_key: string 
          
              :param file_name: source file name 
              :type file_name:string 
          
              :param cipher_rsa: encrypt symmetric key 
              :type cipher_rsa: CipherWithRSA 
          
              :return :**Http Response** 
          
              """
          
              try: 
                  if not isinstance(bos_client,BosClient): 
                      raise Exception("bos client is None!") 
                  if not (bucket_name and object_key): 
                      raise Exception("bucket or object is invalid!") 
                  if not os.path.isfile(file_name): 
                      raise Exception("file name  is invalid!") 
                  if not isinstance(cipher_rsa,CipherWithRSA): 
                      raise Exception("cipher_rsa is invalid!") 
              except Exception as e: 
                  print e 
                  exit() 
              temp_file = _random_string(20) 
              #Read the data of the file to upload 
              content="" 
              with open(file_name,"r") as fp: 
                  content = fp.read() 
              cipher_aes = CipherWithAES() 
              #Encrypt data and write to temp_file 
              encrypt_content = cipher_aes.encrypt(content) 
              with  open(temp_file,"w") as ft: 
                  ft.write(encrypt_content) 
              cipher_rsa = CipherWithRSA() 
              #Encrypt AES key as meta of object 
              user_metadata = { 
                      "key": base64.b64encode(cipher_rsa.encrypt(str(cipher_aes.key))), 
                      "start":base64.b64encode(cipher_rsa.encrypt(str(cipher_aes.start))) 
                      } 
              #Upload encrypted file 
              response = bos_client.put_object_from_file(bucket = bucket_name, 
                      key = object_key, 
                      file_name = temp_file,
                      user_metadata = user_metadata) 
              os.remove(temp_file) 
              return response 
          
          ################ get common file ##################### 
          #Download encrypted normal files 
          def get_common_file(bos_client, bucket_name, object_key, des_file, cipher_rsa): 
          
              """
              Put file to baidu object storage 
          
              :param bos_client:bos client 
              :type bos_client:baidubce.services.bos.bos_client.BosClient 
          
              :param bucket_name:None 
              :type bucket_name:string 
          
              :param object_key: destion object key of file 
              :type object_key: string 
          
              :param des_file: destination file name 
              :type des_file:string 
          
              :param cipher_rsa: dencrypt symmetric key 
              :type cipher_rsa: CipherWithRSA 
          
              :return :**Http Response** 
          
              """
              try: 
                  if not isinstance(bos_client,BosClient): 
                      raise Exception("bos client is None!") 
                  if not (bucket_name and object_key): 
                      raise Exception("bucket or object is invalid!") 
                  if not des_file: 
                      raise Exception("destination file is invalid!") 
                  if not isinstance(cipher_rsa,CipherWithRSA): 
                      raise Exception("cipher_rsa is invalid!") 
              except Exception as e: 
                  print e 
                  exit() 
              #Download and get meta 
              response = bos_client.get_object_meta_data(bucket_name, object_key) 
              download_aes_key = base64.b64decode(getattr(response.metadata,"bce_meta_key")) 
              download_aes_start = base64.b64decode(str(getattr(response.metadata,"bce_meta_start"))) 
              #Download encrypted data locally 
              download_content = bos_client.get_object_as_string(bucket_name, object_key) 
              #Decrypt to get AES key plaintext 
              aes_key = cipher_rsa.decrypt(download_aes_key) 
              aes_start = cipher_rsa.decrypt(download_aes_start) 
          
              cipher_aes = CipherWithAES(aes_key,int(aes_start)) 
              plaintext_content = cipher_aes.decrypt(download_content) 
              with open(des_file,"w") as fd: 
                  fd.write(plaintext_content) 
          
          if __name__ == "__main__": 
          	 #Taking the object storage in Beijing as an example, and replace AK, SK, and bucket name as user data 
              HOST = 'bj.bcebos.com' 
              AK = 'Your_Access_Key' 
              SK = 'Your_Secret_Access_Key' 
          	bucket_name = "Your-bucket-Name"
              super_file= "super_file"
              super_object_key = "my-super-object"
              #Get bos client 
              config = BceClientConfiguration(credentials=BceCredentials(AK, SK), endpoint=HOST) 
              bos_client = BosClient(config) 
          
              #1.1 Upload large files 
              #Set block size 
              part_size = 10*1024*1024
              cipher_rsa = CipherWithRSA() 
              #Upload large files in blocks 
              put_super_file(bos_client, bucket_name, super_object_key, super_file, part_size,cipher_rsa) 
              #1.2 Get large files and block them 
              #It is recommended to set the start position of the block to get as an integer multiple of 16 bytes 
              range_start = 16*10
              range_end = 16*11-1
              #Get plaintext data block 
              result = get_part_super_file(bos_client, bucket_name, super_object_key, range_start, range_end,cipher_rsa) 
              print "#"*20 
              print result 
              print "#"*20 
              print "length:",len(result) 
          
              #2.1 Upload common files 
              object_key = "myobject" 
              source_file = "myobject.txt" 
              put_common_file(bos_client,bucket_name,object_key,source_file,cipher_rsa) 
              #2.2 Download common files 
              des_file = "des_myobject.txt" 
              get_common_file(bos_client,bucket_name,object_key,des_file,cipher_rsa)
          Previous
          Web Direct Transmission Practice
          Next
          Upload Data to BOSCDN by CDN Dynamic Acceleration