Bir Klasörde Bulunan Tüm Dosyaların Veya Spesifik Bir Dosyanın Şifrelenmesi İle İki Klasör İçindeki Dosyaların Karşılaştırılması
Selamlar,
Bu makalede, biraz olsun güvenlik konuşacağız. Bazı dosyaların herkese açılmaması gerekir. Özellikle kişisel bilgilerin kayıtlı olduğu mail veya muhasebe gibi kayıtların şifrelenmesi, büyük önem arz etmektedir.
Bu makalede, dosya veya klasör içindeki bazı dosyaların encryption işlemini tabi tutacağız. Ayrıca ilgili klasörün eşleniğini alarak, iki folder’ı match işlemine tabi tutacağız. Bu işlem sonunda, encrypt olmayan dosyaların listesini çıkaracağız. Son olarak encrypt edilen dosyalara bir flag atayarak işaretleyeceğiz. Böylece encrypt dosyaları kolayca ayırt edip, bunun faydaları üzerinde konuşacağız.
Bir File’ın Şifrelenmesi: Aşağıda görüldüğü gibi path’i verilen bir dosya aşağıdaki gibi şifrelenmektedir. “ Öncelikle gerekmedikçe ‘Encoding’ kullanmadan, ‘byte[]’ ile çalışmanızı tavsiye ederim.”
- “ReadOnlySpan<byte> clearBytes = File.ReadAllBytes(filePath).AsSpan()”: Burada şifrelenecek dosya okunup, “AsSpan()” extension’ı ile “ReadOnlySpan<byte>” olarak bir değişkene atanmıştır. Googlelayıp bulacağınız örnekler :) , genelde ilgili dosyayı byte[] olarak bir değişkene atamaktadır. Spanlar’ın, memory management’ındaki performansına, şu makaleden ulaşabilirsiniz : https://www.borakasmer.com/net-coreda-span/
- Encryption Key: Go’da yazdığım “https://github.com/borakasmer/pwd-cli” reposu ile aşağıdaki gibi oluşturulmuştur. Dışardan içine ne koyduklarını bilmediğim için, ben evde kendim hazırladım ?
- “using (Aes encryptor = Aes.Create())”: Burada şifereleme amaçlı, Microsoft’un “System.Security.Cryptography” sınıfından Aes kullanılmıştır.
- “Rfc2898DeriveBytes pdb”: Aes’ın CryptoTransform’u olarak kullanılmıştır.
-
“new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }” : Salt keydir.
-
- Nedir bu encryption işlerinin tam mantığı diyenlere bu makale gelsin: https://www.borakasmer.com/net-coreda-encrypt-decrypt-islemleri-ve-hasden-farki/
- “using (MemoryStream ms = new MemoryStream())”: CryptoStream için Memory Stream kullanılmıştır.
- Genelde okuma işlemi “System.Text.Encoding.UTF8.GetBytes” ve yazma işleminde “System.Convert.ToBase64String()” ile yapılır. Eğer DB’de bir alana kaydedilmeyecek ise ve web servisinde bir endpoint’e gönderilmeyecek ise string’e çevirmeye gerek yoktur.
- “cs.Write(clearBytes.ToArray(), 0, clearBytes.Length)”: Veriyi şifreleyerek başından sonuna kadar belleğe binary[] olarak yazıyoruz.
- “File.WriteAllBytes(filePath, clearBytes.ToArray())”: Dosya üzerine encryptedli hali yazılmaktadır.
Hiçbir Encoding kullanılmadan ve Base64ToString()’e, ilgili file çevrilmeden doğrudan binary[] olarak çalışılması ile , ilgili dosyanın örneğin 9KB yerine 4KB yer kaplaması sağlanmıştır. Ayrıca PDF gibi dökümanlarda, encode,decode sırasında oluşabilecek türkçe karakter sorununun önüne geçilmiştir.
EncryptFile():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public static void EncryptFile(string filePath) { ReadOnlySpan<byte> clearBytes = File.ReadAllBytes(filePath).AsSpan(); string EncryptionKey = "753ovb050y7hdd9ly2f7r4h3lq7jj2c89qbf"; using (Aes encryptor = Aes.Create()) { Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x47, 0x76, 0x62, 0x6f, 0x20, 0x4d, 0x73, 0x64, 0x73, 0x65, 0x55, 0x20, 0x33 }); encryptor.Key = pdb.GetBytes(32); encryptor.IV = pdb.GetBytes(16); using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write)) { cs.Write(clearBytes.ToArray(), 0, clearBytes.Length); cs.FlushFinalBlock(); cs.Close(); } clearBytes = ms.ToArray(); } } File.WriteAllBytes(filePath, clearBytes.ToArray()); } |
DencryptFile():
Encrypt için geçerli tüm adımlar, Decrypt için de geçerlidir. Tek fark binary olarak şifrelenen dosya, okunur hale geri getirilmektedir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public static void DecryptFile(string filePath) { ReadOnlySpan<byte> cipherBytes = File.ReadAllBytes(filePath).AsSpan(); // Get encryption key from config file string EncryptionKey = "753ovb050y7hdd9ly2f7r4h3lq7jj2c89qbf"; //string EncryptionKey = ConfigurationManager.AppSettings["EncryptionKey"]; using (Aes encryptor = Aes.Create()) { Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x47, 0x76, 0x62, 0x6f, 0x20, 0x4d, 0x73, 0x64, 0x73, 0x65, 0x55, 0x20, 0x33 }); encryptor.Key = pdb.GetBytes(32); encryptor.IV = pdb.GetBytes(16); using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write)) { cs.Write(cipherBytes.ToArray(), 0, cipherBytes.Length); cs.FlushFinalBlock(); cs.Close(); } cipherBytes = ms.ToArray(); } } File.WriteAllBytes(filePath, cipherBytes.ToArray()); } |
Örnek: Aşağıda görüldüğü gibi “Pdf” dosyasının şifrelendikten ve deşifre edildikten sonraki halleri gözükmektedir.
EncryptFile(“C://mails/Test.pdf”):
DecryptFile(“C://mails/Test.pdf”))
Bir Klasör İçindeki Tüm Dosyaların Encrypt Edilmesi:
Burada amaç, ilgili folder altındaki tüm dosyaların “.eml, .gz, .rar, .exe, .pdf, .xlsx, .msg” vb., tamamının şifrelenmesidir. Şifreli dosyaların şifrelenmemesi, sadece şifreli dosyaların deşifre edilmesi ve şifrelenmeden kalan dosyaların belirlenmesi ayrıca üzerinde konuşacağımız konulardır.
- “DirectoryInfo d = new DirectoryInfo(folderPath); FileInfo[] Files = d.GetFiles(fileType)”: İlgili klasör altındaki, tüm dosyaların bilgisi alınır. Ya da fileType belirtilir ise, sadece belirlenen tipdeki dosyalar seçilerek şifrelenir.
- “foreach (FileInfo file in Files)”: Klasör içindeki herbir dosya ya da belirlenen tipdeki dosyalar, tek tek gezilerek şifrelenir.
* “FileAttributes attributes = File.GetAttributes(filePath)”: Dosya üzerinde, varsa tanımlı attributeler alınır. Bu attributleri, biz bir işaretleyici olarak kullanacağız. Böylece Encrypted olan dosyalara bir attribute atayıp, tekrar tekrar şifrelemeyeceğiz. Ya da encrypted olmayan bir dosyayı bu attribute’den fark edip, decrypt etmeye çalışmayacağız.
- “if (!((attributes & FileAttributes.Offline) == FileAttributes.Offline))”: Burada, ilgili dosyanın “Offline” attribute’ünün var olup olmamasına bakılır. Olmadığı durumlarda, ilgili dosya encrypt edilecektir. Encrypt edilen dosyalara “Offline” attribute’ü atanacaktır.
- Yukarıdaki “EncryptFile()” methodunda anlatıldığı gibi, ilgili dosya şifrelenerek encrypted hale getirilir.
- “File.SetAttributes(filePath, File.GetAttributes(filePath) | FileAttributes.Offline)”: Şifreleme işlemi bittiği zaman, ilgili dosyaya “Offline” attribute’ü atanmaktadır.
EncryptAllFilesInFolder():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public static void EncryptAllFilesInFolder(string folderPath, string fileType) { DirectoryInfo d = new DirectoryInfo(folderPath); FileInfo[] Files = d.GetFiles(fileType); string EncryptionKey = "753ovb050y7hdd9ly2f7r4h3lq7jj2c89qbf"; foreach (FileInfo file in Files) { string filePath = $"{folderPath}\\{file.Name}"; FileAttributes attributes = File.GetAttributes(filePath); //System, Offline, Readonly if (!((attributes & FileAttributes.Offline) == FileAttributes.Offline)) { ReadOnlySpan<byte> clearBytes = File.ReadAllBytes(filePath).AsSpan(); using (Aes encryptor = Aes.Create()) { Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }); encryptor.Key = pdb.GetBytes(32); encryptor.IV = pdb.GetBytes(16); using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write)) { cs.Write(clearBytes.ToArray(), 0, clearBytes.Length); cs.FlushFinalBlock(); cs.Close(); } clearBytes = ms.ToArray(); } } File.WriteAllBytes(filePath, clearBytes.ToArray()); File.SetAttributes(filePath, File.GetAttributes(filePath) | FileAttributes.Offline); } } } |
Bir Klasör İçindeki Tüm Dosyaların Decrypt Edilmesi:
Burada amaç, ilgili folder altındaki tüm dosyaların “.eml, .gz, .rar, .exe, .pdf, .xlsx, .msg” vb. , tamamının deşifre edilmesidir.
- “DirectoryInfo d = new DirectoryInfo(folderPath); FileInfo[] Files = d.GetFiles(fileType)”: İlgili klasör altındaki, tüm dosyaların bilgisi alınır.
- “foreach (FileInfo file in Files)”: Klasör içindeki herbir dosya, tek tek gezilerek deşifre edilir.
- “FileAttributes attributes = File.GetAttributes(filePath)”: Burada da ilgili dosya içinde gezilen herbir dosyanın attributeleri alınır.
- * “if (((attributes & FileAttributes.Offline) == FileAttributes.Offline))”: Dosyaya atanan attributeler içinde “Offline” olanlar “Bitwise” ile kontrol edilir. Bu şekilde Decrypt edilecek dosyanın, encryptedli olup olmadığına bakılır. Bu attribute yani (Offline) yok ise, Decrypt işlemi yapılmaz. Encrypt olmayan bir dosyayı, decrypt edilmeye çalışılır ise hata alınır.
-
- Bitwise ile => “(4096 & (4096+32) )) == 4096″ : Her dosya aynı zamanda default bir Archive attribute’una sahiptir. (4096) Offline attiribute’u, dosyanın sahip olduğu (4096+32)Offline+Archive içinde var mı şeklinde kontrol yapılmaktadır. Bitwise hakkında daha detyalı bilgiye burdaki videodan erişebilirsiniz.
- “attributes = attributes & ~FileAttributes.Offline; File.SetAttributes(filePath, attributes)”: Dosya üzerindeki Offline attribute’ü, Decrypt işlemi yapılmadan önce kaldırılır. Böylece ilgili dosyanın, açık flat bir şekilde olduğu belirtilmiş olunur.
- Yukarıdaki “DencryptFile()” methodunda anlatıldığı gibi, tanımlanan bir dosyanın decrypt edilmesi işlemlerinin tamamı, burada da geçerlidir.
DecryptAllFilesInFolder:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public static void DecryptAllFilesInFolder(string folderPath, string fileType) { DirectoryInfo d = new DirectoryInfo(folderPath); FileInfo[] Files = d.GetFiles(fileType); string EncryptionKey = "753ovb050y7hdd9ly2f7r4h3lq7jj2c89qbf"; foreach (FileInfo file in Files) { try { string filePath = $"{folderPath}\\{file.Name}"; FileAttributes attributes = File.GetAttributes(filePath); if (((attributes & FileAttributes.Offline) == FileAttributes.Offline)) { attributes = attributes & ~FileAttributes.Offline; File.SetAttributes(filePath, attributes); ReadOnlySpan<byte> cipherBytes = File.ReadAllBytes(filePath).AsSpan(); using (Aes encryptor = Aes.Create()) { Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }); encryptor.Key = pdb.GetBytes(32); encryptor.IV = pdb.GetBytes(16); using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write)) { cs.Write(cipherBytes.ToArray(), 0, cipherBytes.Length); cs.FlushFinalBlock(); cs.Close(); } cipherBytes = ms.ToArray(); } } File.WriteAllBytes(filePath, cipherBytes.ToArray()); } } catch (Exception ex) { } } } |
MatchFiles(): Bu method tamamen bonus amaçlı verilmiştir. Bir dosyada Encrypt işlemine başlanmadan önce, başka bir tabloya yedeği alınır. İlgili klasördeki tüm dosyalar için Encrypt işlemi tamamlandıktan sonra, encrypt edilmeyen dosyaların bulunması amacı ile bu iki folder’ın karşılaştırılır. Eşit olan dosyalar var ise bu encrypt olmadıkları anlamına gelmektedir. Bu durumda ilgili dosyaların tekrardan encrypt işlemine tabi tutulması gerekmektedir. İşte bu durumda aşağıdaki gibi bir methoda ihtiyaç duyulmaktadır.
“File.ReadAllBytes(sourceFilePath).SequenceEqual(File.ReadAllBytes(targetFilePath))”: Belirlenen source ve target klasörü içindeki dosyalar sıra ile byte[] dizisine çevrilir, ve sonrasında SquenceEquel() methodu ile karşılaştırılarak boole’ın sonuç geri dönülür.
1 2 3 4 |
public static bool MatchFiles(string sourceFilePath, string targetFilePath) { return File.ReadAllBytes(sourceFilePath).SequenceEqual(File.ReadAllBytes(targetFilePath)); } |
MatchAllFilesInFolder(string sourcePath, string targetPath): Senaryoya göre, bir klasör içindeki dosyaların encryption işlemine başlanmadan önce, clone’u başka bir kalsöre alınır. Tüm dosyalar için encryption işlemi bittikten sonra, şifrelenmemiş dosyalarının belirlenmesi için iki klasör match edilir. Aynı olan dosyalar, şifrelenmemiş anlamına gelir ve o dosyalar için Encryption işlemi tekrarlanır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public static List<string> MatchAllFilesInFolder(string sourcePath, string targetPath) { DirectoryInfo d = new DirectoryInfo(sourcePath); FileInfo[] Files = d.GetFiles(); List<string> diffFilesList = new List<string>(); foreach (FileInfo file in Files) { string sourceFilePath = $"{sourcePath}\\{file.Name}"; string targetFilePath = $"{targetPath}\\{file.Name}"; if (!MatchFiles(sourceFilePath, targetFilePath)) { diffFilesList.Add(file.Name); } else { Console.WriteLine($"Şifrelenmemiş Dosya:{file.Name}"); } } return diffFilesList; } |
Sonuç:
Bu makalede bir klasör içindeki dosyaların, ya tamamının ya da belli bir kısmının türüne göre şifrelenmesini sağladık. Bunu yaparken size birkaç trick noktadan bahsetmek istedim.
- Genelde şifrelenen dosyaların boyutu orjinal boyutuna göre en az 2 kat fazla olur. Ama biz bu örnekde encryption sırasında “Base64String” kullanmıyarak, şifrelenmiş dosyalarının boyutunun orjinali ile aynı olmasını sağladık. Ayrıca PDF gibi dökümanlarda, encode,decode sırasında oluşabilecek türkçe karakter sorununun önüne geçtik. (Bu yöntem, ilgili dosyanın DB’de varchar bir alana kaydedilmeyecek, ve web servisinde bir endpoint’e gönderilmeyecek ise geçerlidir.)
- Byte[] şekline döndürülen dosyaları, C#’da bellek optimizasyonu için “AsSpan()” ile “ReadOnlySpan<byte>()” çevirip bu şekilde bir değişkene atadık.. Ve ilgili değişken üzerinde, bu şekilde işlem yaptık.
- Şifrelenen dosyalara, bitwise şeklinde bir Attribute atayarak (Offline) gibi işaretlenmesi sağladık. Bu şekilde encrypted yapılan dosyaları belirlemiş olduk. Encrypted sırasında, ilgili dosyanın attribute’una bakarak şifreli ise tekrardan şifrelenmesini, ya da decrypt işlemi sırasında encrypted olmayan bir dosyanın decrypt edilmesini engelledik. Böylece hata almaktan kurtulduk :)
- Son olarak ayrıca şifrelenecek dosyaların bir eşleniğini başka bir klasörde tuttularak, şifreleme işlemi bittikten sonra bir karşılaştırma yaptık. Böylece, şifrelenmemiş dosyaları belirleyip tekrardan şifrelemeyi deneyebildik.Tabi ki amaç güvenlik. Bir hata sonucu şifrelenmemiş dosyaların gözden kaçarak, açık şekilde kalmasının önüne geçtik.
Geldik bir makalenin daha sonuna. Hepinize 2023 yılının sağlık, mutluluk, huzur ve bol kazanç getirmesi dileği ile yeni bir makalede görüşmek üzere hoşçakalın.
Source Code: https://gist.github.com/borakasmer/0f5b0394447309ada63c6ccf8f9c1877
Eline sağlık Bora hocam , bu makale 2023 yılındaki ilk okuduğum makale olacak :), nice sağlıklı, mutlu ve huzurlu yıllar dilerim
Teşekkürler Fahreddin..