Client-side encryption is used to encrypt objects on the local client before they are uploaded to Object Storage Service (OSS).

Disclaimer

  • When you use client-side encryption, you must ensure the integrity and validity of the customer master key (CMK). If the CMK is incorrectly used or lost due to improper maintenance, you will be held responsible for all losses and consequences caused by decryption failures.
  • When you copy or migrate encrypted data, you must ensure the integrity and validity of the object metadata related to client-side encryption. If the encrypted metadata is incorrectly used or lost due to improper maintenance, you will be held responsible for all losses and consequences caused by decryption failures.

Background information

In client-side encryption, a random data key is generated for each object to perform symmetric encryption on the object. The client uses a CMK to encrypt the random data key. The encrypted data key is uploaded as a part of the object metadata and stored in the OSS server. When an encrypted object is downloaded, the client uses the CMK to decrypt the random data key and then uses the data key to decrypt the object. To ensure data security, the CMK is used only on the client and is not transmitted over the network or stored in the server.

Encryption method

You can use CMKs managed in one of the following methods:

  • Use KMS-managed CMKs

    When you use a CMK stored in Key Management Service (KMS) for client-side encryption, you must send the CMK ID to OSS SDK for Python.

  • Use RSA-based CMKs managed by yourself

    When you use a CMK managed by yourself for client-side encryption, you must send the public key and the private key of your CMK to OSS SDK for Python as parameters.

You can use the preceding methods to prevent data leaks and protect your data on the client. Even if your data is leaked, the data cannot be decrypted by other users.

For more information about client-side encryption, see Client-side encryption in OSS Developer Guide.

Client-side encryption V2 (recommended)

Notice
  • Client-side encryption supports multipart upload for objects larger than 5 GB. When you use multipart upload to upload an object, you must specify the total size of the object and the size of each part. The size of each part except for the last part must be the same and a multiple of 16 bytes.
  • After you upload objects encrypted on the client, the object metadata related to client-side encryption is protected and cannot be modified by calling operations such as CopyObject.
  • Object metadata related to client-side encryption
    Parameter Description Required
    x-oss-meta-client-side-encryption-key The encrypted data key. The encrypted data key is a string encrypted by using a RSA-based CMK or a CMK managed by KMS and encoded in Base64. Yes
    x-oss-meta-client-side-encryption-start The initialization vector generated randomly for data encryption. The encrypted data key is a string encrypted by using a RSA-based CMK or a CMK managed by KMS and encoded in Base64. Yes
    x-oss-meta-client-side-encryption-cek-alg The algorithm used to encrypt data. Yes
    x-oss-meta-client-side-encryption-wrap-alg The algorithm used to encrypt the data key. Yes
    x-oss-meta-client-side-encryption-matdesc The description of the content encryption key (CEK) in the JSON format. No
    x-oss-meta-client-side-encryption-unencrypted-content-length The length of data before encryption. If Content-Length is not specified, this parameter is not generated. No
    x-oss-meta-client-side-encryption-unencrypted-content-md5 The MD5 hash of data before encryption. if Content-MD5 is not specified, this parameter is not generated. No
    x-oss-meta-client-side-encryption-data-size The total size of the object that you want to upload by using multipart upload. No (required for multipart upload)
    x-oss-meta-client-side-encryption-part-size The size of each part in multipart upload. No (required for multipart upload)
  • Create a bucket for client-side encryption

    Before you encrypt and upload an object or download and decrypt an object on the client, you must initialize a bucket instance. You can call the operations of the bucket instance to upload or download objects. In client-side encryption, the CryptoBucket class inherits the operations from the Bucket class. You can configure parameters to initialize a CryptoBucket instance in the same way as you initialize a bucket instance.

    • Initialize a bucket for object encryption by using a RSA-based CMK managed by yourself
      Notice If you use a RSA-based CMK, you must manage the CMK by yourself. The loss of the CMK or the damage to the CMK data may cause decryption failures. We recommend that you use CMKs managed by KMS. If you must use a RSA-based CMK to perform encryption, we recommend that you back up your CMK data.

      The following code provides an example on how to initialize a bucket for object encryption by using a RSA-based CMK managed by yourself:

      # -*- coding: utf-8 -*-
      import os
      import oss2
      from  oss2.crypto import RsaProvider
      
      # The AccessKey pair of an Alibaba Cloud account has permissions on all API operations. Using these credentials to perform operations in OSS is a high-risk operation. We recommend that you use a RAM user to call API operations or perform routine O&M. To create a RAM user, log on to the RAM console. 
      auth = oss2.Auth('yourAccessKeyId', 'yourAccessKeySecret')
      
      # If you want only to decrypt objects, specify only the private key. 
      # key_pair = {'private_key': 'yourPrivateKey'}
      # If you want only to encrypt objects, specify only the public key. 
      # key_pair = {'public_key': 'yourPublicKey'}
      # If you want to encrypt and decrypt objects, you must specify both the public key and the private key. 
      key_pair = {'private_key': 'yourPrivateKey', 'public_key': 'yourPublicKey'}
      bucket = oss2.CryptoBucket(oss2.Auth(access_key_id, access_key_secret), endpoint, bucket_name,
                                 crypto_provider=RsaProvider(key_pair))
    • Initialize a bucket for object encryption by using a CMK managed by KMS

      The following code provides an example on how to initialize a bucket for object encryption by using a CMK managed by KMS:

      import os
      
      import oss2
      from oss2.crypto import AliKMSProvider
      
      kms_provider=AliKMSProvider('yourAccessKeyId', 'yourAccessKeySecret', 'yourRegion', 'yourCMKID')
      bucket = oss2.CryptoBucket(oss2.Auth('yourAccessKeyId', 'yourAccessKeySecret'), 'yourEndpoint', 'yourBucketName', crypto_provider = kms_provider)
    • Use and manage multiple CMKs

      You may use different CMKs to encrypt objects uploaded to a bucket or decrypt objects downloaded from the same bucket. You can specify different descriptions for the CMKs and add the CMKs and the descriptions to the encryption-related information about the bucket. When data is decrypted, OSS SDK for Python automatically matches a CMK based on the description. Sample code:

      # -*- coding: utf-8 -*-
      import os
      import oss2
      from  oss2.crypto import RsaProvider
      
      # Create a RSA-based key pair. 
      key_pair_1 = {'private_key': 'yourPrivateKey_1', 'public_key': 'yourPublicKey_1'}
      mat_desc_1 = {'key1': 'value1'}
      
      # Create another RSA-based key pair. 
      key_pair_2 = {'private_key': 'yourPrivateKey_2', 'public_key': 'yourPublicKey_2'}
      mat_desc_2 = {'key2': 'value2'}
      
      # Add the description of key_pair_1 to provider. 
      provider = RsaProvider(key_pair={'private_key': private_key_str_2, 'public_key': public_key_str_2}, mat_desc=mat_desc_2)
      encryption_materials = oss2.EncryptionMaterials(mat_desc_1, key_pair=key_pair_1)
      provider.add_encryption_materials(encryption_materials)
      
      # Use provider to initialize a crypto_bucket instance. Then, you can call the operations of crypto_bucket to download the object data that is encrypted by a CMK whose description is mat_desc_1. 
      crypto_bucket = oss2.CryptoBucket(oss2.Auth('yourAccessKeyId', 'yourAccessKeySecret'), 'yourEndpoint', 'yourBucketName', crypto_provider=provider)
  • Perform client-side encryption in simple upload and download

    The following code provides an example on how to use a CMK managed by KMS to encrypt an object when you upload the object by means of simple upload or decrypt an object when you download the object:

    # -*- coding: utf-8 -*-
    import os
    import oss2
    from  oss2.crypto import RsaProvider
    
    # The AccessKey pair of an Alibaba Cloud account has permissions on all API operations. Using these credentials to perform operations in OSS is a high-risk operation. We recommend that you use a RAM user to call API operations or perform routine O&M. To create a RAM user, log on to the RAM console. 
    auth = oss2.Auth('yourAccessKeyId', 'yourAccessKeySecret')
    
    kms_provider=AliKMSProvider('yourAccessKeyId', 'yourAccessKeySecret', 'yourRegion', 'yourCMKID')
    bucket = oss2.CryptoBucket(oss2.Auth('yourAccessKeyId', 'yourAccessKeySecret'), 'yourEndpoint', 'yourBucketName', crypto_provider = kms_provider)
    
    key = 'motto.txt'
    content = b'a' * 1024 * 1024
    filename = 'download.txt'
    
    
    # Upload an object. 
    bucket.put_object(key, content, headers={'content-length': str(1024 * 1024)})
    
    # Download the object from OSS to the local memory. 
    result = bucket.get_object(key)
    
    # Verify the data consistency between the content of the downloaded object and that of the object before upload. 
    content_got = b''
    for chunk in result:
        content_got += chunk
    assert content_got == content
    
    # Download the object from OSS to the local memory. 
    result = bucket.get_object_to_file(key, filename)
    
    # Verify the data consistency between the content of the downloaded object and that of the object before upload. 
    with open(filename, 'rb') as fileobj:
        assert fileobj.read() == content
  • Perform client-side encryption in multipart upload
    Note
    • If a multipart upload task is interrupted and the process is exited, the context for multipart upload may be lost. If a multipart upload task is interrupted and you want to reupload the object, you must reupload the entire object.
    • We recommend that you call the operation for resumable upload in OSS to upload large objects. This way, you can store the context of your multipart upload on the local client so that the context can be retained even if the upload task is interrupted.

    The following code provides an example on how to use a CMK managed by KMS to encrypt an object that you want to upload by means of multipart upload:

    # -*- coding: utf-8 -*-
    import os
    import oss2
    from  oss2.crypto import RsaProvider
    
    # The AccessKey pair of an Alibaba Cloud account has permissions on all API operations. Using these credentials to perform operations in OSS is a high-risk operation. We recommend that you use a RAM user to call API operations or perform routine O&M. To create a RAM user, log on to the RAM console. 
    auth = oss2.Auth('yourAccessKeyId', 'yourAccessKeySecret')
    
    kms_provider=AliKMSProvider('yourAccessKeyId', 'yourAccessKeySecret', 'yourRegion', 'yourCMKID')
    bucket = oss2.CryptoBucket(oss2.Auth('yourAccessKeyId', 'yourAccessKeySecret'), 'yourEndpoint', 'yourBucketName', crypto_provider = kms_provider)
    
    """
    Perform client-side encryption in multipart upload
    """
    # Initialize the parts that you want to upload. 
    part_a = b'a' * 1024 * 100
    part_b = b'b' * 1024 * 100
    part_c = b'c' * 1024 * 100
    multi_content = [part_a, part_b, part_c]
    
    parts = []
    data_size = 100 * 1024 * 3
    part_size = 100 * 1024
    multi_key = "test_crypto_multipart"
    
    # Initialize the context for the multipart upload when you use client-side encryption. 
    context = models.MultipartUploadCryptoContext(data_size, part_size)
    res = bucket.init_multipart_upload(multi_key, upload_context=context)
    upload_id = res.upload_id
    
    # In this example, parts are uploaded in sequence. In practice, multipart upload supports multi-threaded upload to accelerate the upload. 
    for i in range(3):
        # The context value cannot be modified. If the values in context are modified, data upload fails. 
        result = bucket.upload_part(multi_key, upload_id, i + 1, multi_content[i], upload_context=context)
        parts.append(oss2.models.PartInfo(i + 1, result.etag, size=part_size, part_crc=result.crc))
    
    # Complete the multipart upload task. 
    result = bucket.complete_multipart_upload(multi_key, upload_id, parts)
    
    # Verify the data consistency between the content of the downloaded object and that of the object before upload. 
    result = bucket.get_object(multi_key)
    content_got = b''
    for chunk in result:
        content_got += chunk
    assert content_got[0:102400] == part_a
    assert content_got[102400:204800] == part_b
    assert content_got[204800:307200] == part_c
  • Perform client-side encryption in resumable upload

    The following code provides an example on how to use a RSA-based CMK managed by yourself to encrypt an object that you want to upload by means of resumable upload:

    # -*- coding: utf-8 -*-
    import os
    import oss2
    from  oss2.crypto import RsaProvider
    
    key = 'motto.txt'
    content = b'a' * 1024 * 1024 * 100
    file_name_put = 'upload.txt'
    
    # Write the data included in content to the file. 
    with open(file_name_put, 'wb') as fileobj:
        fileobj.write(content)
    
    # The AccessKey pair of an Alibaba Cloud account has permissions on all API operations. Using these credentials to perform operations in OSS is a high-risk operation. We recommend that you use a RAM user to call API operations or perform routine O&M. To create a RAM user, log on to the RAM console. 
    auth = oss2.Auth('yourAccessKeyId', 'yourAccessKeySecret')
    
    # Create a bucket and use the RSA-based CMK to perform client-side encryption. This encryption method supports only uploads and downloads of entire objects. 
    # If you use OSS SDK for Python V2.9.0 or later, we recommend that you do not use LocalRsaProvider to initialize the bucket. 
    # bucket = oss2.CryptoBucket(auth,'yourEndpoint', 'yourBucketName', crypto_provider=LocalRsaProvider())
    
    # If you want only to decrypt objects, specify only the private key. 
    # key_pair = {'private_key': 'yourPrivateKey'}
    # If you want only to encrypt objects, specify only the public key. 
    # key_pair = {'public_key': 'yourPublicKey'}
    # If you want to encrypt and decrypt objects, you must specify both the public key and the private key. 
    key_pair = {'private_key': 'yourPrivateKey', 'public_key': 'yourPublicKey'}
    
    # Initialize the bucket. Set upload_contexts_flag to True. You do not need to configure parameters for multipart upload context when you call the upload_part operation. 
    bucket = oss2.CryptoBucket(oss2.Auth(access_key_id, access_key_secret), endpoint, bucket_name,
                               crypto_provider=RsaProvider(key_pair))
    
    # In this example, multipart_threshold is set to 10 * 1024 * 1024 for convenience. In practice, you can specify multipart_threshold based on your requirements. Default value: 10 MB. 
    # multipart_threshold indicates a threshold for object sizes. If the object size exceeds the threshold, multipart upload is used. If the object size is smaller than the threshold, we recommend that you call put_object to upload the object. 
    # part_size indicates the size of each part in multipart upload. Default value: 10 MB. 
    # num_threads indicates the number of concurrent upload threads. Default value: 1. 
    oss2.resumable_upload(bucket, key, file_name_put, multipart_threshold=10 * 1024 * 1024, part_size=1024 * 1024, num_threads=3)
  • Perform client-side encryption in resumable download

    The following code provides an example on how to use a CMK managed by KMS to decrypt an object that you want to download in resumable download:

    # -*- coding: utf-8 -*-
    import os
    import oss2
    from  oss2.crypto import RsaProvider
    
    key = 'motto.txt'
    content = b'a' * 1024 * 1024 * 100
    file_name_get = 'download.txt'
    
    kms_provider=AliKMSProvider('yourAccessKeyId', 'yourAccessKeySecret', 'yourRegion', 'yourCMKID')
    bucket = oss2.CryptoBucket(oss2.Auth('yourAccessKeyId', 'yourAccessKeySecret'), 'yourEndpoint', 'yourBucketName', crypto_provider = kms_provider)
    
    
    # Download an object by using resumable download. 
    oss2.resumable_download(bucket, key, file_name_get, multiget_threshold=10 * 1024 * 1024, part_size=1024 * 1024, num_threads=3)
    
    # Verify the data consistency between the content of the downloaded object and that of the object before upload. 
    with open(file_name_get, 'rb') as fileobj:
        assert fileobj.read() == content

Client-side encryption V1 (not recommended)

Note
  • Client-side encryption V1 supports only the upload of objects smaller than 5 GB by calling PutObject. The multipart upload, resumable upload, and resumable download operations are not supported.
  • After you upload an object by calling the operations of the CryptoBucket class, operations such as CopyObject cannot be called to modify the metadata related to client-side encryption. If the metadata is modified, the data may fail to be decrypted.
  • Only OSS SDK for Python supports client-side encryption V1. OSS SDKs for other programming languages cannot decrypt data uploaded by using client-side encryption V1.
  • OSS SDK for Python V2.11.0 and later support client-side encryption V2. Client-side encryption V2 provides more features than client-side encryption V1. We recommend that you upgrade to the latest version.
  • Object metadata related to client-side encryption
    Parameter Description Required
    x-oss-meta-oss-crypto-key The encrypted data key. The encrypted data key is a string encrypted by using a RSA-based CMK and encoded in Base64. Yes
    x-oss-meta-oss-crypto-start The initial value generated randomly for data encryption. The encrypted data key is a string encrypted by using a RSA-based CMK and encoded in Base64. Yes
    x-oss-meta-oss-cek-alg The algorithm used to encrypt data. Yes
    x-oss-meta-oss-wrap-alg The algorithm used to encrypt the data key. Yes
    x-oss-meta-oss-matdesc The description of the CEK in the JSON format. This parameter does not take effect. No
    x-oss-meta-unencrypted-content-length The length of data before encryption. If Content-Length is not specified, this parameter is not generated. No
    x-oss-meta-unencrypted-content-md5 The MD5 hash of data before encryption. If Content-MD5 is not specified, this parameter is not generated. No
  • Use RSA-based CMKs managed by yourself to perform client-side encryption for object uploads and downloads

    The following code provides an example on how to use a RSA-based CMK to encrypt an object that you want to upload or decrypt an object that you want to download:

    # -*- coding: utf-8 -*-
    import os
    import oss2
    from  oss2.crypto import LocalRsaProvider
    
    # The AccessKey pair of an Alibaba Cloud account has permissions on all API operations. Using these credentials to perform operations in OSS is a high-risk operation. We recommend that you use a RAM user to call API operations or perform routine O&M. To create a RAM user, log on to the RAM console. 
    auth = oss2.Auth('yourAccessKeyId', 'yourAccessKeySecret')
    
    # Create a bucket and use the RSA-based CMK to perform client-side encryption. This encryption method supports only uploads and downloads of entire objects. 
    bucket = oss2.CryptoBucket(auth,'yourEndpoint', 'yourBucketName', crypto_provider=LocalRsaProvider())
    
    key = 'motto.txt'
    content = b'a' * 1024 * 1024
    filename = 'download.txt'
    
    
    # Upload an object. 
    bucket.put_object(key, content, headers={'content-length': str(1024 * 1024)})
    
    # Download the object from OSS to the local memory. 
    result = bucket.get_object(key)
    
    # Verify the data consistency between the content of the downloaded object and that of the object before upload. 
    content_got = b''
    for chunk in result:
        content_got += chunk
    assert content_got == content
    
    # Download the object from OSS to the local memory. 
    result = bucket.get_object_to_file(key, filename)
    
    # Verify the data consistency between the content of the downloaded object and that of the object before upload. 
    with open(filename, 'rb') as fileobj:
        assert fileobj.read() == content

References

For the complete sample code that is used to perform client-side encryption, visit GitHub.