Java客户端加密

重要

本文中含有需要您注意的重要提示信息,忽略该信息可能对您的业务造成影响,请务必仔细阅读。

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

免责声明

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

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

使用场景

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

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

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

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

注意事项

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

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

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

背景信息

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

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

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

加密方式

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

  • 使用KMS托管用户主密钥

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

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

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

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

加密元数据

参数

描述

是否必须

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

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

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

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

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

数据的加密算法。

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

数据密钥的加密算法。

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

主密钥的描述信息。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

若加密Multipart文件,则需要在init_multipart时传入整个大文件的总大小。

是(分片上传)

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

若加密Multipart文件,则需要在init_multipart时传入分片大小。

重要

目前分片大小必须是16的整数倍。

是(分片上传)

OpenSSL工具说明

通过3.x版本的OpenSSL工具默认生成PRIVATE_PKCS8_PEM。如果您需要生成PRIVATE_PKCS1_PEM,需要结合-traditional选项。详情请参见OpenSSL

创建加密客户端

说明

创建RSA和KMS加密客户端时,需确保您使用了OSS Java SDK 3.9.1及以上版本,同时在Maven项目中引入Bouncy Castle提供的Java库(bcprov-jdk15on)。

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.62</version>
</dependency>

如果运行时报java.security.InvalidKeyException: Illegal key size or default parameters异常,则需要补充Oracle的JCE文件,将其部署在JRE的环境中。

请根据使用的JDK版本分别下载对应的文件,将其解压后保存在jre/lib/security目录下。

创建RSA加密客户端

创建RSA加密客户端之前,需要创建非对称密钥KeyPair对象。OSS Java SDK提供了从PKCS1编码或PKCS8编码的pem格式私钥字符串到RSAPrivateKey对象的转换,以及从X509编码pem格式公钥字符串到RSAPublicKey对象的转换。

上述密钥对应的转换方法如下:

RSAPrivateKey SimpleRSAEncryptionMaterials.getPrivateKeyFromPemPKCS1(String privateKeyStr);

RSAPrivateKey SimpleRSAEncryptionMaterials.getPrivateKeyFromPemPKCS8(String privateKeyStr);

RSAPublicKey SimpleRSAEncryptionMaterials.getPublicKeyFromPemX509(String publicKeyStr);

创建RSA加密客户端示例代码如下:

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.crypto.SimpleRSAEncryptionMaterials;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) throws Throwable {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 您可以使用以下命令分别生成私钥与公钥pem文件,然后复制pem文件中的字符串到PRIVATE_PKCS1_PEM,PUBLIC_X509_PEM变量中。
        // openssl genrsa -out private_key.pem 2048
        // openssl rsa -in private_key.pem -out rsa_public_key.pem -pubout

        // 填写您的RSA私钥字符串,可以使用OpenSSL工具生成。以下为RSA私钥字符串的示例值。
        final String PRIVATE_PKCS1_PEM =
                "-----BEGIN RSA PRIVATE KEY-----\n" +
                "MIICWwIBAAKBgQCokfiAVXXf5ImFzKDw+XO/UByW6mse2QsIgz3ZwBtMNu59fR5z\n" +
                "ttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC5MFO1PByrE/MNd5AAfSVba93\n" +
                "I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MmR1EKib1Id8hpooY5xaQIDAQAB\n" +
                "AoGAOPUZgkNeEMinrw31U3b2JS5sepG6oDG2CKpPu8OtdZMaAkzEfVTJiVoJpP2Y\n" +
                "nPZiADhFW3e0ZAnak9BPsSsySRaSNmR465cG9tbqpXFKh9Rp/sCPo4Jq2n65yood\n" +
                "JBrnGr6/xhYvNa14sQ6xjjfSgRNBSXD1XXNF4kALwgZyCAECQQDV7t4bTx9FbEs5\n" +
                "36nAxPsPM6aACXaOkv6d9LXI7A0J8Zf42FeBV6RK0q7QG5iNNd1WJHSXIITUizVF\n" +
                "6aX5NnvFAkEAybeXNOwUvYtkgxF4s28s6gn11c5HZw4/a8vZm2tXXK/QfTQrJVXp\n" +
                "VwxmSr0FAajWAlcYN/fGkX1pWA041CKFVQJAG08ozzekeEpAuByTIOaEXgZr5MBQ\n" +
                "gBbHpgZNBl8Lsw9CJSQI15wGfv6yDiLXsH8FyC9TKs+d5Tv4Cvquk0efOQJAd9OC\n" +
                "lCKFs48hdyaiz9yEDsc57PdrvRFepVdj/gpGzD14mVerJbOiOF6aSV19ot27u4on\n" +
                "Td/3aifYs0CveHzFPQJAWb4LCDwqLctfzziG7/S7Z74gyq5qZF4FUElOAZkz123E\n" +
                "yZvADwuz/4aK0od0lX9c4Jp7Mo5vQ4TvdoBnPuGo****\n" +
                "-----END RSA PRIVATE KEY-----";
        // 填写您的RSA公钥字符串,可以使用OpenSSL工具生成。以下为RSA公钥字符串的示例值。
        final String PUBLIC_X509_PEM =
                "-----BEGIN PUBLIC KEY-----\n" +
                "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCokfiAVXXf5ImFzKDw+XO/UByW\n" +
                "6mse2QsIgz3ZwBtMNu59fR5zttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC\n" +
                "5MFO1PByrE/MNd5AAfSVba93I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MnR\n" +
                "1EKib1Id8hpooY5xaQID****\n" +
                "-----END PUBLIC KEY-----";

        // 创建一个RSA密钥对。
        RSAPrivateKey privateKey = SimpleRSAEncryptionMaterials.getPrivateKeyFromPemPKCS1(PRIVATE_PKCS1_PEM);
        RSAPublicKey publicKey = SimpleRSAEncryptionMaterials.getPublicKeyFromPemX509(PUBLIC_X509_PEM);
        KeyPair keyPair = new KeyPair(publicKey, privateKey);

        // 创建主密钥RSA的描述信息,创建后不允许修改。主密钥描述信息和主密钥一一对应。
        // 如果所有的Object都使用相同的主密钥,主密钥描述信息可以为空,但后续不支持更换主密钥。
        // 如果主密钥描述信息为空,解密时无法判断文件使用的是哪个主密钥进行加密。
        // 强烈建议为每个主密钥都配置描述信息,由客户端保存主密钥和描述信息之间的对应关系(服务端不保存两者之间的对应关系)。
        Map<String, String> matDesc = new HashMap<String, String>();
        matDesc.put("desc-key", "desc-value");

        // 创建RSA加密材料。
        SimpleRSAEncryptionMaterials encryptionMaterials = new SimpleRSAEncryptionMaterials(keyPair, matDesc);
        // 如果要下载并解密其他RSA密钥加密的文件,请将其他主密钥及其描述信息添加到加密材料中。
        // encryptionMaterials.addKeyPairDescMaterial(<otherKeyPair>, <otherKeyPairMatDesc>);

        // 创建RSA加密客户端。
        OSSEncryptionClient ossEncryptionClient = new OSSEncryptionClientBuilder().
                build(endpoint, credentialsProvider, encryptionMaterials);

        try {
            // 填写具体业务代码。
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossEncryptionClient != null) {
                ossEncryptionClient.shutdown();
            }
        }
    }
}

创建KMS加密客户端

说明

创建KMS加密客户端时,除了要求在Maven项目中引入Bouncy Castle提供的Java库(bcprov-jdk15on)以外,还需要添加阿里云KMS提供的Java库(kms-transfer-client)。

<dependency>
    <groupId>com.aliyun.kms</groupId>
    <artifactId>kms-transfer-client</artifactId>
    <version>0.1.0</version>
    <scope>test</scope>
</dependency>

创建KMS加密客户端示例代码如下:

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.crypto.ContentCryptoMaterialRW;
import com.aliyun.oss.crypto.EncryptionMaterials;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.http.FormatType;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.kms.model.v20160120.DecryptRequest;
import com.aliyuncs.kms.model.v20160120.DecryptResponse;
import com.aliyuncs.kms.model.v20160120.EncryptRequest;
import com.aliyuncs.kms.model.v20160120.EncryptResponse;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) throws Throwable {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 用户主密钥,例如e1935511-cf88-1123-a0f8-1be8d251****。
        String cmk = "e1935511-cf88-1123-a0f8-1be8d251****";
        // cmk所在的region,例如cn-hangzhou。
        String region = "cn-hangzhou";


        // 创建主密钥KMS的描述信息,创建后不允许修改。主密钥描述信息、region以及用户主密钥(cmk)是一一对应关系。
        // 如果所有的Object都使用相同的cmk,主密钥描述信息可以为空,但后续不支持更换主密钥。
        // 如果主密钥描述信息为空,解密时无法判断文件使用的是哪个主密钥进行加密。
        // 强烈建议为每个主密钥都配置主密钥描述信息,由客户端保存主密钥和描述信息之间的对应关系(服务端不保存两者之间的对应关系)。
        Map<String, String> matDesc = new HashMap<String, String>();
        matDesc.put("desc-key", "desc-value");

        // 创建KMS加密材料。
        KmsEncryptionMaterialsV3 encryptionMaterials = new KmsEncryptionMaterialsV3(region, cmk, matDesc);

        encryptionMaterials.setKmsCredentialsProvider(credentialsProvider);

        // 如果要下载并解密其他cmk加密的文件,请将cmk的region名称以及描述信息添加到KMS加密材料中。
        // encryptionMaterials.addKmsDescMaterial(<otherKmsRegion>, <otherKmsMatDesc>);
        // 如果要下载并解密其他cmk加密的文件,且在KMS的账号不同于OSS客户端账号的情况下,请将KMS的region名称、凭证以及描述信息添加到加密材料中。
        // encryptionMaterials.addKmsDescMaterial(<otherKmsRegion>, <otherKmsCredentialsProvider>, <otherKmsMatDesc>);

        // 创建加密客户端。
        OSSEncryptionClient ossEncryptionClient = new OSSEncryptionClientBuilder().
                build(endpoint, credentialsProvider, encryptionMaterials);

        try {
            // 填写具体业务代码。
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossEncryptionClient != null) {
                ossEncryptionClient.shutdown();
            }
        }
    }
}


class KmsEncryptionMaterialsV3 implements EncryptionMaterials {
    private static final String KEY_WRAP_ALGORITHM = "KMS/ALICLOUD";
    private String region;
    private String cmk;
    CredentialsProvider credentialsProvider;

    private final Map<String, String> desc;
    private final LinkedHashMap<KmsEncryptionMaterialsV3.KmsClientSuite, Map<String, String>> kmsDescMaterials =
            new LinkedHashMap<KmsEncryptionMaterialsV3.KmsClientSuite, Map<String, String>>();

    public KmsEncryptionMaterialsV3(String region, String cmk) {
        assertParameterNotNull(region, "kms region");
        assertParameterNotNull(cmk, "kms cmk");
        this.region = region;
        this.cmk = cmk;
        this.desc = new HashMap<String, String>();
    }

    public KmsEncryptionMaterialsV3(String region, String cmk, Map<String, String> desc) {
        assertParameterNotNull(region, "kms region");
        assertParameterNotNull(region, "kms cmk");
        this.region = region;
        this.cmk = cmk;
        this.desc = (desc == null) ? new HashMap<String, String>() : new HashMap<String, String>(desc);
    }

    private final class KmsClientSuite {
        private String region;
        private CredentialsProvider credentialsProvider;
        KmsClientSuite(String region, CredentialsProvider credentialsProvider) {
            this.region = region;
            this.credentialsProvider = credentialsProvider;
        }
    }

    public void setKmsCredentialsProvider(CredentialsProvider credentialsProvider) {
        this.credentialsProvider = credentialsProvider;
        kmsDescMaterials.put(new KmsEncryptionMaterialsV3.KmsClientSuite(region, credentialsProvider), desc);
    }

    private DefaultAcsClient createKmsClient(String region, CredentialsProvider credentialsPorvider) {
        Credentials credentials = credentialsPorvider.getCredentials();
        IClientProfile profile = DefaultProfile.getProfile(region, credentials.getAccessKeyId(),
                credentials.getSecretAccessKey(), credentials.getSecurityToken());
        return new KmsTransferAcsClient(profile);
    }

    private EncryptResponse encryptPlainText(String keyId, String plainText) throws ClientException {
        DefaultAcsClient kmsClient = createKmsClient(region, credentialsProvider);
        final EncryptRequest encReq = new EncryptRequest();
        encReq.setSysProtocol(ProtocolType.HTTPS);
        encReq.setAcceptFormat(FormatType.JSON);
        encReq.setSysMethod(MethodType.POST);
        encReq.setKeyId(keyId);
        encReq.setPlaintext(plainText);

        final EncryptResponse encResponse;
        try {
            encResponse = kmsClient.getAcsResponse(encReq);
        } catch (Exception e) {
            throw new ClientException("the kms client encrypt data failed." + e.getMessage(), e);
        }
        return encResponse;
    }

    private DecryptResponse decryptCipherBlob(KmsEncryptionMaterialsV3.KmsClientSuite kmsClientSuite, String cipherBlob)
            throws ClientException {
        final DefaultAcsClient kmsClient = createKmsClient(kmsClientSuite.region, kmsClientSuite.credentialsProvider);
        final DecryptRequest decReq = new DecryptRequest();
        decReq.setSysProtocol(ProtocolType.HTTPS);
        decReq.setAcceptFormat(FormatType.JSON);
        decReq.setSysMethod(MethodType.POST);
        decReq.setCiphertextBlob(cipherBlob);

        final DecryptResponse decResponse;
        try {
            decResponse = kmsClient.getAcsResponse(decReq);
        } catch (Exception e) {
            throw new ClientException("The kms client decrypt data faild." + e.getMessage(), e);
        }
        return decResponse;
    }

    public void addKmsDescMaterial(String region, Map<String, String> description) {
        addKmsDescMaterial(region, credentialsProvider, description);
    }

    public synchronized void addKmsDescMaterial(String region, CredentialsProvider credentialsProvider, Map<String, String> description) {
        assertParameterNotNull(region, "region");
        assertParameterNotNull(credentialsProvider, "credentialsProvider");
        KmsEncryptionMaterialsV3.KmsClientSuite kmsClientSuite = new KmsEncryptionMaterialsV3.KmsClientSuite(region, credentialsProvider);
        if (description != null) {
            kmsDescMaterials.put(kmsClientSuite, new HashMap<String, String>(description));
        } else {
            kmsDescMaterials.put(kmsClientSuite, new HashMap<String, String>());
        }
    }

    private KmsEncryptionMaterialsV3.KmsClientSuite findKmsClientSuiteByDescription(Map<String, String> desc) {
        if (desc == null) {
            return null;
        }
        for (Map.Entry<KmsEncryptionMaterialsV3.KmsClientSuite, Map<String, String>> entry : kmsDescMaterials.entrySet()) {
            if (desc.equals(entry.getValue())) {
                return entry.getKey();
            }
        }
        return null;
    }

    private <K, V> Map.Entry<K, V> getTailByReflection(LinkedHashMap<K, V> map)
            throws NoSuchFieldException, IllegalAccessException {
        Field tail = map.getClass().getDeclaredField("tail");
        tail.setAccessible(true);
        return (Map.Entry<K, V>) tail.get(map);
    }

    @Override
    public void encryptCEK(ContentCryptoMaterialRW contentMaterialRW) {
        try {
            assertParameterNotNull(contentMaterialRW, "contentMaterialRW");
            assertParameterNotNull(contentMaterialRW.getIV(), "contentMaterialRW#getIV");
            assertParameterNotNull(contentMaterialRW.getCEK(), "contentMaterialRW#getCEK");

            byte[] iv = contentMaterialRW.getIV();
            EncryptResponse encryptresponse = encryptPlainText(cmk, BinaryUtil.toBase64String(iv));
            byte[] encryptedIV = BinaryUtil.fromBase64String(encryptresponse.getCiphertextBlob());

            SecretKey cek = contentMaterialRW.getCEK();
            encryptresponse = encryptPlainText(cmk, BinaryUtil.toBase64String(cek.getEncoded()));
            byte[] encryptedCEK = BinaryUtil.fromBase64String(encryptresponse.getCiphertextBlob());

            contentMaterialRW.setEncryptedCEK(encryptedCEK);
            contentMaterialRW.setEncryptedIV(encryptedIV);
            contentMaterialRW.setKeyWrapAlgorithm(KEY_WRAP_ALGORITHM);
            contentMaterialRW.setMaterialsDescription(desc);
        } catch (Exception e) {
            throw new ClientException("Kms encrypt CEK IV error. "
                    + "Please check your cmk, region, accessKeyId and accessSecretId." + e.getMessage(), e);
        }
    }

    @Override
    public void decryptCEK(ContentCryptoMaterialRW contentMaterialRW) {
        assertParameterNotNull(contentMaterialRW, "ContentCryptoMaterialRW");
        assertParameterNotNull(contentMaterialRW.getEncryptedCEK(), "ContentCryptoMaterialRW#getEncryptedCEK");
        assertParameterNotNull(contentMaterialRW.getEncryptedIV(), "ContentCryptoMaterialRW#getEncryptedIV");
        assertParameterNotNull(contentMaterialRW.getKeyWrapAlgorithm(), "ContentCryptoMaterialRW#getKeyWrapAlgorithm");

        if (!contentMaterialRW.getKeyWrapAlgorithm().toLowerCase().equals(KEY_WRAP_ALGORITHM.toLowerCase())) {
            throw new ClientException(
                    "Unrecognize your object key wrap algorithm: " + contentMaterialRW.getKeyWrapAlgorithm());
        }

        try {
            KmsEncryptionMaterialsV3.KmsClientSuite kmsClientSuite = findKmsClientSuiteByDescription(contentMaterialRW.getMaterialsDescription());
            if (kmsClientSuite == null) {
                Map.Entry<KmsEncryptionMaterialsV3.KmsClientSuite, Map<String, String>> entry = getTailByReflection(kmsDescMaterials);
                kmsClientSuite = entry.getKey();
            }

            DecryptResponse decryptIvResp = decryptCipherBlob(kmsClientSuite,
                    BinaryUtil.toBase64String(contentMaterialRW.getEncryptedIV()));
            byte[] iv = BinaryUtil.fromBase64String(decryptIvResp.getPlaintext());

            DecryptResponse decryptCEKResp = decryptCipherBlob(kmsClientSuite,
                    BinaryUtil.toBase64String(contentMaterialRW.getEncryptedCEK()));
            byte[] cekBytes = BinaryUtil.fromBase64String(decryptCEKResp.getPlaintext());
            SecretKey cek = new SecretKeySpec(cekBytes, "");

            contentMaterialRW.setCEK(cek);
            contentMaterialRW.setIV(iv);
        } catch (Exception e) {
            throw new ClientException("Unable to decrypt content secured key and iv. "
                    + "Please check your kms region and materails description." + e.getMessage(), e);
        }
    }

    private void assertParameterNotNull(Object parameterValue, String errorMessage) {
        if (parameterValue == null)
            throw new IllegalArgumentException(errorMessage);
    }
}

以下提供了如何使用用户自主管理的主密钥(RSA)进行普通上传和下载文件、分片上传、范围下载等场景的完整示例。

说明

使用主密钥KMS与使用主密钥RSA的方式,区别仅在于OSSEncryptionClient的创建过程。

普通上传和下载文件

使用主密钥RSA进行普通上传和下载Object示例代码如下:

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.crypto.SimpleRSAEncryptionMaterials;
import com.aliyun.oss.model.OSSObject;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) throws Throwable {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "examplebucket";
        // 填写Object完整路径,例如exampleobject.txt。Object完整路径中不能包含Bucket名称。
        String objectName = "exampleobject.txt";
        String content = "Hello OSS!";

        // 填写您的RSA私钥字符串,可以使用OpenSSL工具生成。以下为RSA私钥字符串的示例值。
        final String PRIVATE_PKCS1_PEM =
                "-----BEGIN RSA PRIVATE KEY-----\n" +
                "MIICWwIBAAKBgQCokfiAVXXf5ImFzKDw+XO/UByW6mse2QsIgz3ZwBtMNu59fR5z\n" +
                "ttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC5MFO1PByrE/MNd5AAfSVba93\n" +
                "I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MmR1EKib1Id8hpooY5xaQIDAQAB\n" +
                "AoGAOPUZgkNeEMinrw31U3b2JS5sepG6oDG2CKpPu8OtdZMaAkzEfVTJiVoJpP2Y\n" +
                "nPZiADhFW3e0ZAnak9BPsSsySRaSNmR465cG9tbqpXFKh9Rp/sCPo4Jq2n65yood\n" +
                "JBrnGr6/xhYvNa14sQ6xjjfSgRNBSXD1XXNF4kALwgZyCAECQQDV7t4bTx9FbEs5\n" +
                "36nAxPsPM6aACXaOkv6d9LXI7A0J8Zf42FeBV6RK0q7QG5iNNd1WJHSXIITUizVF\n" +
                "6aX5NnvFAkEAybeXNOwUvYtkgxF4s28s6gn11c5HZw4/a8vZm2tXXK/QfTQrJVXp\n" +
                "VwxmSr0FAajWAlcYN/fGkX1pWA041CKFVQJAG08ozzekeEpAuByTIOaEXgZr5MBQ\n" +
                "gBbHpgZNBl8Lsw9CJSQI15wGfv6yDiLXsH8FyC9TKs+d5Tv4Cvquk0efOQJAd9OC\n" +
                "lCKFs48hdyaiz9yEDsc57PdrvRFepVdj/gpGzD14mVerJbOiOF6aSV19ot27u4on\n" +
                "Td/3aifYs0CveHzFPQJAWb4LCDwqLctfzziG7/S7Z74gyq5qZF4FUElOAZkz123E\n" +
                "yZvADwuz/4aK0od0lX9c4Jp7Mo5vQ4TvdoBnPuGo****\n" +
                "-----END RSA PRIVATE KEY-----";
        // 填写您的RSA公钥字符串,可以使用OpenSSL工具生成。以下为RSA公钥字符串的示例值。
        final String PUBLIC_X509_PEM =
                "-----BEGIN PUBLIC KEY-----\n" +
                "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCokfiAVXXf5ImFzKDw+XO/UByW\n" +
                "6mse2QsIgz3ZwBtMNu59fR5zttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC\n" +
                "5MFO1PByrE/MNd5AAfSVba93I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MnR\n" +
                "1EKib1Id8hpooY5xaQID****\n" +
                "-----END PUBLIC KEY-----";

        // 创建一个RSA密钥对。
        RSAPrivateKey privateKey = SimpleRSAEncryptionMaterials.getPrivateKeyFromPemPKCS1(PRIVATE_PKCS1_PEM);
        RSAPublicKey publicKey = SimpleRSAEncryptionMaterials.getPublicKeyFromPemX509(PUBLIC_X509_PEM);
        KeyPair keyPair = new KeyPair(publicKey, privateKey);

        // 创建主密钥RSA的描述信息。创建后不允许修改。主密钥描述信息和主密钥一一对应。
        // 如果所有的object都使用相同的主密钥,主密钥描述信息可以为空,但后续不支持更换主密钥。
        // 如果主密钥描述信息为空,解密时无法判断文件使用的是哪个主密钥进行加密。
        // 强烈建议为每个主密钥都配置描述信息,由客户端保存主密钥和描述信息之间的对应关系(服务端不保存两者之间的对应关系)。
        Map<String, String> matDesc = new HashMap<String, String>();
        matDesc.put("desc-key", "desc-value");

        // 创建RSA加密材料。
        SimpleRSAEncryptionMaterials encryptionMaterials = new SimpleRSAEncryptionMaterials(keyPair, matDesc);
        // 如果要下载并解密其他RSA密钥加密的文件,请将其他主密钥及其描述信息添加到加密材料中。
        // encryptionMaterials.addKeyPairDescMaterial(<otherKeyPair>, <otherKeyPairMatDesc>);

        // 创建加密客户端。
        OSSEncryptionClient ossEncryptionClient = new OSSEncryptionClientBuilder().
                build(endpoint, credentialsProvider, encryptionMaterials);

        try {
            // 加密上传文件。
            ossEncryptionClient.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));

            // 下载文件时自动解密。
            OSSObject ossObject = ossEncryptionClient.getObject(bucketName, objectName);
            BufferedReader reader = new BufferedReader(new InputStreamReader(ossObject.getObjectContent()));
            StringBuffer buffer = new StringBuffer();
            String line;
            while ((line = reader.readLine()) != null) {
                buffer.append(line);
            }
            reader.close();

            // 查看解密后的内容是否与上传的明文一致。
            System.out.println("Put plain text: " + content);
            System.out.println("Get and decrypted text: " + buffer.toString());
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossEncryptionClient != null) {
                ossEncryptionClient.shutdown();
            }
        }
    }
}

分片上传

使用主密钥RSA进行分片上传的示例代码如下:

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.crypto.MultipartUploadCryptoContext;
import com.aliyun.oss.crypto.SimpleRSAEncryptionMaterials;
import com.aliyun.oss.model.*;

import java.io.*;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Demo {
    public static void main(String[] args) throws Throwable {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "examplebucket";
        // 填写Object完整路径,例如exampleobject.txt。Object完整路径中不能包含Bucket名称。
        String objectName = "exampleobject.txt";
        // 文件地址。例如:D:\\localpath\\examplefile.txt
        String localFile = "D:\\localpath\\examplefile.txt";

        // 填写您的RSA私钥字符串,可以使用OpenSSL工具生成。以下为RSA私钥字符串的示例值。
        final String PRIVATE_PKCS1_PEM =
                "-----BEGIN RSA PRIVATE KEY-----\n" +
                "MIICWwIBAAKBgQCokfiAVXXf5ImFzKDw+XO/UByW6mse2QsIgz3ZwBtMNu59fR5z\n" +
                "ttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC5MFO1PByrE/MNd5AAfSVba93\n" +
                "I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MmR1EKib1Id8hpooY5xaQIDAQAB\n" +
                "AoGAOPUZgkNeEMinrw31U3b2JS5sepG6oDG2CKpPu8OtdZMaAkzEfVTJiVoJpP2Y\n" +
                "nPZiADhFW3e0ZAnak9BPsSsySRaSNmR465cG9tbqpXFKh9Rp/sCPo4Jq2n65yood\n" +
                "JBrnGr6/xhYvNa14sQ6xjjfSgRNBSXD1XXNF4kALwgZyCAECQQDV7t4bTx9FbEs5\n" +
                "36nAxPsPM6aACXaOkv6d9LXI7A0J8Zf42FeBV6RK0q7QG5iNNd1WJHSXIITUizVF\n" +
                "6aX5NnvFAkEAybeXNOwUvYtkgxF4s28s6gn11c5HZw4/a8vZm2tXXK/QfTQrJVXp\n" +
                "VwxmSr0FAajWAlcYN/fGkX1pWA041CKFVQJAG08ozzekeEpAuByTIOaEXgZr5MBQ\n" +
                "gBbHpgZNBl8Lsw9CJSQI15wGfv6yDiLXsH8FyC9TKs+d5Tv4Cvquk0efOQJAd9OC\n" +
                "lCKFs48hdyaiz9yEDsc57PdrvRFepVdj/gpGzD14mVerJbOiOF6aSV19ot27u4on\n" +
                "Td/3aifYs0CveHzFPQJAWb4LCDwqLctfzziG7/S7Z74gyq5qZF4FUElOAZkz123E\n" +
                "yZvADwuz/4aK0od0lX9c4Jp7Mo5vQ4TvdoBnPuGo****\n" +
                "-----END RSA PRIVATE KEY-----";
        // 填写您的RSA公钥字符串,可以使用OpenSSL工具生成。以下为RSA公钥字符串的示例值。
        final String PUBLIC_X509_PEM =
                "-----BEGIN PUBLIC KEY-----\n" +
                "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCokfiAVXXf5ImFzKDw+XO/UByW\n" +
                "6mse2QsIgz3ZwBtMNu59fR5zttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC\n" +
                "5MFO1PByrE/MNd5AAfSVba93I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MnR\n" +
                "1EKib1Id8hpooY5xaQID****\n" +
                "-----END PUBLIC KEY-----";

        // 创建一个RSA密钥对。
        RSAPrivateKey privateKey = SimpleRSAEncryptionMaterials.getPrivateKeyFromPemPKCS1(PRIVATE_PKCS1_PEM);
        RSAPublicKey publicKey = SimpleRSAEncryptionMaterials.getPublicKeyFromPemX509(PUBLIC_X509_PEM);
        KeyPair keyPair = new KeyPair(publicKey, privateKey);

        // 创建主密钥RSA的描述信息,创建后不允许修改。主密钥描述信息和主密钥一一对应。
        // 如果所有的Object都使用相同的主密钥,主密钥描述信息可以为空,但后续不支持更换主密钥。
        // 如果主密钥描述信息为空,解密时无法判断文件使用的是哪个主密钥进行加密。
        // 强烈建议为每个主密钥都配置描述信息,由客户端保存主密钥和描述信息之间的对应关系(服务端不保存两者之间的对应关系)。
        Map<String, String> matDesc = new HashMap<String, String>();
        matDesc.put("desc-key", "desc-value");

        // 创建RSA加密材料。
        SimpleRSAEncryptionMaterials encryptionMaterials = new SimpleRSAEncryptionMaterials(keyPair, matDesc);
        // 如果要下载并解密其他RSA密钥加密的文件,请将其他主密钥及其描述信息添加到加密材料中。
        // encryptionMaterials.addKeyPairDescMaterial(<otherKeyPair>, <otherKeyPairMatDesc>);

        // 创建加密客户端。
        OSSEncryptionClient ossEncryptionClient = new OSSEncryptionClientBuilder().
                build(endpoint, credentialsProvider, encryptionMaterials);

        try {
            // 创建MultipartUploadCryptoContext对象,指定分片大小与文件大小。分片大小需16字节对齐。
            File file = new File(localFile);
            long fileLength = file.length();
            final long partSize = 100 * 1024L;   // 100K
            MultipartUploadCryptoContext context = new MultipartUploadCryptoContext();
            context.setPartSize(partSize);
            context.setDataSize(fileLength);

            // 初始化一个分片上传事件。
            InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName, objectName);
            // 传入MultipartUploadCryptoContext对象。
            InitiateMultipartUploadResult upresult = ossEncryptionClient.initiateMultipartUpload(initiateMultipartUploadRequest, context);
            String uploadId = upresult.getUploadId();

            // 创建PartETag的集合。PartETag由分片的ETag和分片号组成。
            List<PartETag> partETags =  new ArrayList<PartETag>();
            int partCount = (int) (fileLength / partSize);
            if (fileLength % partSize != 0) {
                partCount++;
            }

            // 遍历分片上传。
            for (int i = 0; i < partCount; i++) {
                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                InputStream instream = new FileInputStream(file);
                instream.skip(startPos);
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(bucketName);
                uploadPartRequest.setKey(objectName);
                uploadPartRequest.setUploadId(uploadId);
                uploadPartRequest.setInputStream(instream);
                uploadPartRequest.setPartSize(curPartSize);
                uploadPartRequest.setPartNumber( i + 1);
                // 传入MultipartUploadCryptoContext对象。
                UploadPartResult uploadPartResult = ossEncryptionClient.uploadPart(uploadPartRequest, context);
                partETags.add(uploadPartResult.getPartETag());
            }

            // 完成分片上传。
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
            ossEncryptionClient.completeMultipartUpload(completeMultipartUploadRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossEncryptionClient != null) {
                ossEncryptionClient.shutdown();
            }
        }
    }
}

断点续传上传

使用主密钥RSA进行断点续传上传的示例代码如下:

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.crypto.SimpleRSAEncryptionMaterials;
import com.aliyun.oss.model.*;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) throws Throwable {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "examplebucket";
        // 填写Object完整路径,例如exampleobject.txt。Object完整路径中不能包含Bucket名称。
        String objectName = "exampleobject.txt";
        // 文件地址。例如:D:\\localpath\\examplefile.txt
        String localFile = "D:\\localpath\\examplefile.txt";

        // 填写您的RSA私钥字符串,可以使用OpenSSL工具生成。以下为RSA私钥字符串的示例值。
        final String PRIVATE_PKCS1_PEM =
                "-----BEGIN RSA PRIVATE KEY-----\n" +
                "MIICWwIBAAKBgQCokfiAVXXf5ImFzKDw+XO/UByW6mse2QsIgz3ZwBtMNu59fR5z\n" +
                "ttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC5MFO1PByrE/MNd5AAfSVba93\n" +
                "I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MmR1EKib1Id8hpooY5xaQIDAQAB\n" +
                "AoGAOPUZgkNeEMinrw31U3b2JS5sepG6oDG2CKpPu8OtdZMaAkzEfVTJiVoJpP2Y\n" +
                "nPZiADhFW3e0ZAnak9BPsSsySRaSNmR465cG9tbqpXFKh9Rp/sCPo4Jq2n65yood\n" +
                "JBrnGr6/xhYvNa14sQ6xjjfSgRNBSXD1XXNF4kALwgZyCAECQQDV7t4bTx9FbEs5\n" +
                "36nAxPsPM6aACXaOkv6d9LXI7A0J8Zf42FeBV6RK0q7QG5iNNd1WJHSXIITUizVF\n" +
                "6aX5NnvFAkEAybeXNOwUvYtkgxF4s28s6gn11c5HZw4/a8vZm2tXXK/QfTQrJVXp\n" +
                "VwxmSr0FAajWAlcYN/fGkX1pWA041CKFVQJAG08ozzekeEpAuByTIOaEXgZr5MBQ\n" +
                "gBbHpgZNBl8Lsw9CJSQI15wGfv6yDiLXsH8FyC9TKs+d5Tv4Cvquk0efOQJAd9OC\n" +
                "lCKFs48hdyaiz9yEDsc57PdrvRFepVdj/gpGzD14mVerJbOiOF6aSV19ot27u4on\n" +
                "Td/3aifYs0CveHzFPQJAWb4LCDwqLctfzziG7/S7Z74gyq5qZF4FUElOAZkz123E\n" +
                "yZvADwuz/4aK0od0lX9c4Jp7Mo5vQ4TvdoBnPuGo****\n" +
                "-----END RSA PRIVATE KEY-----";
        // 填写您的RSA公钥字符串,可以使用OpenSSL工具生成。以下为RSA公钥字符串的示例值。
        final String PUBLIC_X509_PEM =
                "-----BEGIN PUBLIC KEY-----\n" +
                "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCokfiAVXXf5ImFzKDw+XO/UByW\n" +
                "6mse2QsIgz3ZwBtMNu59fR5zttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC\n" +
                "5MFO1PByrE/MNd5AAfSVba93I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MnR\n" +
                "1EKib1Id8hpooY5xaQID****\n" +
                "-----END PUBLIC KEY-----";

        // 创建一个RSA密钥对。
        RSAPrivateKey privateKey = SimpleRSAEncryptionMaterials.getPrivateKeyFromPemPKCS1(PRIVATE_PKCS1_PEM);
        RSAPublicKey publicKey = SimpleRSAEncryptionMaterials.getPublicKeyFromPemX509(PUBLIC_X509_PEM);
        KeyPair keyPair = new KeyPair(publicKey, privateKey);

        // 创建主密钥RSA的描述信息,创建后不允许修改。主密钥描述信息和主密钥一一对应。
        // 如果所有的Object都使用相同的主密钥,主密钥描述信息可以为空,但后续不支持更换主密钥。
        // 如果主密钥描述信息为空,解密时无法判断文件使用的是哪个主密钥进行加密。
        // 强烈建议为每个主密钥都配置描述信息,由客户端保存主密钥和描述信息之间的对应关系(服务端不保存两者之间的对应关系)。
        Map<String, String> matDesc = new HashMap<String, String>();
        matDesc.put("desc-key", "desc-value");

        // 创建RSA加密材料。
        SimpleRSAEncryptionMaterials encryptionMaterials = new SimpleRSAEncryptionMaterials(keyPair, matDesc);
        // 如果要下载并解密其他RSA密钥加密的文件,请将其他主密钥及其描述信息添加到加密材料中。
        // encryptionMaterials.addKeyPairDescMaterial(<otherKeyPair>, <otherKeyPairMatDesc>);

        // 创建加密客户端。
        OSSEncryptionClient ossEncryptionClient = new OSSEncryptionClientBuilder().
                build(endpoint, credentialsProvider, encryptionMaterials);

        try {
            // 创建UploadFileRequest对象。
            UploadFileRequest uploadFileRequest = new UploadFileRequest(bucketName, objectName);

            // 设置要上传的文件的路径。
            uploadFileRequest.setUploadFile(localFile);
            // 指定上传的分片大小,范围为100 KB~5 GB,默认为文件大小的1/10000。
            uploadFileRequest.setPartSize(100 * 1024);
            // 开启断点续传,默认关闭。
            uploadFileRequest.setEnableCheckpoint(true);
            // 设置断点记录文件。如未指定,则默认名称为localfile + ".ucp",与localfile同目录。
            // 上传过程中的进度信息会保存在该文件中,如果某一分片上传失败,再次上传时会根据文件中记录的点继续上传。上传完成后,该文件会被删除。
            uploadFileRequest.setCheckpointFile("test-upload.ucp");

            // 开始断点续传上传。
            ossEncryptionClient.uploadFile(uploadFileRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossEncryptionClient != null) {
                ossEncryptionClient.shutdown();
            }
        }
    }
}

断点续传下载

使用主密钥RSA进行断点续传下载的示例代码如下:

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.crypto.SimpleRSAEncryptionMaterials;
import com.aliyun.oss.model.*;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) throws Throwable {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "examplebucket";
        // 填写Object完整路径,例如exampleobject.txt。Object完整路径中不能包含Bucket名称。
        String objectName = "exampleobject.txt";

        // 填写您的RSA私钥字符串,可以使用OpenSSL工具生成。以下为RSA私钥字符串的示例值。
        final String PRIVATE_PKCS1_PEM =
                "-----BEGIN RSA PRIVATE KEY-----\n" +
                "MIICWwIBAAKBgQCokfiAVXXf5ImFzKDw+XO/UByW6mse2QsIgz3ZwBtMNu59fR5z\n" +
                "ttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC5MFO1PByrE/MNd5AAfSVba93\n" +
                "I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MmR1EKib1Id8hpooY5xaQIDAQAB\n" +
                "AoGAOPUZgkNeEMinrw31U3b2JS5sepG6oDG2CKpPu8OtdZMaAkzEfVTJiVoJpP2Y\n" +
                "nPZiADhFW3e0ZAnak9BPsSsySRaSNmR465cG9tbqpXFKh9Rp/sCPo4Jq2n65yood\n" +
                "JBrnGr6/xhYvNa14sQ6xjjfSgRNBSXD1XXNF4kALwgZyCAECQQDV7t4bTx9FbEs5\n" +
                "36nAxPsPM6aACXaOkv6d9LXI7A0J8Zf42FeBV6RK0q7QG5iNNd1WJHSXIITUizVF\n" +
                "6aX5NnvFAkEAybeXNOwUvYtkgxF4s28s6gn11c5HZw4/a8vZm2tXXK/QfTQrJVXp\n" +
                "VwxmSr0FAajWAlcYN/fGkX1pWA041CKFVQJAG08ozzekeEpAuByTIOaEXgZr5MBQ\n" +
                "gBbHpgZNBl8Lsw9CJSQI15wGfv6yDiLXsH8FyC9TKs+d5Tv4Cvquk0efOQJAd9OC\n" +
                "lCKFs48hdyaiz9yEDsc57PdrvRFepVdj/gpGzD14mVerJbOiOF6aSV19ot27u4on\n" +
                "Td/3aifYs0CveHzFPQJAWb4LCDwqLctfzziG7/S7Z74gyq5qZF4FUElOAZkz123E\n" +
                "yZvADwuz/4aK0od0lX9c4Jp7Mo5vQ4TvdoBnPuGo****\n" +
                "-----END RSA PRIVATE KEY-----";
        // 填写您的RSA公钥字符串,可以使用OpenSSL工具生成。以下为RSA公钥字符串的示例值。
        final String PUBLIC_X509_PEM =
                "-----BEGIN PUBLIC KEY-----\n" +
                "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCokfiAVXXf5ImFzKDw+XO/UByW\n" +
                "6mse2QsIgz3ZwBtMNu59fR5zttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC\n" +
                "5MFO1PByrE/MNd5AAfSVba93I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MnR\n" +
                "1EKib1Id8hpooY5xaQID****\n" +
                "-----END PUBLIC KEY-----";

        // 创建一个RSA密钥对。
        RSAPrivateKey privateKey = SimpleRSAEncryptionMaterials.getPrivateKeyFromPemPKCS1(PRIVATE_PKCS1_PEM);
        RSAPublicKey publicKey = SimpleRSAEncryptionMaterials.getPublicKeyFromPemX509(PUBLIC_X509_PEM);
        KeyPair keyPair = new KeyPair(publicKey, privateKey);

        // 创建主密钥RSA的描述信息,创建后不允许修改。主密钥描述信息和主密钥一一对应。
        // 如果所有的Object都使用相同的主密钥,主密钥描述信息可以为空,但后续不支持更换主密钥。
        // 如果主密钥描述信息为空,解密时无法判断文件使用的是哪个主密钥进行加密。
        // 强烈建议为每个主密钥都配置描述信息,由客户端保存主密钥和描述信息之间的对应关系(服务端不保存两者之间的对应关系)。
        Map<String, String> matDesc = new HashMap<String, String>();
        matDesc.put("desc-key", "desc-value");

        // 创建RSA加密材料。
        SimpleRSAEncryptionMaterials encryptionMaterials = new SimpleRSAEncryptionMaterials(keyPair, matDesc);
        // 如果要下载并解密其他RSA密钥加密的文件,请将其他主密钥及其描述信息添加到加密材料中。
        // encryptionMaterials.addKeyPairDescMaterial(<otherKeyPair>, <otherKeyPairMatDesc>);

        // 创建加密客户端。
        OSSEncryptionClient ossEncryptionClient = new OSSEncryptionClientBuilder().
                build(endpoint, credentialsProvider, encryptionMaterials);

        try {
            // 发起下载请求,指定10个任务并发下载,启动断点续传下载。
            DownloadFileRequest downloadFileRequest = new DownloadFileRequest(bucketName, objectName);
            downloadFileRequest.setDownloadFile("<yourDownloadFile>");
            downloadFileRequest.setPartSize(1 * 1024 * 1024);
            downloadFileRequest.setTaskNum(10);
            downloadFileRequest.setEnableCheckpoint(true);
            downloadFileRequest.setCheckpointFile("<yourCheckpointFile>");

            // 开始断点续传下载。
            DownloadFileResult downloadRes = ossEncryptionClient.downloadFile(downloadFileRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossEncryptionClient != null) {
                ossEncryptionClient.shutdown();
            }
        }
    }
}

范围下载

使用主密钥RSA对范围下载的文件进行解密的示例代码如下:

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.crypto.SimpleRSAEncryptionMaterials;
import com.aliyun.oss.model.*;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) throws Throwable {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "examplebucket";
        // 填写Object完整路径,例如exampleobject.txt。Object完整路径中不能包含Bucket名称。
        String objectName = "exampleobject.txt";
        String content = "test-range-get-content-82042795hlnf12s8yhfs976y2nfoshhnsdfsf235bvsmnhtskbcfd!";

        // 填写您的RSA私钥字符串,可以使用OpenSSL工具生成。以下为RSA私钥字符串的示例值。
        final String PRIVATE_PKCS1_PEM =
                "-----BEGIN RSA PRIVATE KEY-----\n" +
                "MIICWwIBAAKBgQCokfiAVXXf5ImFzKDw+XO/UByW6mse2QsIgz3ZwBtMNu59fR5z\n" +
                "ttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC5MFO1PByrE/MNd5AAfSVba93\n" +
                "I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MmR1EKib1Id8hpooY5xaQIDAQAB\n" +
                "AoGAOPUZgkNeEMinrw31U3b2JS5sepG6oDG2CKpPu8OtdZMaAkzEfVTJiVoJpP2Y\n" +
                "nPZiADhFW3e0ZAnak9BPsSsySRaSNmR465cG9tbqpXFKh9Rp/sCPo4Jq2n65yood\n" +
                "JBrnGr6/xhYvNa14sQ6xjjfSgRNBSXD1XXNF4kALwgZyCAECQQDV7t4bTx9FbEs5\n" +
                "36nAxPsPM6aACXaOkv6d9LXI7A0J8Zf42FeBV6RK0q7QG5iNNd1WJHSXIITUizVF\n" +
                "6aX5NnvFAkEAybeXNOwUvYtkgxF4s28s6gn11c5HZw4/a8vZm2tXXK/QfTQrJVXp\n" +
                "VwxmSr0FAajWAlcYN/fGkX1pWA041CKFVQJAG08ozzekeEpAuByTIOaEXgZr5MBQ\n" +
                "gBbHpgZNBl8Lsw9CJSQI15wGfv6yDiLXsH8FyC9TKs+d5Tv4Cvquk0efOQJAd9OC\n" +
                "lCKFs48hdyaiz9yEDsc57PdrvRFepVdj/gpGzD14mVerJbOiOF6aSV19ot27u4on\n" +
                "Td/3aifYs0CveHzFPQJAWb4LCDwqLctfzziG7/S7Z74gyq5qZF4FUElOAZkz123E\n" +
                "yZvADwuz/4aK0od0lX9c4Jp7Mo5vQ4TvdoBnPuGo****\n" +
                "-----END RSA PRIVATE KEY-----";
        // 填写您的RSA公钥字符串,可以使用OpenSSL工具生成。以下为RSA公钥字符串的示例值。
        final String PUBLIC_X509_PEM =
                "-----BEGIN PUBLIC KEY-----\n" +
                "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCokfiAVXXf5ImFzKDw+XO/UByW\n" +
                "6mse2QsIgz3ZwBtMNu59fR5zttSx+8fB7vR4CN3bTztrP9A6bjoN0FFnhlQ3vNJC\n" +
                "5MFO1PByrE/MNd5AAfSVba93I6sx8NSk5MzUCA4NJzAUqYOEWGtGBcom6kEF6MnR\n" +
                "1EKib1Id8hpooY5xaQID****\n" +
                "-----END PUBLIC KEY-----";

        // 创建一个RSA密钥对。
        RSAPrivateKey privateKey = SimpleRSAEncryptionMaterials.getPrivateKeyFromPemPKCS1(PRIVATE_PKCS1_PEM);
        RSAPublicKey publicKey = SimpleRSAEncryptionMaterials.getPublicKeyFromPemX509(PUBLIC_X509_PEM);
        KeyPair keyPair = new KeyPair(publicKey, privateKey);

        // 创建主密钥RSA的描述信息,创建后不允许修改。主密钥描述信息和主密钥一一对应。
        // 如果所有的Object都使用相同的主密钥,主密钥描述信息可以为空,但后续不支持更换主密钥。
        // 如果主密钥描述信息为空,解密时无法判断文件使用的是哪个主密钥进行加密。
        // 强烈建议为每个主密钥都配置描述信息,由客户端保存主密钥和描述信息之间的对应关系(服务端不保存两者之间的对应关系)。
        Map<String, String> matDesc = new HashMap<String, String>();
        matDesc.put("desc-key", "desc-value");

        // 创建RSA加密材料。
        SimpleRSAEncryptionMaterials encryptionMaterials = new SimpleRSAEncryptionMaterials(keyPair, matDesc);
        // 如果要下载并解密其他RSA密钥加密的文件,请将其他主密钥及其描述信息添加到加密材料中。
        // encryptionMaterials.addKeyPairDescMaterial(<otherKeyPair>, <otherKeyPairMatDesc>);

        // 创建加密客户端。
        OSSEncryptionClient ossEncryptionClient = new OSSEncryptionClientBuilder().
                build(endpoint, credentialsProvider, encryptionMaterials);

        try {
            // 加密上传文件。
            ossEncryptionClient.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));

            // 范围下载。
            int start = 17;
            int end = 35;
            GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, objectName);
            getObjectRequest.setRange(start, end);
            OSSObject ossObject = ossEncryptionClient.getObject(getObjectRequest);
            BufferedReader reader = new BufferedReader(new InputStreamReader(ossObject.getObjectContent()));
            StringBuffer buffer = new StringBuffer();
            String line;
            while ((line = reader.readLine()) != null) {
                buffer.append(line);
            }
            reader.close();

            // 查看范围下载结果。
            System.out.println("Range-Get plain text:" + content.substring(start, end + 1));
            System.out.println("Range-Get decrypted text: " + buffer.toString());
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossEncryptionClient != null) {
                ossEncryptionClient.shutdown();
            }
        }
    }
}

相关文档

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