Итак, мне удалось наконец решить эту проблему с помощью @Jason (см. Комментарии).
В моем вопросе, который я написал, метод FileEncrypt
принимает файл (чей путь, который вы передаете в метод в качестве параметра), открывая его и читая его с помощью потока файлов fsIn
, шифруя его с помощью крипто-потока cs
, а затем записывая зашифрованный файл в какое-то место с использованием потока файлов fsCrypt. Чтобы загрузить его в Firebase Storage, я передал путь к какой-то папке в моем телефоне потоку файлов, который записывает зашифрованный файл. Это приводит к сохранению зашифрованного файла в эту папку. Затем я передаю поток файлов в этот зашифрованный файл, который теперь сохраняется в моем телефоне, и передаю его методу UploadFile
в качестве параметра, который затем загружает его в Firebase Storage. Методы ниже:
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;
}
}
Что касается расшифровки, то она очень похожа. То, как я должен был это сделать, было предложено Джейсоном. Мы загружаем зашифрованный файл из Firebase Storage на наше устройство, затем передаем путь к этому зашифрованному файлу методом FileDecrypt
. Это немного длинный / странный способ сделать это, но я не мог придумать лучшей альтернативы из-за ограничений в формах Xamarin. Методы ниже:
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();
}