客户端加密是指将数据发送到OSS之前在用户本地进行加密。
免责声明
- 使用客户端加密功能时,您需要对主密钥的完整性和正确性负责。因您维护不当导致主密钥用错或丢失,从而导致加密数据无法解密所引起的一切损失和后果均由您自行承担。
- 在对加密数据进行复制或者迁移时,您需要对加密元信息的完整性和正确性负责。因您维护不当导致加密元信息出错或丢失,从而导致加密数据无法解密所引起的一切损失和后果均由您自行承担。
背景信息
使用客户端加密时,会为每个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.crypto import RsaProvider # 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录RAM控制台创建RAM账号。 auth = oss2.Auth('<yourAccessKeyId>', '<yourAccessKeySecret>') 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))
- 初始化KMS主密钥加密方式的Bucket
初始化KMS主密钥加密方式的Bucket的示例代码如下:
import os import oss2 from oss2.crypto import AliKMSProvider kms_provider=AliKMSProvider('<yourAccessKeyId>', '<yourAccessKeySecret>', '<yourRegion>', '<yourCMKID>') bucket = oss2.CryptoBucket(oss2.Auth('<yourAccessKeyId>', '<yourAccessKeySecret>'), '<yourEndpoint>', '<yourBucket>', crypto_provider = kms_provider)
- 使用和管理多个密钥
对于同一个bucket,您在上传或者下载不同的数据时,可能会使用不同的密钥进行加密。您可以给不同的密钥配置不同的描述信息,并将这些密钥和描述信息添加到bucket的加密信息中,待您再解密数据时,SDK内部会根据加密数据的描述信息自动匹配密钥,从而实现数据无缝解密。示例代码如下:
# -*- coding: utf-8 -*- import os import oss2 from oss2.crypto import RsaProvider # 创建一个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'} # 将key_pair_1的描述信息添加到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) # 使用provider初始化crypto_bucket,这样可以使用crypto_bucket下载使用描述信息为mat_desc_1的主密钥加密的对象数据。 crypto_bucket = oss2.CryptoBucket(oss2.Auth('<yourAccessKeyId>', '<yourAccessKeySecret>'), '<yourEndpoint>', '<yourBucketName>', crypto_provider=provider)
- 初始化非对称加密主密钥方式(RSA)的Bucket
- 普通上传和下载文件
使用主密钥KMS普通上传和下载文件的示例代码如下:
# -*- coding: utf-8 -*- import os import oss2 from oss2.crypto import RsaProvider # 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录RAM控制台创建RAM账号。 auth = oss2.Auth('<yourAccessKeyId>', '<yourAccessKeySecret>') kms_provider=AliKMSProvider('<yourAccessKeyId>', '<yourAccessKeySecret>', '<yourRegion>', '<yourCMKID>') bucket = oss2.CryptoBucket(oss2.Auth('<yourAccessKeyId>', '<yourAccessKeySecret>'), '<yourEndpoint>', '<yourBucket>', 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.crypto import RsaProvider # 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录RAM控制台创建RAM账号。 auth = oss2.Auth('<yourAccessKeyId>', '<yourAccessKeySecret>') kms_provider=AliKMSProvider('<yourAccessKeyId>', '<yourAccessKeySecret>', '<yourRegion>', '<yourCMKID>') bucket = oss2.CryptoBucket(oss2.Auth('<yourAccessKeyId>', '<yourAccessKeySecret>'), '<yourEndpoint>', '<yourBucket>', 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.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) # 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录RAM控制台创建RAM账号。 auth = oss2.Auth('<yourAccessKeyId>', '<yourAccessKeySecret>') # 创建存储空间,使用用户自主管理(RSA)方式加密,此方式只支持文件整体上传下载操作。 # 如果您使用2.9.0及以上版本的SDK,不建议使用LocalRsaProvider初始化bucket。 # bucket = oss2.CryptoBucket(auth,'<yourEndpoint>', '<yourBucketName>', crypto_provider=LocalRsaProvider()) key_pair = {'private_key': '<yourPrivateKey>', 'public_key': '<yourPublicKey>'} # 初始化bucket将upload_contexts_flag设置为True,调用upload_part接口不用传入加密分片上传上下文参数。 bucket = oss2.CryptoBucket(oss2.Auth(access_key_id, access_key_secret), endpoint, bucket_name, crypto_provider=RsaProvider(key_pair)) # 此示例为了演示方便,将multipart_threshold的值设置为100*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 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>', '<yourBucket>', 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.crypto import LocalRsaProvider # 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://console.cloud.tmall.com 创建RAM账号。 auth = oss2.Auth('<yourAccessKeyId>', '<yourAccessKeySecret>') # 创建存储空间,使用用户自主管理(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