简体   繁体   English

带有KMS的AWS S3客户端加密在最后转移的部分上发出ProtocolViolationException

[英]AWS S3 Client-Side Crypto with KMS in .NET issues ProtocolViolationException on last part transferred

The following code attempts to copy a 17MB test file to an S3 bucket using multi-part transfer, client-side envelope encryption and the Amazon KMS service to handle the data encryption key. 以下代码尝试使用多部分传输,客户端信封加密和Amazon KMS服务将17MB测试文件复制到S3存储桶以处理数据加密密钥。 The multi-part block size is 5MB. 多部分块大小为5MB。

Upon transferring the last (partial) block, and only if the IsLastPart flag is set to true , the call to UploadPart generates a System.Net.ProtocolViolationException indicating: Bytes to be written to the stream exceed the Content-Length bytes size specified. 在传输最后一个(部分)块时,并且IsLastPart标志设置为true ,对UploadPart的调用将生成System.Net.ProtocolViolationException指示: UploadPart Bytes to be written to the stream exceed the Content-Length bytes size specified.

This suggests that the Content-Length html header was not updated to reflect the necessary "pad bytes" added by the encryption engine to the last cipher block for proper alignment. 这表明Content-Length html标头未更新以反映加密引擎添加到最后一个密码块以进行正确对齐所必需的“填充字节”。 As a result, when those final bytes were added, they exceeded the given Content-Length and generated this error. 结果,当添加了最后的字节时,它们超过了给定的Content-Length并产生了这个错误。

If IsLastPart is not set (ie left false ), then the operation succeeds, but upon downloading and decrypting the operation also fails. 如果设置IsLastPart (即保留为false ),则操作成功,但在下载和解密操作时也会失败。

NOTE: The KmsAlgorithm class is not provided by the AWS .NET SDK. 注意:AWS .NET SDK不提供KmsAlgorithm类。 This class comes from another Stack Overflow posting because the .NET version of the AWS SDK does not provide a connector class between KMS and S3 to support envelope encryption as the Java SDK does. 此类来自另一个Stack Overflow 帖子,因为AWS SDK的.NET版本不提供KMS和S3之间的连接器类 ,以支持Java SDK所支持的信封加密。

So what is the proper way to send multi-part uploads to S3 with client-side encryption and KMS managed keys? 那么使用客户端加密和KMS管理密钥将多部分上传发送到S3的正确方法是什么?

    static string bucketName = "*****************************";
    static string keyName = "test.encrypted.bin";
    static string uploadSourcePath = "c:\\temp\\test.bin";
    static long partSize = 5 * 1024 * 1024;
    static String uploadId = "";

    static void Main(string[] args)
    {
        if (checkRequiredFields())
        {
            String cmkId = "************************************";

            // Prepare our KMS client and kmsAlgorithm
            using (AmazonKeyManagementServiceClient kmsClient = new AmazonKeyManagementServiceClient())
            using (KMSAlgorithm kmsAlgo = new KMSAlgorithm(kmsClient, cmkId))
            {
                // Generate the encryption materials object with the algorithm object
                EncryptionMaterials encryptionMaterials = new EncryptionMaterials(kmsAlgo);

                // Now prepare an S3 crypto client
                using (AmazonS3EncryptionClient cryptoClient = new AmazonS3EncryptionClient(encryptionMaterials))
                {
                    // Initiate the multipart upload request specifying the bucket and key values
                    InitiateMultipartUploadResponse initResp = cryptoClient.InitiateMultipartUpload(
                        new InitiateMultipartUploadRequest()
                        {
                            BucketName = bucketName,
                            Key = keyName
                        });

                    uploadId = initResp.UploadId;

                    long fileLength = new FileInfo(uploadSourcePath).Length;
                    long contentLength = fileLength;
                    long bytesRemaining = fileLength;


                    List<PartETag> partETags = new List<PartETag>();
                    int partNumber = 0;

                    while (bytesRemaining > 0)
                    {
                        long transferSize = bytesRemaining > partSize ? partSize : bytesRemaining;
                        long partIndex = fileLength - bytesRemaining;

                        partNumber++;

                        UploadPartResponse resp =
                            cryptoClient.UploadPart(
                                new UploadPartRequest()
                                {
                                    BucketName = bucketName,
                                    Key = keyName,
                                    FilePath = uploadSourcePath,
                                    FilePosition = partIndex,
                                    PartSize = transferSize,
                                    PartNumber = partNumber,
                                    UploadId = uploadId,
                                    IsLastPart = transferSize < AwsS3FileSystemSample1.Program.partSize
                                });

                        partETags.Add( new PartETag( partNumber, resp.ETag ));

                        bytesRemaining -= transferSize;
                    }

                    // Now complete the transfer
                    CompleteMultipartUploadResponse compResp = cryptoClient.CompleteMultipartUpload(
                        new CompleteMultipartUploadRequest()
                        {
                            Key = keyName,
                            BucketName = bucketName,
                            UploadId = initResp.UploadId,
                            PartETags = partETags
                        });
                }
            }
        }

        Console.WriteLine("Press any key to continue...");
        Console.ReadKey();
    }

Apologies for any errors and any help would be greatly appreciated. 对于任何错误和任何帮助的道歉将不胜感激。

After much testing and a little code spelunking on git hub in the AWS SDK for .NET source code the magic secret here is setting the PartSize member of UploadPartRequest to zero (0) when setting the IsLastPart member to true. 许多测试和修改一些代码,在AWS SDK这里git的枢纽洞穴探险的.NET源代码中的魔法秘密之后UploadPartRequestPartSize成员设置为零设置IsLastPart成员为true时(0)。

Exactly why this works is a matter for some debate. 究竟为什么这个工作是一个争论的问题。 Since the data of the last part is typically padded by the crypto-engine to meet a cipher block boundary, the actual Content-Length is unknown until after encryption completes. 由于最后一部分的数据通常由加密引擎填充以满足密码块边界,因此实际的内容长度在加密完成之前是未知的。 Perhaps setting PartSize to zero allows the underlying code to set Content-Length to the padded length of the cipher-text rather than the value give in PartSize . 也许将PartSize设置为零允许底层代码将Content-Length设置为密码文本的填充长度,而不是PartSize中的值。 Why this isn't done automatically when IsLastPart is set to true is a mystery. IsLastPart设置为true时,为什么不自动完成这是一个谜。

In any event, the following summary may help. 无论如何,以下摘要可能会有所帮助。 When using client-side encryption and multi-part uploads, set the PartSize member to zero (0) and IsLastPart to true when uploading the last part of an object's data. 使用客户端加密和多部分上载时,在上载对象数据的最后部分时,将PartSize成员设置为零(0),将IsLastPart设置为true。

This code fragment might help. 此代码片段可能有所帮助。

      while (bytesRemaining > 0)
      {
          long transferSize = bytesRemaining > partSize ? partSize : bytesRemaining;
          long partIndex = fileLength - bytesRemaining;

          bytesRemaining -= transferSize;
          bool isLastPart = bytesRemaining == 0;

          partNumber++;

          UploadPartResponse resp =
              cryptoClient.UploadPart(
                  new UploadPartRequest()
                  {
                      BucketName   = bucketName,
                      Key          = keyName,
                      FilePath     = uploadSourcePath,
                      FilePosition = partIndex,
                      PartSize     = isLastPart ? 0 : transferSize,
                      PartNumber   = partNumber,
                      UploadId     = uploadId,
                      IsLastPart   = isLastPart 
                  });

          partETags.Add( new PartETag( partNumber, resp.ETag ));
      }

I hope this helps someone else out there. 我希望这可以帮助其他人。

Thank you for trying to use to my implementation in the linked post. 感谢您尝试在链接帖子中使用我的实现。

Although I haven't read anything to explicitly support this, I think the Amazon S3 Encryption Client might be incompatible with Multipart Uploads. 虽然我没有阅读任何明确支持这一点,但我认为Amazon S3加密客户端可能与Multipart上传不兼容。 Although it's not definitive, I haven't been able to find a Java example (which the SDK has a KMS implementation for) using both the Amazon S3 Encryption Client on Multipart Uploads. 虽然它不是决定性的,但我还是找不到使用Multipart上传的Amazon S3加密客户端的Java示例(SDK具有KMS实现)。 The reason I have my doubts is that the file is encrypted using chain blocking and if each part of a Multipart Upload is encrypted piecemeal, the chain blocking would be broken and the individual initialization vectors for each piece would be lost for decryption. 我怀疑的原因是文件是使用链阻塞加密的,如果分段上传的每个部分都是零散加密的,链断块将被破坏,每个部分的各个初始化向量将丢失以进行解密。

I guess the only way to know is to test it out with Java first to be sure. 我想唯一可以知道的方法是首先用Java测试它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM