【问题标题】:AWS S3 Client-Side Crypto with KMS in .NET issues ProtocolViolationException on last part transferred.NET 中带有 KMS 的 AWS S3 客户端加密在传输的最后部分发出 ProtocolViolationException
【发布时间】:2016-10-19 00:56:46
【问题描述】:

以下代码尝试使用多部分传输、客户端信封加密和 Amazon KMS 服务将 17MB 测试文件复制到 S3 存储桶以处理数据加密密钥。多部分块大小为 5MB。

在传输最后一个(部分)块时,如果IsLastPart 标志设置为true,对UploadPart 的调用会生成一个System.Net.ProtocolViolationException,指示:Bytes to be written to the stream exceed the Content-Length bytes size specified.

这表明 Content-Length html 标头未更新以反映加密引擎添加到最后一个密码块以进行正确对齐的必要“填充字节”。结果,当添加这些最终字节时,它们超出了给定的 Content-Length 并产生了此错误。

如果IsLastPart设置(即留下false),则操作成功,但下载和解密操作也失败。

注意:KmsAlgorithm 类并非由 AWS .NET 开发工具包提供。这个类来自另一个 Stack Overflow posting,因为 .NET 版本的 AWS SDK 没有像 Java SDK 那样在 KMS 和 S3 之间提供 connector class 来支持信封加密。

那么,使用客户端加密和 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();
    }

对于任何错误和任何帮助,我们深表歉意。

【问题讨论】:

    标签: c# .net amazon-web-services


    【解决方案1】:

    在 AWS SDK for .NET 源代码中的 git hub 上经过大量测试和一些代码探索后,这里的神奇秘诀是将 UploadPartRequestPartSize 成员设置为零(0) 将 IsLastPart 成员设置为 true 时。

    究竟为什么这会起作用是一个有争议的问题。由于最后一部分的数据通常由加密引擎填充以满足密码块边界,因此在加密完成之前,实际的 Content-Length 是未知的。也许将 PartSize 设置为零允许底层代码将 Content-Length 设置为密文的填充长度,而不是 PartSize中给出的值>。为什么当 IsLastPart 设置为 true 时不会自动执行此操作是一个谜。

    无论如何,以下摘要可能会有所帮助。使用客户端加密和多部分上传时,在上传对象数据的最后一部分时,将 PartSize 成员设置为零 (0) 并将 IsLastPart 设置为 true。

    此代码片段可能会有所帮助。

          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 ));
          }
    

    我希望这对其他人有帮助。

    【讨论】:

      【解决方案2】:

      感谢您尝试使用我在链接帖子中的实现。

      虽然我没有阅读任何明确支持这一点的内容,但我认为 Amazon S3 加密客户端可能与分段上传不兼容。虽然它不是确定的,但我一直无法找到一个 Java 示例(SDK 有一个 KMS 实现),同时使用 Amazon S3 Encryption Client on Multipart Uploads。我怀疑的原因是文件是使用链阻塞加密的,如果分段上传的每个部分都是零散加密的,那么链阻塞就会被破坏,并且每个片段的单独初始化向量将丢失以进行解密。

      我想唯一知道的方法是先用 Java 测试一下。

      【讨论】:

      • 嗨,Alton,我非常感谢您的实施,它完全按照您的描述工作。做得好。您对块链接的看法是正确的,在 git hub 上深入研究他们的 .NET Transfer Utility 的源代码后,我发现他们在使用加密时明确阻止了“并行”多部分上传。
      • 对该来源的进一步检查表明,在多部分上传的最后一个块中,他们设置了 isLastPart 标志并将 partSize 值设置为零 (0)。当我进行这个小改动时,上传成功,随后的下载也成功。因此,当将 isLastPart 设置为“true”时,似乎“神奇”的方法是在最后一部分的请求中将 partSize 设置为零。究竟为什么这会起作用是一个推测的问题,但到目前为止它似乎解决了这个问题。经过更多测试后,尽管没有支持文档,但我将继续将其写为“答案”。
      猜你喜欢
      • 2016-12-16
      • 1970-01-01
      • 2021-11-27
      • 2021-03-07
      • 1970-01-01
      • 2015-02-16
      • 2018-04-01
      • 2017-09-14
      • 1970-01-01
      相关资源
      最近更新 更多