/// <summary>
        /// Set up the encryption context required for encrypting blobs.
        /// </summary>
        /// <param name="metadata">Reference to blob metadata object that is used to set the encryption materials.</param>
        /// <param name="noPadding">Value indicating if the padding mode should be set or not.</param>
        internal ICryptoTransform CreateAndSetEncryptionContext(IDictionary <string, string> metadata, bool noPadding)
        {
            CommonUtility.AssertNotNull("metadata", metadata);

            // The Key should be set on the policy for encryption. Otherwise, throw an error.
            if (this.Key == null)
            {
                throw new InvalidOperationException(SR.KeyMissingError, null);
            }

#if WINDOWS_DESKTOP && !WINDOWS_PHONE
            using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider())
            {
                if (noPadding)
                {
                    aesProvider.Padding = PaddingMode.None;
                }
#else
            using (AesManaged aesProvider = new AesManaged())
            {
#endif
                BlobEncryptionData encryptionData = new BlobEncryptionData();
                encryptionData.EncryptionAgent = new EncryptionAgent(Constants.EncryptionConstants.EncryptionProtocolV1, EncryptionAlgorithm.AES_CBC_256);

                // Wrap always happens locally, irrespective of local or cloud key. So it is ok to call it synchronously.
                Tuple <byte[], string> wrappedKey = CommonUtility.RunWithoutSynchronizationContext(() => this.Key.WrapKeyAsync(aesProvider.Key, null /* algorithm */, CancellationToken.None).GetAwaiter().GetResult());
                encryptionData.WrappedContentKey   = new WrappedKey(this.Key.Kid, wrappedKey.Item1, wrappedKey.Item2);
                encryptionData.EncryptionMode      = this.EncryptionMode.ToString();
                encryptionData.KeyWrappingMetadata = new Dictionary <string, string>();
                encryptionData.KeyWrappingMetadata[Constants.EncryptionConstants.AgentMetadataKey] = Constants.EncryptionConstants.AgentMetadataValue;
                encryptionData.ContentEncryptionIV = aesProvider.IV;
                metadata[Constants.EncryptionConstants.BlobEncryptionData] = JsonConvert.SerializeObject(encryptionData, Formatting.None);
                return(aesProvider.CreateEncryptor());
            }
        }
        /// <summary>
        /// Return a reference to a <see cref="CryptoStream"/> object, given a user stream. This method is used for decrypting blobs.
        /// </summary>
        /// <param name="userProvidedStream">The output stream provided by the user.</param>
        /// <param name="metadata">A reference to a dictionary containing blob metadata that includes the encryption data.</param>
        /// <param name="transform">The <see cref="ICryptoTransform"/> function for the request.</param>
        /// <param name="requireEncryption">A boolean value to indicate whether the data read from the server should be encrypted.</param>
        /// <param name="iv">The iv to use if pre-buffered. Used only for range reads.</param>
        /// <param name="noPadding">Value indicating if the padding mode should be set or not.</param>
        /// <returns>A reference to a <see cref="CryptoStream"/> that will be written to.</returns>
        internal Stream DecryptBlob(Stream userProvidedStream, IDictionary <string, string> metadata, out ICryptoTransform transform, bool?requireEncryption, byte[] iv = null, bool noPadding = false)
        {
            CommonUtility.AssertNotNull("metadata", metadata);

            string encryptionDataString = null;

            // If encryption policy is set but the encryption metadata is absent, throw an exception.
            bool encryptionMetadataAvailable = metadata.TryGetValue(Constants.EncryptionConstants.BlobEncryptionData, out encryptionDataString);

            if (requireEncryption.HasValue && requireEncryption.Value && !encryptionMetadataAvailable)
            {
                throw new StorageException(SR.EncryptionDataNotPresentError, null)
                      {
                          IsRetryable = false
                      };
            }

            try
            {
                if (encryptionDataString != null)
                {
                    BlobEncryptionData encryptionData = JsonConvert.DeserializeObject <BlobEncryptionData>(encryptionDataString);

                    CommonUtility.AssertNotNull("ContentEncryptionIV", encryptionData.ContentEncryptionIV);
                    CommonUtility.AssertNotNull("EncryptedKey", encryptionData.WrappedContentKey.EncryptedKey);

                    // Throw if the encryption protocol on the blob doesn't match the version that this client library understands
                    // and is able to decrypt.
                    if (encryptionData.EncryptionAgent.Protocol != Constants.EncryptionConstants.EncryptionProtocolV1)
                    {
                        throw new StorageException(SR.EncryptionProtocolVersionInvalid, null)
                              {
                                  IsRetryable = false
                              };
                    }

                    // Throw if neither the key nor the resolver are set.
                    if (this.Key == null && this.KeyResolver == null)
                    {
                        throw new StorageException(SR.KeyAndResolverMissingError, null)
                              {
                                  IsRetryable = false
                              };
                    }

                    byte[] contentEncryptionKey = null;

                    // 1. Invoke the key resolver if specified to get the key. If the resolver is specified but does not have a
                    // mapping for the key id, an error should be thrown. This is important for key rotation scenario.
                    // 2. If resolver is not specified but a key is specified, match the key id on the key and and use it.
                    // Calling UnwrapKeyAsync synchronously is fine because for the storage client scenario, unwrap happens
                    // locally. No service call is made.
                    if (this.KeyResolver != null)
                    {
                        IKey keyEncryptionKey = CommonUtility.RunWithoutSynchronizationContext(() => this.KeyResolver.ResolveKeyAsync(encryptionData.WrappedContentKey.KeyId, CancellationToken.None).GetAwaiter().GetResult());

                        CommonUtility.AssertNotNull("KeyEncryptionKey", keyEncryptionKey);
                        contentEncryptionKey = CommonUtility.RunWithoutSynchronizationContext(() => keyEncryptionKey.UnwrapKeyAsync(encryptionData.WrappedContentKey.EncryptedKey, encryptionData.WrappedContentKey.Algorithm, CancellationToken.None).GetAwaiter().GetResult());
                    }
                    else
                    {
                        if (this.Key.Kid == encryptionData.WrappedContentKey.KeyId)
                        {
                            contentEncryptionKey = CommonUtility.RunWithoutSynchronizationContext(() => this.Key.UnwrapKeyAsync(encryptionData.WrappedContentKey.EncryptedKey, encryptionData.WrappedContentKey.Algorithm, CancellationToken.None).GetAwaiter().GetResult());
                        }
                        else
                        {
                            throw new StorageException(SR.KeyMismatch, null)
                                  {
                                      IsRetryable = false
                                  };
                        }
                    }

                    switch (encryptionData.EncryptionAgent.EncryptionAlgorithm)
                    {
                    case EncryptionAlgorithm.AES_CBC_256:
                        using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider())
                        {
                            aesProvider.IV  = iv != null ? iv : encryptionData.ContentEncryptionIV;
                            aesProvider.Key = contentEncryptionKey;

                            if (noPadding)
                            {
                                aesProvider.Padding = PaddingMode.None;
                            }

                            transform = aesProvider.CreateDecryptor();
                            return(new CryptoStream(userProvidedStream, transform, CryptoStreamMode.Write));
                        }

                    default:
                        throw new StorageException(SR.InvalidEncryptionAlgorithm, null)
                              {
                                  IsRetryable = false
                              };
                    }
                }
                else
                {
                    transform = null;
                    return(userProvidedStream);
                }
            }
            catch (JsonException ex)
            {
                throw new StorageException(SR.EncryptionMetadataError, ex)
                      {
                          IsRetryable = false
                      };
            }
            catch (StorageException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new StorageException(SR.DecryptionLogicError, ex)
                      {
                          IsRetryable = false
                      };
            }
        }