Python客户端加密

OSS客户端加密是在数据上传至OSS之前,由用户在本地对数据进行加密处理,确保只有密钥持有者才能解密数据,增强数据在传输和存储过程中的安全性。

免责声明

  • 使用客户端加密功能时,您需要对主密钥的完整性和正确性负责。因您维护不当导致主密钥用错或丢失,从而导致加密数据无法解密所引起的一切损失和后果均由您自行承担。

  • 在对加密数据进行复制或者迁移时,您需要对加密元数据的完整性和正确性负责。因您维护不当导致加密元数据出错或丢失,从而导致加密数据无法解密所引起的一切损失和后果均由您自行承担。

使用场景

  • 高度敏感数据:对于包含极高敏感度信息的数据,如个人身份信息(PII)、金融交易记录、医疗健康数据等,用户可能希望在数据离开本地环境之前就对其进行加密处理,确保即使数据在传输过程中被截获,原始数据仍能得到有效保护。

  • 合规要求:某些行业和法规(例如HIPAA、GDPR等)要求对存储在第三方平台上的数据进行严格的加密控制,客户端加密能够满足这些合规性要求,因为密钥由用户自己管理,不通过网络传递,也不由云服务商直接掌握。

  • 更强的自主控制权:企业或者开发者可能希望对加密过程有完全的控制权,包括选择加密算法、管理和轮换密钥。通过客户端加密,可以实现这一目标,确保只有合法授权的用户才能解密和访问数据。

  • 跨区域数据迁移安全性:在将数据从一个地区迁移到另一个地区的过程中,使用客户端加密可以在数据迁移前后保持数据始终处于加密状态,增强了数据在公网传输的安全性。

注意事项

  • 本文以华东1(杭州)外网Endpoint为例。如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的Region与Endpoint的对应关系,请参见访问域名和数据中心

  • 本文以从环境变量读取访问凭证为例。如何配置访问凭证,请参见配置访问凭证

  • 本文以OSS域名新建OSSClient为例。如果您希望通过自定义域名、STS等方式新建OSSClient,请参见初始化

背景信息

使用客户端加密时,会为每个Object生成一个随机数据加密密钥,用该随机数据加密密钥明文对Object的数据进行对称加密。主密钥用于生成随机的数据加密密钥,加密后的内容会当作Object的meta信息保存在服务端。解密时先用主密钥将加密后的随机密钥解密出来,再用解密出来的随机数据加密密钥明文解密Object的数据。主密钥只参与客户端本地计算,不会在网络上进行传输或保存在服务端,以保证主密钥的数据安全。

加密方式

对于主密钥的使用,目前支持如下两种方式:

  • 使用KMS托管用户主密钥

    当使用KMS托管用户主密钥用于客户端数据加密时,需要将KMS用户主密钥ID(即CMK ID)传递给SDK。

  • 使用用户自主管理的主密钥(RSA)

    主密钥信息由用户提供,需要用户将主密钥的公钥、私钥信息当做参数传递给SDK。

使用以上两种加密方式能够有效地避免数据泄漏,保护客户端数据安全。即使数据泄漏,其他人也无法解密得到原始数据。

关于客户端加密的更多信息,请参见开发指南中的客户端加密

V2版本客户端加密(推荐)

重要
  • 客户端加密支持分片上传超过5 GB的文件。在使用分片方式上传文件时,需要指定上传文件的总大小和分片大小, 除了最后一个分片外,每个分片的大小要一致,且分片大小目前必须是16的整数倍。

  • 调用客户端加密上传文件后,加密元数据会被保护,无法通过CopyObject等接口修改加密相关元数据信息。

  • 加密元数据

    参数

    描述

    是否必需

    x-oss-meta-client-side-encryption-key

    加密后的密钥。 经过RSA或KMS加密后再经过base64编码的字符串。

    x-oss-meta-client-side-encryption-start

    随机产生的加密数据的初始向量 。经过RSA或KMS加密后再经过base64编码的字符串。

    x-oss-meta-client-side-encryption-cek-alg

    数据的加密算法。

    x-oss-meta-client-side-encryption-wrap-alg

    数据密钥的加密算法。

    x-oss-meta-client-side-encryption-matdesc

    内容加密密钥(CEK)描述,JSON格式。

    x-oss-meta-client-side-encryption-unencrypted-content-length

    加密前的数据长度。如未指定content-length,则不生成该参数。

    x-oss-meta-client-side-encryption-unencrypted-content-md5

    加密前的数据的MD5。如未指定MD5,则不生成该参数。

    x-oss-meta-client-side-encryption-data-size

    分片上传文件的总大小。

    否(分片上传时必须指定)

    x-oss-meta-client-side-encryption-part-size

    分片上传中每个part的大小。

    否(分片上传时必须指定)

  • 创建加密Bucket

    同普通上传、下载等操作一样,在使用客户端加密上传、下载文件之前需要先初始化Bucket实例。通过Bucket实例的上传、下载等接口进行文件的上传、下载操作。客户端加密通过CryptoBucket这个类继承了普通Bucket的接口,需要使用到客户端加密时,只需要传入相应的参数,同初始化Bucket实例一样,初始化一个类似的CryptoBucket实例即可。

    • 初始化非对称加密主密钥方式(RSA)的Bucket

      重要

      使用RSA加密方式时,需要您自己管理加密的密钥对,一旦丢失密钥或者密钥数据出现损坏,可能会导致数据无法解密,推荐使用KMS托管方式进行加密。如果由于业务场景一定要使用RSA加密方式,建议您做好密钥数据的备份。

      初始化非对称加密主密钥方式(RSA)的Bucket示例代码如下:

      # -*- coding: utf-8 -*-
      import os
      import oss2
      from oss2.credentials import EnvironmentVariableCredentialsProvider
      from oss2.crypto import RsaProvider
      
      # 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
      auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
      
      # 只使用解密功能。
      # key_pair = {'private_key': 'yourPrivateKey'}
      # 只使用加密功能。
      # key_pair = {'public_key': 'yourPublicKey'}
      # 同时使用加密和解密功能。
      key_pair = {'private_key': 'yourPrivateKey', 'public_key': 'yourPublicKey'}
      bucket = oss2.CryptoBucket(auth, 'yourEndpoint', 'yourBucketName',
                                 crypto_provider=RsaProvider(key_pair))
      
    • 初始化KMS主密钥加密方式的Bucket

      初始化KMS主密钥加密方式的Bucket的示例代码如下:

      # -*- coding: utf-8 -*-
      import os
      
      import oss2
      from oss2.crypto import AliKMSProvider
      from oss2.credentials import EnvironmentVariableCredentialsProvider
      
      # 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
      auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
      kms_provider = AliKMSProvider(auth, 'yourRegion', 'yourCMKID')
      bucket = oss2.CryptoBucket(auth, 'yourEndpoint', 'yourBucketName', crypto_provider=kms_provider)
      
    • 使用和管理多个密钥

      对于同一个bucket,您在上传或者下载不同的数据时,可能会使用不同的密钥进行加密。您可以给不同的密钥配置不同的描述信息,并将这些密钥和描述信息添加到bucket的加密信息中,待您再解密数据时,SDK内部会根据加密数据的描述信息自动匹配密钥,从而实现数据无缝解密。示例代码如下:

      # -*- coding: utf-8 -*-
      import os
      import oss2
      from oss2.crypto import RsaProvider
      from oss2.credentials import EnvironmentVariableCredentialsProvider
      
      # 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
      auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
      
      # 创建一个RSA密钥对。
      key_pair_1 = {'private_key': 'yourPrivateKey_1', 'public_key': 'yourPublicKey_1'}
      mat_desc_1 = {'key1': 'value1'}
      
      # 创建另一个RSA密钥对。
      key_pair_2 = {'private_key': 'yourPrivateKey_2', 'public_key': 'yourPublicKey_2'}
      mat_desc_2 = {'key2': 'value2'}
      
      provider = RsaProvider(key_pair=key_pair_2, mat_desc=mat_desc_2)
      # 将key_pair_1的描述信息添加到provider。
      encryption_materials = oss2.EncryptionMaterials(mat_desc_1, key_pair=key_pair_1)
      provider.add_encryption_materials(encryption_materials)
      
      # 使用provider初始化crypto_bucket,这样可以使用crypto_bucket下载使用描述信息为mat_desc_1的主密钥加密的对象数据。
      crypto_bucket = oss2.CryptoBucket(auth, 'yourEndpoint', 'yourBucketName', crypto_provider=provider)
      
  • 普通上传和下载文件

    使用主密钥KMS普通上传和下载文件的示例代码如下:

    # -*- coding: utf-8 -*-
    import os
    import oss2
    from oss2.credentials import EnvironmentVariableCredentialsProvider
    from oss2.crypto import RsaProvider
    from oss2.cryptoimportAliKMSProvider
    
    # 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
    
    kms_provider = AliKMSProvider(auth, 'yourRegion', 'yourCMKID')
    bucket = oss2.CryptoBucket(auth, 'yourEndpoint', 'yourBucketName', crypto_provider=kms_provider)
    
    key = 'motto.txt'
    content = b'a' * 1024 * 1024
    filename = 'download.txt'
    
    
    # 上传文件。
    bucket.put_object(key, content, headers={'content-length': str(1024 * 1024)})
    
    # 下载OSS文件到本地内存。
    result = bucket.get_object(key)
    
    # 验证获取到的文件内容跟上传时的文件内容是否一致。
    content_got = b''
    for chunk in result:
        content_got += chunk
    assert content_got == content
    
    # 下载OSS文件到本地文件。
    result = bucket.get_object_to_file(key, filename)
    
    # 验证获取到的文件内容跟上传时的文件内容是否一致。
    with open(filename, 'rb') as fileobj:
        assert fileobj.read() == content
  • 分片上传

    说明
    • 分片上传文件时,一旦上传中断(进程退出)后,加密分片上传上下文可能会丢失。上传中断后如果要继续上传该文件,则必须再次上传整个文件。

    • 推荐您直接使用OSS封装好的断点续传上传接口上传大文件,该接口已将加密分片上传上下文保存在用户本地了,即使是上传出现中断,该上下文信息也不会丢失。

    使用主密钥KMS分片上传文件示例代码如下:

    # -*- coding: utf-8 -*-
    import os
    import oss2
    from oss2.credentials import EnvironmentVariableCredentialsProvider
    from  oss2.crypto import RsaProvider
    from oss2.cryptoimportAliKMSProvider
    
    # 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
    
    kms_provider=AliKMSProvider(auth, 'yourRegion', 'yourCMKID')
    bucket = oss2.CryptoBucket(auth, 'yourEndpoint', 'yourBucketName', crypto_provider = kms_provider)
    
    """
    分片上传
    """
    # 初始化上传分片。
    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"
    
    # 初始化加密分片上传上下文。
    context = models.MultipartUploadCryptoContext(data_size, part_size)
    res = bucket.init_multipart_upload(multi_key, upload_context=context)
    upload_id = res.upload_id
    
    # 分片上传示例中使用顺序上传,实际使用中为了加快上传速度也可以支持多个线程并发上传。
    for i in range(3):
        # context的值不允许修改,否则将导致数据上传失败。
        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))
    
    # 完成分片上传。
    result = bucket.complete_multipart_upload(multi_key, upload_id, parts)
    
    # 验证获取到的文件内容跟上传时的文件内容是否一致。
    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
  • 断点续传上传

    使用主密钥RSA断点续传上传文件示例代码如下:

    # -*- coding: utf-8 -*-
    import os
    import oss2
    from oss2.credentials import EnvironmentVariableCredentialsProvider
    from  oss2.crypto import RsaProvider
    
    key = 'motto.txt'
    content = b'a' * 1024 * 1024 * 100
    file_name_put = 'upload.txt'
    
    # 将content的内容写入文件。
    with open(file_name_put, 'wb') as fileobj:
        fileobj.write(content)
    
    # 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
    
    # 创建存储空间,使用用户自主管理(RSA)方式加密,此方式只支持文件整体上传、下载操作。
    # 如果您使用2.9.0及以上版本的SDK,不建议使用LocalRsaProvider初始化bucket。
    # bucket = oss2.CryptoBucket(auth,'yourEndpoint', 'yourBucketName', crypto_provider=LocalRsaProvider())
    
    # 只使用解密功能。
    # key_pair = {'private_key': 'yourPrivateKey'}
    # 只使用加密功能。
    # key_pair = {'public_key': 'yourPublicKey'}
    # 同时使用加密和解密功能。
    key_pair = {'private_key': 'yourPrivateKey', 'public_key': 'yourPublicKey'}
    
    # 初始化bucket将upload_contexts_flag设置为True,调用upload_part接口不用传入加密分片上传上下文参数。
    bucket = oss2.CryptoBucket(auth, endpoint, bucket_name,
                               crypto_provider=RsaProvider(key_pair))
    
    # 为演示方便,本示例将multipart_threshold的值设置为10*1024*1024,默认值为10 MB。实际使用过程中可以根据使用场景灵活设置。
    # multipart_threshold表示文件超过这个阈值就是用分片上传方式上传问题,如果文件大小小于这个值,建议使用简单的put_object接口上传文件。
    # part_size为使用分片上传时分片的大小,默认值为10 MB。
    # num_threads为并发上传线程的个数,默认值为1。
    oss2.resumable_upload(bucket, key, file_name_put, multipart_threshold=10 * 1024 * 1024, part_size=1024 * 1024, num_threads=3)
  • 断点续传下载

    使用主密钥KMS断点续传下载文件示例代码如下:

    # -*- coding: utf-8 -*-
    import os
    import oss2
    from oss2.crypto import RsaProvider
    from oss2.cryptoimportAliKMSProvider
    from oss2.credentials import EnvironmentVariableCredentialsProvider
    
    # 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
    key = 'motto.txt'
    content = b'a' * 1024 * 1024 * 100
    file_name_get = 'download.txt'
    
    kms_provider = AliKMSProvider(auth, 'yourRegion', 'yourCMKID')
    bucket = oss2.CryptoBucket(auth, 'yourEndpoint', 'yourBucketName', crypto_provider=kms_provider)
    
    
    # 断点续传下载。
    oss2.resumable_download(bucket, key, file_name_get, multiget_threshold=10 * 1024 * 1024, part_size=1024 * 1024, num_threads=3)
    
    # 验证获取到的文件内容跟上传时的文件内容是否一致。
    with open(file_name_get, 'rb') as fileobj:
        assert fileobj.read() == content
    

V1版本客户端加密(不推荐)

说明
  • V1版本的客户端加密只支持通过PutObject接口上传5 GB以下的文件,不支持分片上传、断点续传上传和断点续传下载接口。

  • 通过客户端加密接口上传文件后,不允许通过CopyObject等接口修改对象的加密元数据。如果修改了这些元数据,可能会导致数据无法解密。

  • V1版本的客户端加密仅在Python SDK上支持,其它语言的SDK无法解密通过V1版本客户端加密上传的数据。

  • 自2.11.0版本后开始支持V2版本的客户端加密,V2版本支持的功能更加完善,条件允许的情况下建议升级到新版本。

  • 加密元数据

    参数

    描述

    是否必需

    x-oss-meta-oss-crypto-key

    加密后的密钥。 经过RSA加密后再经过base64编码的字符串。

    x-oss-meta-oss-crypto-start

    随机产生的加密数据的初始值 。经过RSA加密后再经过base64编码的字符串。

    x-oss-meta-oss-cek-alg

    数据的加密算法。

    x-oss-meta-oss-wrap-alg

    数据密钥的加密算法。

    x-oss-meta-oss-matdesc

    内容加密密钥(CEK)描述,JSON格式。暂未生效。

    x-oss-meta-unencrypted-content-length

    加密前的数据长度。如未指定content-length则不生成该参数。

    x-oss-meta-unencrypted-content-md5

    加密前的数据的MD5。如未指定MD5则不生成该参数。

  • 使用RSA方式上传和下载文件

    使用RSA方式上传和下载文件示例代码如下:

    # -*- coding: utf-8 -*-
    import os
    import oss2
    from oss2.credentials import EnvironmentVariableCredentialsProvider
    from oss2.crypto import LocalRsaProvider
    
    # 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
    
    # 创建存储空间,使用用户自主管理(RSA)方式加密,此方式只支持文件整体上传下载操作。
    bucket = oss2.CryptoBucket(auth, 'yourEndpoint', 'yourBucketName', crypto_provider=LocalRsaProvider())
    
    key = 'motto.txt'
    content = b'a' * 1024 * 1024
    filename = 'download.txt'
    
    
    # 上传文件。
    bucket.put_object(key, content, headers={'content-length': str(1024 * 1024)})
    
    # 下载OSS文件到本地内存。
    result = bucket.get_object(key)
    
    # 验证获取到的文件内容跟上传时的文件内容是否一致。
    content_got = b''
    for chunk in result:
        content_got += chunk
    assert content_got == content
    
    # 下载OSS文件到本地文件。
    result = bucket.get_object_to_file(key, filename)
    
    # 验证获取到的文件内容跟上传时的文件内容是否一致。
    with open(filename, 'rb') as fileobj:
        assert fileobj.read() == content

相关文档

关于客户端加密的完整示例代码,请参见GitHub示例