加密和上传 & 解密和下载图像 to/from Xamarin Forms 中的 Firebase 存储

Encrypt and Upload & Decrypt and Download Image to/from Firebase Storage in Xamarin Forms

我正在尝试使用 AES 加密图像并将其上传到 Firebase 存储,然后解密并下载它。我正在使用以下方法:

private async void BtnUpload_Clicked(object sender, EventArgs e)
        {
            var fileStream = FileEncrypt(file.Path);
            var user = await GetUser(localEmail);
            await firebaseHelper.UploadFile(fileStream.Result, Path.GetFileName(file.Path), user.UserID);
            var downloadurl = await firebaseHelper.GetFile(Path.GetFileName(file.Path), user.UserID);
            await firebaseHelper.UploadURL(Path.GetFileName(file.Path), downloadurl.ToString(), user.UserID);
            await DisplayAlert("Success", "Uploaded", "OK");
        }
private async Task<FileStream> FileEncrypt(string inputFile)
        {
            var user = await GetUser(localEmail);
            FileStream fsCrypt = new FileStream(inputFile + ".aes", FileMode.Create);
            //Set Rijndael symmetric encryption algorithm
            RijndaelManaged AES = new RijndaelManaged();
            AES.KeySize = 256;
            AES.BlockSize = 128;
            AES.Padding = PaddingMode.PKCS7;
            var key = new Rfc2898DeriveBytes(user.Key, user.Salt, 50000);
            AES.Key = key.GetBytes(AES.KeySize / 8);
            AES.IV = key.GetBytes(AES.BlockSize / 8);
            AES.Mode = CipherMode.CFB;
            // write salt to the begining of the output file
            fsCrypt.Write(user.Salt, 0, user.Salt.Length);
            CryptoStream cs = new CryptoStream(fsCrypt, AES.CreateEncryptor(), CryptoStreamMode.Write);
            FileStream fsIn = new FileStream(inputFile, FileMode.Open);

            //create a buffer (1mb) so only this amount will allocate in the memory and not the whole file
            //1048576 is 1MB in binary
            byte[] buffer = new byte[1048576];
            int read;
            try
            {
                while ((read = fsIn.Read(buffer, 0, buffer.Length)) > 0)
                {
                    //Application.DoEvents(); // -> for responsive GUI, using Task will be better!
                    cs.Write(buffer, 0, read);
                }
                //fsIn.Close();//causes error
            }
            catch (Exception ex)
            {
                DisplayAlert("Error", "Error: " + ex.Message, "Ok");
            }
            finally
            {
                cs.Close();
                fsCrypt.Close();
            }
            return fsIn;
        }

但是,当我尝试下载加密文件并保存到用户 Android 画廊时,它无法识别文件路径并抛出错误,指出部分文件路径未找到。我已检查以确保文件路径正确。 (如果我在不加密的情况下上传图像,然后使用下载 url 作为我拥有的图像预览框的来源,它会成功显示图像,因此我推断我正在使用的下载 url 是正确)。

有人能帮忙吗?

解密下载方法如下:

 private async void FileDecrypt(string inputFile, string outputFile)
        {
            var user = await GetUser(localEmail);
            FileStream fsCrypt = new FileStream(inputFile, FileMode.Open);
            fsCrypt.Read(user.Salt, 0, user.Salt.Length);

            RijndaelManaged AES = new RijndaelManaged();
            AES.KeySize = 256;
            AES.BlockSize = 128;
            var key = new Rfc2898DeriveBytes(user.Key, user.Salt, 50000);
            AES.Key = key.GetBytes(AES.KeySize / 8);
            AES.IV = key.GetBytes(AES.BlockSize / 8);
            AES.Padding = PaddingMode.PKCS7;
            AES.Mode = CipherMode.CFB;
            CryptoStream cs = new CryptoStream(fsCrypt, AES.CreateDecryptor(), CryptoStreamMode.Read);
            FileStream fsOut = new FileStream(outputFile, FileMode.Create);
            int read;
            byte[] buffer = new byte[1048576];
            try
            {
                while ((read = cs.Read(buffer, 0, buffer.Length)) > 0)
                {
                    //Application.DoEvents();
                    fsOut.Write(buffer, 0, read);
                }
            }
            catch (CryptographicException ex_CryptographicException)
            {
                DisplayAlert("Error", "CryptographicException error: " + ex_CryptographicException.Message, "Ok");
            }
            catch (Exception ex)
            {
                DisplayAlert("Error", "Error: " + ex.Message, "Ok");
            }
            try
            {
                cs.Close();
            }
            catch (Exception ex)
            {
                DisplayAlert("Error", "Error by closing CryptoStream: " + ex.Message, "Ok");
            }
            finally
            {
                fsOut.Close();
                fsCrypt.Close();
            }
        }

private async void BtnDownload_Clicked(object sender, EventArgs e)
        {
            //output path
            string galleryPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures).AbsolutePath;
            string outputPath = Path.Combine(galleryPath, localDownloadUrl.FileName);

            FileDecrypt(localDownloadUrl.Url, outputPath);
            await DisplayAlert("Success", "Image saved to gallery", "OK");
            await App.Current.MainPage.Navigation.PopModalAsync();
        }

所以,在@Jason 的大力帮助下,我终于解决了这个问题(参考评论)。

在我发布的问题中,FileEncrypt 方法正在获取一个文件(您将其路径作为参数传递给该方法),打开它并使用文件流 fsIn 读取它,使用加密流 cs 对其进行加密,然后使用文件流 fsCrypt 将加密文件写入某个位置。为了将其上传到 Firebase 存储,我将 phone 中某个文件夹的路径传递给写出加密文件的文件流。这导致加密文件被保存到该文件夹​​中。然后,我将文件流传输到现在保存在我的 phone 中的这个加密文件,并将其作为参数传递给 UploadFile 方法,然后将其上传到 Firebase 存储。方法如下:

private async void FileEncrypt(string inputFile)
        {
            //this is the path to where the encrypted file will be saved, I have folder named Vault there, I've added the .aes extension so I know it's encrypted.
            string galleryPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures).AbsolutePath;
            string outputPath = Path.Combine(galleryPath + "/Vault", Path.GetFileName(file.Path) + ".aes");
            var user = await GetUser(localEmail);//gets user object (this is my own method)
            FileStream fsCrypt = new FileStream(outputPath, FileMode.Create);//used to create the encrypted file
            //Set Rijndael symmetric encryption algorithm
            RijndaelManaged AES = new RijndaelManaged();
            AES.KeySize = 256;
            AES.BlockSize = 128;
            AES.Padding = PaddingMode.Zeros;
            var key = new Rfc2898DeriveBytes(user.Key, user.Salt, 50000);
            AES.Key = key.GetBytes(AES.KeySize / 8);
            AES.IV = key.GetBytes(AES.BlockSize / 8);
            AES.Mode = CipherMode.CFB;
            // write salt to the beginning of the output file
            fsCrypt.Write(user.Salt, 0, user.Salt.Length);
            CryptoStream cs = new CryptoStream(fsCrypt, AES.CreateEncryptor(), CryptoStreamMode.Write);//used to encrypt the contents of your file 
            FileStream fsIn = new FileStream(inputFile, FileMode.Open);//used to read your file

            //create a buffer (1mb) so only this amount will allocate in the memory and not the whole file
            //1048576 is 1MB in binary
            byte[] buffer = new byte[1048576];
            int read;
            try
            {
                while ((read = fsIn.Read(buffer, 0, buffer.Length)) > 0)
                {
                    cs.Write(buffer, 0, read);
                }
                fsIn.Close();
            }
            catch (Exception ex)
            {
                await DisplayAlert("Error", "Error: " + ex.Message, "Ok");
            }
            finally
            {
                cs.Close();
                fsCrypt.Close();
            }
        }

 private async void BtnUpload_Clicked(object sender, EventArgs e)
        {
            //same path as inside the encrypt method
            string galleryPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures).AbsolutePath;
            string outputPath = Path.Combine(galleryPath + "/Vault", Path.GetFileName(file.Path) + ".aes");


            FileEncrypt(file.Path);//file is declared at the top and set in my PickImage method. This will be just be the path to the file you want to encrypt

            var user = await GetUser(localEmail);//own method, gets user object which I need to use 

            FileStream filestream = System.IO.File.OpenRead(outputPath);//get filestream to new encrypted file that we create in File Encrypt method

            await firebaseHelper.UploadFile(filestream, Path.GetFileName(file.Path), user.UserID);//upload the file
            var downloadurl = await firebaseHelper.GetFile(Path.GetFileName(file.Path), user.UserID);//get the download url link for this file we just uploaded, own method
            await firebaseHelper.UploadURL(Path.GetFileName(file.Path), downloadurl.ToString(), user.UserID);//own method, I do this so that I can retrieve the download url, along with user id and original file name easily later on
            await DisplayAlert("Success", "Uploaded", "OK");
            imgChoosed.Source = "";//this is just an Image preview box
        }
 public async Task<string> UploadFile(FileStream fileStream, string fileName, Guid userid)
        {
            try
            {
                var fileAlreadyExists = await GetFile(fileName, userid);
                if (fileAlreadyExists == null)
                {
                    try
                    {
                        //this is main upload to firebase storage bit
                        var imageurl = await firebaseStorage
                        .Child("Media")
                        .Child(fileName + userid)
                        .PutAsync(fileStream);
                        return imageurl;
                    }
                    catch (Exception e)
                    {
                        Debug.WriteLine($"Error:{e}");
                        return null;
                    }
                }
                else
                {
                    //below code never gets used, firebase already recognises it is duplicate and appends a number to the filename, prevents duplicates
                    try
                    {
                        var imageurl = await firebaseStorage
                        .Child("Media")
                        .Child(fileName + Guid.NewGuid() + userid)
                        .PutAsync(fileStream);
                        return imageurl;
                    }
                    catch (Exception e)
                    {
                        Debug.WriteLine($"Error:{e}");
                        return null;
                    }
                }
            }
            catch (Exception e)
            {
                Debug.WriteLine($"Error:{e}");
                return null;
            } 
        }

至于解密,大同小异。 Jason 建议我必须这样做的方式。我们从 Firebase Storage 下载加密文件到我们的设备,然后在 FileDecrypt 方法中传递这个加密文件的路径。这是一种 long/odd 的方式,但由于 Xamarin Forms 的限制,我想不出更好的选择。方法如下:

private async void FileDecrypt(string inputFile, string outputFile)
        {
            var user = await GetUser(localEmail);//own method
            FileStream fsCrypt = new FileStream(inputFile, FileMode.Open);//open and read encrypted file
            fsCrypt.Read(user.Salt, 0, user.Salt.Length);//read salt as this the first thing we wrote when encrypting

            RijndaelManaged AES = new RijndaelManaged();
            AES.KeySize = 256;
            AES.BlockSize = 128;
            AES.Padding = PaddingMode.Zeros;
            var key = new Rfc2898DeriveBytes(user.Key, user.Salt, 50000);
            AES.Key = key.GetBytes(AES.KeySize / 8);
            AES.IV = key.GetBytes(AES.BlockSize / 8);
            AES.Mode = CipherMode.CFB;
            CryptoStream cs = new CryptoStream(fsCrypt, AES.CreateDecryptor(), CryptoStreamMode.Read);//used to decrypt content of the encrypted file
            FileStream fsOut = new FileStream(outputFile, FileMode.Create);//used to create your decrypted file to the path you pass in as output file
            int read;
            byte[] buffer = new byte[1048576];
            try
            {
                while ((read = cs.Read(buffer, 0, buffer.Length)) > 0)
                {
                    fsOut.Write(buffer, 0, read);
                }
            }
            catch (CryptographicException ex_CryptographicException)
            {
                await DisplayAlert("Error", "CryptographicException error: " + ex_CryptographicException.Message, "Ok");
            }
            catch (Exception ex)
            {
                await DisplayAlert("Error", "Error: " + ex.Message, "Ok");
            }
            try
            {
                cs.Close();
            }
            catch (Exception ex)
            {
                await DisplayAlert("Error", "Error by closing CryptoStream: " + ex.Message, "Ok");
            }
            finally
            {
                fsOut.Close();
                fsCrypt.Close();
            }
        }

private async void BtnDownload_Clicked(object sender, EventArgs e)
        {
            //output path for encrypted file
            string galleryPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures).AbsolutePath;
            //remove file extension
            string filename = localDownloadUrl.FileName;
            int index = filename.LastIndexOf(".");
            if (index > 0)
            {
                filename = filename.Substring(0, index);
            }
            string outputPath = Path.Combine(galleryPath + "/Vault/", filename);
            //download encrypted file locally first
            using (var client = new WebClient())
            {
                client.DownloadFile(localDownloadUrl.Url, outputPath);
            }

            //output path for decrypted file
            string galleryPath1 = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures).AbsolutePath;
            string outputPath1 = Path.Combine(galleryPath + "/Vault/", localDownloadUrl.FileName);

            FileDecrypt(outputPath, outputPath1);

            //delete extra file we downloaded
            //File.Delete(outputPath);

            //to show pics in gallery
            MediaScannerConnection.ScanFile(Android.App.Application.Context, new string[] { outputPath1 }, new string[] { "image / jpeg" }, null);

            await DisplayAlert("Success", "Image saved to gallery", "OK");

            //display image in image preview: TODO: change the way it works
            imageView.Source = ImageSource.FromFile(outputPath1);//this is just an image preview box I have, i just set its source to the newly decrypted file so that it displays it


            //await App.Current.MainPage.Navigation.PopModalAsync();
        }