/** @deprecated no longer used and will be removed in the future */
  public static ByteRangeCapturingInputStream getEncryptedInputStream(
      UploadPartRequest request, CipherFactory cipherFactory) {
    try {
      InputStream originalInputStream = request.getInputStream();
      if (request.getFile() != null) {
        originalInputStream =
            new InputSubstream(
                new RepeatableFileInputStream(request.getFile()),

      originalInputStream = new RepeatableCipherInputStream(originalInputStream, cipherFactory);

      if (request.isLastPart() == false) {
        // We want to prevent the final padding from being sent on the stream...
        originalInputStream =
            new InputSubstream(originalInputStream, 0, request.getPartSize(), false);

      long partSize = request.getPartSize();
      int cipherBlockSize = cipherFactory.createCipher().getBlockSize();
      return new ByteRangeCapturingInputStream(
          originalInputStream, partSize - cipherBlockSize, partSize);
    } catch (Exception e) {
      throw new AmazonClientException("Unable to create cipher input stream: " + e.getMessage(), e);
  private PartETag uploadChunk(
      ByteBuffer bytes, InitiateMultipartUploadResult initResponse, int index) throws Exception {
    byte[] md5 = S3Utils.md5(bytes);

    UploadPartRequest request = new UploadPartRequest();
    request.setInputStream(new ByteBufferInputStream(bytes));

    UploadPartResult response = s3Client.uploadPart(request);
    PartETag partETag = response.getPartETag();
    if (!response.getPartETag().getETag().equals(S3Utils.toHex(md5))) {
      throw new Exception("Unable to match MD5 for part " + index);

    return partETag;
 /** @deprecated no longer used and will be removed in the future */
 public static long calculateCryptoContentLength(
     Cipher symmetricCipher, UploadPartRequest request) {
   long plaintextLength;
   if (request.getFile() != null) {
     if (request.getPartSize() > 0) plaintextLength = request.getPartSize();
     else plaintextLength = request.getFile().length();
   } else if (request.getInputStream() != null) {
     plaintextLength = request.getPartSize();
   } else {
     return -1;
   long cipherBlockSize = symmetricCipher.getBlockSize();
   long offset = cipherBlockSize - (plaintextLength % cipherBlockSize);
   return plaintextLength + offset;
   * {@inheritDoc}
   * <p><b>NOTE:</b> Because the encryption process requires context from block N-1 in order to
   * encrypt block N, parts uploaded with the AmazonS3EncryptionClient (as opposed to the normal
   * AmazonS3Client) must be uploaded serially, and in order. Otherwise, the previous encryption
   * context isn't available to use when encrypting the current part.
  public UploadPartResult uploadPart(UploadPartRequest uploadPartRequest)
      throws AmazonClientException, AmazonServiceException {

    appendUserAgent(uploadPartRequest, USER_AGENT);

    boolean isLastPart = uploadPartRequest.isLastPart();
    String uploadId = uploadPartRequest.getUploadId();

    boolean partSizeMultipleOfCipherBlockSize =
        uploadPartRequest.getPartSize() % JceEncryptionConstants.SYMMETRIC_CIPHER_BLOCK_SIZE == 0;
    if (!isLastPart && !partSizeMultipleOfCipherBlockSize) {
      throw new AmazonClientException(
          "Invalid part size: part sizes for encrypted multipart uploads must be multiples "
              + "of the cipher block size ("
              + JceEncryptionConstants.SYMMETRIC_CIPHER_BLOCK_SIZE
              + ") with the exception of the last part.  "
              + "Otherwise encryption adds extra padding that will corrupt the final object.");

    // Generate the envelope symmetric key and initialize a cipher to encrypt the object's data
    EncryptedUploadContext encryptedUploadContext = currentMultipartUploadSecretKeys.get(uploadId);
    if (encryptedUploadContext == null)
      throw new AmazonClientException(
          "No client-side information available on upload ID " + uploadId);

    SecretKey envelopeSymmetricKey = encryptedUploadContext.getEnvelopeEncryptionKey();
    byte[] iv = encryptedUploadContext.getNextInitializationVector();
    CipherFactory cipherFactory =
        new CipherFactory(
            envelopeSymmetricKey, Cipher.ENCRYPT_MODE, iv, this.cryptoConfig.getCryptoProvider());

    // Create encrypted input stream
    InputStream encryptedInputStream =
        EncryptionUtils.getEncryptedInputStream(uploadPartRequest, cipherFactory);

    // The last part of the multipart upload will contain extra padding from the encryption process,
    // which
    // changes the
    if (uploadPartRequest.isLastPart()) {
      // We only change the size of the last part
      long cryptoContentLength =
              cipherFactory.createCipher(), uploadPartRequest);
      if (cryptoContentLength > 0) uploadPartRequest.setPartSize(cryptoContentLength);

      if (encryptedUploadContext.hasFinalPartBeenSeen()) {
        throw new AmazonClientException(
            "This part was specified as the last part in a multipart upload, but a previous part was already marked as the last part.  "
                + "Only the last part of the upload should be marked as the last part, otherwise it will cause the encrypted data to be corrupted.");


    // Treat all encryption requests as input stream upload requests, not as file upload requests.

    UploadPartResult result = super.uploadPart(uploadPartRequest);

    if (encryptedInputStream instanceof ByteRangeCapturingInputStream) {
      ByteRangeCapturingInputStream bris = (ByteRangeCapturingInputStream) encryptedInputStream;
    } else {
      throw new AmazonClientException("Unable to access last block of encrypted data");

    return result;