Search code examples
asp.net-mvc-3azure-storageazure-web-rolesencryption-symmetric

Downloading encrypted file from Window Azure storage


I have created a MVC WebRole Window Azure application where i upload encrypted files to Azure blob storage using SymmetricAlgorithm (Rijndael) like this

Controler>Action is

[HttpPost]
public ActionResult UploadImage_post(HttpPostedFileBase fileBase)
{
    if (fileBase.ContentLength > 0)
    {
       // Retrieve a reference to a container 
       Microsoft.WindowsAzure.StorageClient.CloudBlobContainer blobContainer =
              _myBlobStorageService.GetCloudBlobContainer();

       Microsoft.WindowsAzure.StorageClient.CloudBlob blob =
                blobContainer.GetBlobReference(fileBase.FileName);
       using (BlobStream blobStream = blob.OpenWrite())
       {
             string encryptionKey = //somekey;
             byte[] file = new byte[fileBase.ContentLength];
             EncDecAlgo.EncryptBlobFile(file, blobStream, encryptionKey);
       }
    }
}

public void EncryptBlobFile(byte[] file, BlobStream bs, string key)
    {
        PasswordDeriveBytes pdb = new PasswordDeriveBytes(key,
            new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 
        0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});
        Rijndael alg = Rijndael.Create();

        alg.Key = pdb.GetBytes(32);
        alg.IV = pdb.GetBytes(16);

        CryptoStream cs = new CryptoStream(bs,
           alg.CreateEncryptor(), CryptoStreamMode.Write);

        foreach (var data in file)
        {
            cs.WriteByte((byte)data);
        }

        cs.Close();
        bs.Close();
    }

The above File encryption is working fine.

For Downloading code is

 public ActionResult DownloadFile(string filename)
    {
        // Retrieve reference to a previously created container.
        Microsoft.WindowsAzure.StorageClient.CloudBlobContainer blobContainer =
         _myBlobStorageService.GetCloudBlobContainer();

        Microsoft.WindowsAzure.StorageClient.CloudBlob blob =
            blobContainer.GetBlobReference(filename);
        blob.FetchAttributes();
        string encryptionKey = //same key used in encryption;
        using (BlobStream blobStream = blob.OpenRead())
        {
            EncDecAlgo.DecryptBlobFile(blobStream, encryptionKey, filename);
        }
    }

    public static void DecryptBlobFile(BlobStream bs, string key, string filePath)
    {
        try
        {
            PasswordDeriveBytes pdb = new PasswordDeriveBytes(key,
                new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 
        0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

        Rijndael alg = Rijndael.Create();

        alg.Key = pdb.GetBytes(32);
        alg.IV =  pdb.GetBytes(16);

        CryptoStream cs = new CryptoStream(bs,
            alg.CreateDecryptor(), CryptoStreamMode.Read);

        // Decrypt & Download Here
        System.Web.HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment; filename=" + Path.GetFileName(filePath));
        System.Web.HttpContext.Current.Response.ContentType = "application/" + Path.GetExtension(filePath).Replace(".", "");


        int data;
        while ((data = cs.ReadByte()) != -1)
        {
            if (data != 0)
            {
            }
            System.Web.HttpContext.Current.Response.OutputStream.WriteByte((byte)data);
            System.Web.HttpContext.Current.Response.Flush();

        }
        cs.Close();
        bs.Close();
        }
        catch
        {
        }
    }

On downloading get following error

Server cannot set content type after HTTP headers have been sent.

Please suggest some solution.


Solution

  • This should be fairly simple, hope this is enough to get you started:

    public class CloudFileResult : ActionResult
    {
      private string m_FileName;
      private CloudBlobContainer m_Container;
    
      public CloudFileResult(string imageName, CloudBlobContainer container)
      {
        if (string.IsNullOrEmpty(imageName))
        {
          throw new ArgumentNullException("imageName");
        }
        if (container == null)
        {
          throw new ArgumentNullException("container");
        }
    
        m_FileName = imageName;
        m_Container = container;
      }
    
      public override void ExecuteResult(ControllerContext context)
      {
        context.HttpContext.Response.Clear();
        var blockBlob = m_Container.GetBlockBlobReference(m_FileName);
        blockBlob.FetchAttributes();
        context.HttpContext.Response.ContentType = blockBlob.Metadata["ContentType"];
        const string key = "my secret";
        using (var pdb = new Rfc2898DeriveBytes(key, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }))
        {
          using (var alg = RijndaelManaged.Create())
          {
            alg.Key = pdb.GetBytes(32);
            alg.IV = pdb.GetBytes(16);
            using (var stream = new CryptoStream(context.HttpContext.Response.OutputStream, alg.CreateDecryptor(), CryptoStreamMode.Write))
            {
              blockBlob.DownloadToStream(stream);
            }
          }
        }
      }
    }
    
    static void UploadFileToCloud(CloudBlobContainer container, HttpPostedFileBase file)
    {
      const string key = "my secret";
      using (var pdb = new Rfc2898DeriveBytes(key, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }))
      {
        using (var alg = RijndaelManaged.Create())
        {
          alg.Key = pdb.GetBytes(32);
          alg.IV = pdb.GetBytes(16);
    
          var blockBlob = container.GetBlockBlobReference(file.FileName);
          using (var stream = new CryptoStream(file.InputStream, alg.CreateEncryptor(), CryptoStreamMode.Read))
          {
            blockBlob.UploadFromStream(stream);
          }
          blockBlob.Metadata.Add("ContentType", file.ContentType);
          blockBlob.SetMetadata();
        }
      }
    }
    
    static CloudBlobContainer GetContainer()
    {
      string connection = "DefaultEndpointsProtocol=http;AccountName=AzureAccount;AccountKey=AzureAccountKey;";
      var account = CloudStorageAccount.Parse(connection);
      var client = account.CreateCloudBlobClient();
      var container = client.GetContainerReference("container");
      return container;
    }
    

    As for download you can simple use:

    [HttpGet]
    public ActionResult Index(string fileName)
    {
      if (!string.IsNullOrEmpty(fileName))
      {
        return new CloudFileResult(fileName, GetContainer());
      }
      return View();
    }
    

    Pointers:

    • I prefer using managed crypto algorithms
    • I store contenttype of original file in blob metadata (so you know how to serve it)
    • catch {} gives me creeps, at least log the exception somewhere
    • Rather than playing with HttpContext.Response, create custom ActionResult
    • Always dispose IDisposable stuff