PDA

View Full Version : مشکل در رمزگذاری و رمزگشایی فایل های بزرگ



daniyaltjm
پنج شنبه 30 مرداد 1399, 16:34 عصر
سلام با استفاده از این کد فایل ها رو اینکریپت میکنم ولی فایل اگه از یه حدی بزرگتر باشه خطا میده و Out of Memoryمیده روش دیگه ایی هست؟!:متفکر:


private void button3_Click_1(object sender, EventArgs e)
{
EncryptFile();
MessageBox.Show("رمز گذاری انجام شد!","رمزگذاری",MessageBoxButtons.OK,MessageBoxIcon.Information);
}




private void button4_Click(object sender, EventArgs e)
{
try
{
DecryptFile();
MessageBox.Show("رمز گشایی شد!","رمزگشایی", MessageBoxButtons.OK,MessageBoxIcon.Information);
}
catch
{
MessageBox.Show("رمز نادرست!", "رمزگشایی",MessageBoxButtons.OK,MessageBoxIcon.Error);
}


}





public void EncryptFile()
{
openFileDialog1.ShowDialog();
string file = openFileDialog1.FileName;
string password = textBox2.Text;


byte[] bytesToBeEncrypted = File.ReadAllBytes(file);
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);


// Hash the password with SHA256
passwordBytes = SHA256.Create().ComputeHash(passwordBytes);


byte[] bytesEncrypted = AES_Encrypt(bytesToBeEncrypted, passwordBytes);


string fileEncrypted = openFileDialog1.FileName;


File.WriteAllBytes(fileEncrypted, bytesEncrypted);
}




public void DecryptFile()
{
saveFileDialog1.ShowDialog();
string fileEncrypted = saveFileDialog1.FileName;
string password = textBox2.Text;


byte[] bytesToBeDecrypted = File.ReadAllBytes(fileEncrypted);
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
passwordBytes = SHA256.Create().ComputeHash(passwordBytes);


byte[] bytesDecrypted = AES_Decrypt(bytesToBeDecrypted, passwordBytes);


string file = saveFileDialog1.FileName;
File.WriteAllBytes(file, bytesDecrypted);
}



public byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
byte[] encryptedBytes = null;


// Set your salt here, change it to meet your flavor:
// The salt bytes must be at least 8 bytes.
byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };


using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 128;


var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes,1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);


AES.Mode = CipherMode.CBC;


using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cs.Close();
encryptedBytes = ms.ToArray();
}
}
return encryptedBytes;
}


}




public byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
{


byte[] decryptedBytes = null;


// Set your salt here, change it to meet your flavor:
// The salt bytes must be at least 8 bytes.
byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };


using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 128;


var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);


AES.Mode = CipherMode.CBC;


using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
cs.Close();
decryptedBytes = ms.ToArray();
}
}
return decryptedBytes;
}





}

daniyaltjm
یک شنبه 02 شهریور 1399, 14:51 عصر
دوستان فکر کنم مشکل از کلاس MemoryStream باشه با اینکه رم بنده 6 گیگابایت هست بازم پر میشه ... ایده ای ندارین؟! مثلا مستقیم از هارد استفاده بشه؟ :متفکر:

مهدی کرامتی
یک شنبه 02 شهریور 1399, 15:35 عصر
این ها رو امتحان کن. ویژگی این کدها اینه که برای کار با فایل های بزرگ طراحی شده اند.

فایل مبدا/مقصد هنگام رمزنگاری و رمزگشایی با استفاده از یک بافر 1 مگابایتی و بصورت تکه تکه پردازش میشه، بنابراین با رم کم هم مشکلی پیش نمیاد.

برای رمزنگاری:
private void AES_Encrypt(string inputFile, string password)
{
//http://stackoverflow.com/questions/27645527/aes-encryption-on-large-files

//generate random salt
byte[] salt = GenerateRandomSalt();

//create output file name
FileStream fsCrypt = new FileStream(inputFile + ".aes", FileMode.Create);

//convert password string to byte arrray
byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(password);

//Set Rijndael symmetric encryption algorithm
RijndaelManaged AES = new RijndaelManaged();
AES.KeySize = 256;
AES.BlockSize = 128;
AES.Padding = PaddingMode.PKCS7;

//http://stackoverflow.com/questions/2659214/why-do-i-need-to-use-the-rfc2898derivebytes-class-in-net-instead-of-directly
//"What it does is repeatedly hash the user password along with the salt." High iteration counts.
var key = new Rfc2898DeriveBytes(passwordBytes, salt, 50000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);

//Cipher modes: http://security.stackexchange.com/questions/52665/which-is-the-best-cipher-mode-and-padding-mode-for-aes-encryption
AES.Mode = CipherMode.CFB;

//write salt to the begining of the output file, so in this case can be random every time
fsCrypt.Write(salt, 0, 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
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);
}

//close up
fsIn.Close();

}
catch (Exception ex)
{
Debug.WriteLine("Error: " + ex.Message);
}
finally
{
cs.Close();
fsCrypt.Close();
}
}
برای رمزگشایی:
private void AES_Decrypt(string inputFile, string password)
{
//todo:
// - create error message on wrong password
// - on cancel: close and delete file
// - on wrong password: close and delete file!
// - create a better filen name
// - could be check md5 hash on the files but it make this slow

byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(password);
byte[] salt = new byte[32];

FileStream fsCrypt = new FileStream(inputFile, FileMode.Open);
fsCrypt.Read(salt, 0, salt.Length);

RijndaelManaged AES = new RijndaelManaged();
AES.KeySize = 256;
AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, 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(inputFile + ".decrypted", 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 (System.Security.Cryptography.CryptographicExcepti on ex_CryptographicException)
{
Debug.WriteLine("CryptographicException error: " + ex_CryptographicException.Message);
}
catch (Exception ex)
{
Debug.WriteLine("Error: " + ex.Message);
}

try
{
cs.Close();
}
catch (Exception ex)
{
Debug.WriteLine("Error by closing CryptoStream: " + ex.Message);
}
finally
{
fsOut.Close();
fsCrypt.Close();
}
}
و برای تولید Salt تصادفی:
public static byte[] GenerateRandomSalt()
{
//Source: http://www.dotnetperls.com/rngcryptoserviceprovider
byte[] data = new byte[32];
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
// Ten iterations.
for (int i = 0; i < 10; i++)
{
// Fill buffer.
rng.GetBytes(data);
}
}
return data;
}

تست کن، و نتیجه رو اعلام کن.

daniyaltjm
یک شنبه 02 شهریور 1399, 18:07 عصر
تست کن، و نتیجه رو اعلام کن.




ممنون استاد جواب داد... یکم کد رو تغییر دادم اون قسمت انکریپت میومد پسوند فایل رو "aes." قرار می داد که درسته ولی موقع دیکریپت میومد این پسوند رو اضافه میکرد "decrypted."که حذفش کردم که خود پسوند فایل اصلی باشه...

FileStream fsOut = new FileStream(inputFile.Replace(".aes",""), FileMode.Create);




یک سوال اینکه تغییر سایز بافر توی سرعت اثری داره؟ چون من به 100 مگابایت افزایش دادم فرقی نکرد!

یه چیز دیگه اینکه این کد هم ارور میداد که NameSpace رو نمیشناشه کامنتش کردم و مشکل برطرف شد اینم یه توضیح بدین لطفا..


catch (System.Security.Cryptography.CryptographicExcepti on ex_CryptographicException)
{
Debug.WriteLine("CryptographicException error: " + ex_CryptographicException.Message);
}


سوال دیگه اینکه اگه بخوایم چند فایل رو به ترتیب اینکریپت و دیکریپت کنه (مثل باج افزارها) چه تغییراتی باید توی متد ها بدیم (آرایه ایی از نام فایل ها رو باید بگیرن؟)

سپاس:تشویق:...

اینم فایل نمونه کدهای بالا:

مهدی کرامتی
یک شنبه 02 شهریور 1399, 18:26 عصر
یک سوال اینکه تغییر سایز بافر توی سرعت اثری داره؟ چون من به 100 مگابایت افزایش دادم فرقی نکرد!
نه. فرق چندانی نداره. فقط تعداد دفعات گردش حلقه رو کاهش میده. ولی اگر سایز بافر خیلی بزرگتر از اکنون باشه اون وقت افت بازدهی ایجاد میشه.


اینم یه توضیح بدین لطفا
واضح است. میگه اگر هنگام دیکریپت مشکلی پیش اومد، مثلا موقعی که فایل بصورت غیرمجاز دستکاری شده باشه، روی دیسک بد سکتور افتاده باشه که باعث خرابی داده شده باشه یا امثال این موارد، خطا هندل بشه.


سوال دیگه اینکه اگه بخوایم چند فایل رو به ترتیب اینکریپت و دیکریپت کنه (مثل باج افزارها) چه تغییراتی باید توی متد ها بدیم (آرایه ایی از نام فایل ها رو باید بگیرن؟)
چنان تغییری بهتره در این متدهای رمزنگاری/رمزگشایی اعمال نشود، بلکه در کدهای فراخوان انجام شود.


اینم فایل نمونه کدهای بالا:
این نمونه رو بعنوان ضمیمه در همین تاپیک اضافه کنید. تجربه نشان داده لینک های مثل سایتی که برای آپلود استفاده کرده اید بعد مدتی از بین میره، و زمانی که افراد چند ماه بعد بخواهند نمونه کد رو بدست بیاورند دیگه در دسترس شون نخواهد بود.

daniyaltjm
یک شنبه 02 شهریور 1399, 18:38 عصر
این نمونه رو بعنوان ضمیمه در همین تاپیک اضافه کنید. تجربه نشان داده لینک های مثل سایتی که برای آپلود استفاده کرده اید بعد مدتی از بین میره، و زمانی که افراد چند ماه بعد بخواهند نمونه کد رو بدست بیاورند دیگه در دسترس شون نخواهد بود.




نه منظورم اینه که به این نیم اسپیس ارور میگیره CryptographicExcepti یوزینگ دیگه ایی باید اضافه کنم!؟



این نمونه رو بعنوان ضمیمه در همین تاپیک اضافه کنید. تجربه نشان داده لینک های مثل سایتی که برای آپلود استفاده کرده اید بعد مدتی از بین میره، و زمانی که افراد چند ماه بعد بخواهند نمونه کد رو بدست بیاورند دیگه در دسترس شون نخواهد بود.





چشم.

یک چیز دیگه وقتی پسورد رو اشتباه میزنم یک زمان طول میکشه تا محاسبه کنه و ارور رو بده این طبیعیه؟ یا قابل درست شدن هست ؟

مهدی کرامتی
یک شنبه 02 شهریور 1399, 18:51 عصر
طبیعی است. چون پسورد جایی سیو نمیشه و بر اساس ارزیابی مشخص میشه که صحیح است یا نه.

daniyaltjm
یک شنبه 02 شهریور 1399, 18:56 عصر
طبیعی است. چون پسورد جایی سیو نمیشه و بر اساس ارزیابی مشخص میشه که صحیح است یا نه.

بله درسته باید ارزیابی کنه ولی توی یک نمونه که توی اینترنت دیدم که پروژه هم با WPF نوشته شده وقتی پسورد اشتباه رو میزدم بی درنگ ارور میداد که پسورد درست نیست !! عجیب بود .. بهر حال ممنون :تشویق::تشویق::تشویق:

پرستو پارسایی
دوشنبه 03 شهریور 1399, 11:52 صبح
با سلام امکان داره فایل ضمیمه رو با ورژن پاینتر (vs2010) بگذارید . سپاسگزارم

مهدی کرامتی
دوشنبه 03 شهریور 1399, 11:55 صبح
بله درسته باید ارزیابی کنه ولی توی یک نمونه که توی اینترنت دیدم که پروژه هم با WPF نوشته شده وقتی پسورد اشتباه رو میزدم بی درنگ ارور میداد که پسورد درست نیست !! عجیب بود .. بهر حال ممنون :تشویق::تشویق::تشویق:
اگر می خواهید سریع جواب بده یک متد تست پسورد با یک بافر یک مگابایتی در نظر بگیرید.

daniyaltjm
دوشنبه 03 شهریور 1399, 21:49 عصر
اگر می خواهید سریع جواب بده یک متد تست پسورد با یک بافر یک مگابایتی در نظر بگیرید.


استاد تابعع چک کردن پسورد رو اینطوری ساختم بنظرتون درسته یااز این بهتر هم میشه؟


private void AES_ChekPass(string inputFile, string password)
{
//todo:
// - create error message on wrong password

byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(password);
byte[] salt = new byte[32];


FileStream fsCrypt = new FileStream(inputFile, FileMode.Open);
fsCrypt.Read(salt, 0, salt.Length);


RijndaelManaged AES = new RijndaelManaged();
AES.KeySize = 256;
AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, 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);

int read;
byte[] buffer = new byte[1048576];
MemoryStream ms = new MemoryStream();


try
{

while ((read = cs.Read(buffer, 0, buffer.Length)) > 0)
{
Application.DoEvents();
ms.Write(buffer, 0, read);
}


}


catch (Exception ex)
{
MessageBox.Show("Error:" + ex.Message);

}


try
{
cs.Close();
}
catch(Exception)
{
MessageBox.Show("پسورد نادرست است");
buffer = null;
fsCrypt.Close();
}
finally
{
if(buffer!=null)
MessageBox.Show("پسورد درست است");


}




}

مهدی کرامتی
دوشنبه 03 شهریور 1399, 22:57 عصر
اگر سرعت اجرایش خوبه، حله.
اگر نه، سایز بافر رو یک دهم کنید.

daniyaltjm
سه شنبه 04 شهریور 1399, 11:19 صبح
اگر سرعت اجرایش خوبه، حله.
اگر نه، سایز بافر رو یک دهم کنید.

سرعت اجرا کند هست مخصوصا فایل های حجیم در واقع این از اول تا آخر فایل رو بازم چک میکنه !! بافر هم کم کردم فایده نداره...

" نمیشه خود پسورد رو هش کرد و اول فایل قرار داد و با اون چک کرد؟ یا اینطوری امنیت میاد پایین؟ ":متفکر:

مهدی کرامتی
سه شنبه 04 شهریور 1399, 11:43 صبح
سرعت اجرا کند هست مخصوصا فایل های حجیم در واقع این از اول تا آخر فایل رو بازم چک میکنه !! بافر هم کم کردم فایده نداره...
به خط 35 متد چک پسوردتون دقت کنید، حلقه ای که اونجا هست بهرحال داره تا آخر فایل رو میخواند. برای تسریع اجرا، تو اون حلقه یک شمارنده هم اضافه کنید، پس از خوانده شدن اولین بلوک تست CRC رو انجام بدید. این طوری اجرا تسریع میشه.


نمیشه خود پسورد رو هش کرد و اول فایل قرار داد و با اون چک کرد؟ یا اینطوری امنیت میاد پایین؟
یک قانونی هست که میگه هر ایده ای به ذهن شما رسیده، ممکنه به ذهن 5 نفر دیگه در دنیا، در همون لحظه برسه.
تو فیلم ها بعضا نشون میده طرف در خونه اش رو قفل می کنه، کلیدش رو میگذاره زیر گلدان دم در ورودی. دزدها هم همیشه اول اونجا رو چک می کنند، پس بالطبع، بله، امنیت بسیار میاد پایین.

daniyaltjm
سه شنبه 04 شهریور 1399, 12:06 عصر
به خط 35 متد چک پسوردتون دقت کنید، حلقه ای که اونجا هست بهرحال داره تا آخر فایل رو میخواند. برای تسریع اجرا، تو اون حلقه یک شمارنده هم اضافه کنید، پس از خوانده شدن اولین بلوک تست CRC رو انجام بدید. این طوری اجرا تسریع میشه.


یعنی فقط با یک بلوک چک کنه؟ اگه میشه کد تست CRC رو بنویسین ممنون.

daniyaltjm
سه شنبه 04 شهریور 1399, 17:58 عصر
سرعتش خیلی بهتر شده نسبت به قبل:



private void AES_ChekPass(string inputFile, string password)
{
//todo:
// - create error message on wrong password

byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(password);
byte[] salt = new byte[32];


FileStream fsCrypt = new FileStream(inputFile, FileMode.Open);
fsCrypt.Read(salt, 0, salt.Length);


RijndaelManaged AES = new RijndaelManaged();
AES.KeySize = 256;
AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, 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);

byte[] buffer = new byte[104857];
int c = 0;
try
{


while ((cs.Read(buffer,0, buffer.Length)) > 0)
{

Application.DoEvents();
if (c == 104857)
break;
c++;


}

}


catch (Exception ex)
{

}


try
{
cs.Close();
}
catch(Exception)
{
MessageBox.Show("پسورد نادرست است");
buffer = null;
fsCrypt.Close();
}
finally
{
if(buffer!=null)
MessageBox.Show("پسورد درست است");


}




}

مهدی کرامتی
سه شنبه 04 شهریور 1399, 18:03 عصر
این روش هم بد نیست، اما شما دارید اجازه می دهید حلقه 104 هزار بار اجرا بشه. در صورتی که با همون اجرا شدن بار اول هم میشه تشخیص داد پسورد صحیح است یا نه.

daniyaltjm
سه شنبه 04 شهریور 1399, 18:10 عصر
این روش هم بد نیست، اما شما دارید اجازه می دهید حلقه 104 هزار بار اجرا بشه. در صورتی که با همون اجرا شدن بار اول هم میشه تشخیص داد پسورد صحیح است یا نه.

متاسفانه تو حالتهای دیگه که کمتر از طول بافر هست نمیدونم چرا خطای محاسباتی میده و کلا پسورد درست رو نادرست نشون میده چندین حالت رو تست کردم حتی بافر رو کمتر گرفتم ولی توی این حالت با فایل های زیادی تست کردم و جواب داد در صورتی که تو حالت های دیگه ممکن بود برای یک فایل درست کار کنه ولی برای فایل دیگه نه ولی اینجوری خوب کار میکنه ... :متعجب:

مهدی کرامتی
سه شنبه 04 شهریور 1399, 18:20 عصر
اگر سرعت پروسه از نظر شما قابل قبول است قضیه حل می باشد.