.Net Core Üzerinde İşaretli Kolonların Şifrelenerek Kaydedilmesi
Selamlar,
Bu makalede, istenen bir tabloya ait kolonun işaretlenerek, kaydedilme sırasında şifrelenmesini, .Net Core Entity Framework üzerinde inceleyeceğiz.
Öncelikle bu projede DB First kullanılmıştır. Ama istenir ise aynı işlem, CodeFirst bir yapı için de uygulanabilir.
User.cs: Aşağıda görüldüğü gibi bir User tablosu vardır. Bu örnekte Gsm alanı, güvenlik nedeni ile kaydedilirken, geriye dönülebilir Encrypted bir şekilde SQL DB’de saklanacaktır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class Users { public Users() { UserRoles = new HashSet<UserRoles>(); } public int Id { get; set; } public string Name { get; set; } public string LastName { get; set; } public string UserName { get; set; } public string Password { get; set; } public string PasswordHash { get; set; } public string Email { get; set; } public string Gsm { get; set; } public bool IsDeleted { get; set; } public bool? IsAdmin { get; set; } public virtual ICollection<UserRoles> UserRoles { get; set; } } |
Şimdi Users sınıfına bağlı, partial bir başka Users class’ı oluşturalım. Yeni oluşturulan bu sınıfı BaseEntitiy adında bir sınıftan, Inherit alalım. Böylece DB’den generate edilen classlarda, ortak bir değişikliğe gidilmek istendiğinde, BaseEntity class’ı üzerinde yapılacak bir ekleme, yeterli olacaktır.
PartialEntities/User(1):
1 |
public partial class Users : BaseEntity |
Core/BaseEntity: Örneğin aşağıdaki BaseEntity sınıfında, her entity’nin ortak CreatedTime alanı bulunmaktadır. [NotMapped] olarak işaretlenerek, DB ile herhangi bir eşleşme yapılmaması sağlanmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 |
using System; using System.ComponentModel.DataAnnotations.Schema; namespace Core { public abstract class BaseEntity { private DateTime dateTime; [NotMapped] public DateTime CreatedTime { get { this.dateTime = DateTime.Now; return dateTime; } set { } } } } |
PartialEntities/CryptoData: Şifrelenecek property veya propertylere konacak Crypto işareti, aşağıdaki gibi tanımlanır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using System; using System.Collections.Generic; using System.Text; namespace DB.PartialEntites { [AttributeUsage(AttributeTargets.All)] public class CryptoData : System.Attribute { public CryptoData() { } } } |
Not: Herhangi bir classın var olan bir kolonuna, PartialClass’ında doğrudan işaretlemeye çalışılır ise, böyle bir kolon var hatası alınır. Bunun için MetaData tanımlanıp, ilgili kolonun üstünde işaretlenmesi gerekmektedir.
PartialEntities/UserMetaData: Users tablosunda var olan bir kolonu işaretlemek için, aşağıdaki gibi bir MetaData’nın tanımlanması gerekmektedir.
1 2 3 4 5 |
public class UserMetaData { [CryptoData] public string Gsm { get; set; } } |
PartialEntities/User(2): Aşağıda görüldüğü gibi, ilgili Users Model, “UserMetaData” ile işaretlenir. Böylece Users ==> Gsm kolonu [CryptoData] şeklinde işaretlenmiş olunur.
1 2 |
[MetadataType(typeof(UserMetaData))] public partial class Users : BaseEntity |
Şimdi sıra geldi global bir Repository yaratıp, ilgili işaretleme ile gelen kolonları şifrelemeye.
Repository/IRepository: GeneralRepository katmanın inherit edileceği interfacedir. Örnek amaçlı sadece “Insert()” methodu zorlanmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using Core; using DB; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace Repository { public interface IRepository<T> where T : BaseEntity { IQueryable<T> Table { get; } void Insert(T entity); } } |
Repository/GeneralRepository : IRepository’den türeyen bu sınıfdaki “Insert()” methodu, projedeki tüm servislerden, herhangi bir entity’e kaydedilme anında çağrılan methoddur. Kısaca dağıtık bir mimaride, her yerden çağrılan DB ile iletişimi sağlayan katmandır.
- _context : DB katmanıdır.
- _entities : Entity, yani DB’deki bir tablodur.
- IEncryption : Bir sonraki adımda tanımlanacak olan, şifreleme işleminin yapıldığı sınıftır.
- “public GeneralRepository(NorthwindContext context, IEncryption encryption)” : Dependency injection ile context ve encryption sınıfları sisteme alınmıştır.
- “Insert(T entity)” : BaseEntity’den türeyen tüm entityleri paramtere olarak alan kaydetme işleminin yapıldığı methoddur.
- “System.ComponentModel.DataAnnotations.MetadataTypeAttribute[] metadataTypes = entity.GetType().GetCustomAttributes(true).OfType<System.ComponentModel.DataAnnotations.MetadataTypeAttribute>().ToArray()” : Method üzerinde tanımlı tüm attributeler, bu kod ile “metadataTypes[]” dizisine atanır.
- “foreach (System.ComponentModel.DataAnnotations.MetadataTypeAttribute metadata in metadataTypes)” : Metadata listesi içinde gezilir.
- “foreach (System.Reflection.PropertyInfo pi in properties)” : Her metadata içindeki, propertyler gezilir.
- “if (Attribute.IsDefined(pi, typeof(DB.PartialEntites.CryptoData)))” : Eğer property tipi “CryptoData” ise, değeri şifrelenir.
- “_context.Entry(entity).Property(pi.Name).CurrentValue = “: İlgili property’nin değeri alınır.
- “_encryption.EncryptText(_context.Entry(entity).Property(pi.Name).CurrentValue.ToString())” : Şifrelenerek tekrar kendisine atanır.
- “if (Attribute.IsDefined(pi, typeof(DB.PartialEntites.CryptoData)))” : Eğer property tipi “CryptoData” ise, değeri şifrelenir.
- “foreach (System.Reflection.PropertyInfo pi in properties)” : Her metadata içindeki, propertyler gezilir.
- ” _entities.Add(entity); _context.SaveChanges()” : Tüm kayıtlar DB’ye, entity yardımı ile kaydedilir.
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 class GeneralRepository<T> : IRepository<T> where T : BaseEntity { private readonly NorthwindContext _context; private DbSet<T> _entities; private readonly IEncryption _encryption; public GeneralRepository(NorthwindContext context, IEncryption encryption) { _context = context; _entities = context.Set<T>(); _encryption = encryption; } public void Insert(T entity) { if (entity == null) throw new ArgumentNullException(nameof(entity)); //Set Default UsedTime Parameter ==> UsedTime BaseEntity Property. //Entity'ye ait MetaData varsa bulunur System.ComponentModel.DataAnnotations.MetadataTypeAttribute[] metadataTypes = entity.GetType().GetCustomAttributes(true).OfType<System.ComponentModel.DataAnnotations.MetadataTypeAttribute>().ToArray(); foreach (System.ComponentModel.DataAnnotations.MetadataTypeAttribute metadata in metadataTypes) { System.Reflection.PropertyInfo[] properties = metadata.MetadataClassType.GetProperties(); //Metadata atanmış entity'nin tüm propertyleri tek tek alınır. foreach (System.Reflection.PropertyInfo pi in properties) { //Eğer ilgili property ait CryptoData flag'i var ise ilgili deger encrypt edilir. if (Attribute.IsDefined(pi, typeof(DB.PartialEntites.CryptoData))) { _context.Entry(entity).Property(pi.Name).CurrentValue = _encryption.EncryptText(_context.Entry(entity).Property(pi.Name).CurrentValue.ToString()); } } } _entities.Add(entity); _context.SaveChanges(); } public virtual IQueryable<T> Table => Entities; protected virtual DbSet<T> Entities => _entities ?? (_entities = _context.Set<T>()); } |
Core/Security/IEncryption : Encryption sınıfında kullanılacak methodlar, IEncryption interface’inde aşağıdaki gibi tanımlanır.
1 2 3 4 5 6 7 8 9 10 11 12 |
using System; using System.Collections.Generic; using System.Text; namespace Core.Security { public interface IEncryption { string EncryptText(string text, string privateKey = ""); string DecryptText(string text, string privateKey = ""); } } |
Core/Security/Encryption : Güvenlik amaçlı işaretlenen kolonlar, “Encryption” sınıfındaki “EncryptText()” methodu kullanılarak, şifrelenir ve DB’ye kaydedilir. Aynı şekilde “DecryptText()” methodu kullanılarak, şifreli kolonların gerçek değerlerine ulaşılır.
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
using Core.Configuration; using Core.CustomException; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; namespace Core.Security { public class Encryption : IEncryption { private readonly IOptions<NrtConfig> _nrtConfig; public Encryption(IOptions<NrtConfig> nrtConfig) { _nrtConfig = nrtConfig; } private byte[] EncryptTextToMemory(string data, byte[] key, byte[] iv) { using (var ms = new MemoryStream()) { using (var cs = new CryptoStream(ms, new TripleDESCryptoServiceProvider().CreateEncryptor(key, iv), CryptoStreamMode.Write)) { var toEncrypt = Encoding.Unicode.GetBytes(data); cs.Write(toEncrypt, 0, toEncrypt.Length); cs.FlushFinalBlock(); } return ms.ToArray(); } } private string DecryptTextFromMemory(byte[] data, byte[] key, byte[] iv) { using (var ms = new MemoryStream(data)) { using (var cs = new CryptoStream(ms, new TripleDESCryptoServiceProvider().CreateDecryptor(key, iv), CryptoStreamMode.Read)) { using (var sr = new StreamReader(cs, Encoding.Unicode)) { return sr.ReadToEnd(); } } } } public string EncryptText(string text, string privateKey = "") { if (string.IsNullOrEmpty(text) || text == "null") return string.Empty; if (string.IsNullOrEmpty(privateKey)) privateKey = _nrtConfig.Value.PrivateKey; using (var provider = new TripleDESCryptoServiceProvider()) { provider.Key = Encoding.ASCII.GetBytes(privateKey.Substring(0, 16)); provider.IV = Encoding.ASCII.GetBytes(privateKey.Substring(8, 8)); var encryptedBinary = EncryptTextToMemory(text, provider.Key, provider.IV); return Convert.ToBase64String(encryptedBinary); } } //Example Password: 123456 ==> MTIzNDU2 public string DecryptText(string text, string privateKey = "") { try { if (string.IsNullOrEmpty(text) || text == "null") return string.Empty; if (string.IsNullOrEmpty(privateKey)) privateKey = _nrtConfig.Value.PrivateKey; using (var provider = new TripleDESCryptoServiceProvider()) { provider.Key = Encoding.ASCII.GetBytes(privateKey.Substring(0, 16)); provider.IV = Encoding.ASCII.GetBytes(privateKey.Substring(8, 8)); var buffer = Convert.FromBase64String(text); return DecryptTextFromMemory(buffer, provider.Key, provider.IV); } } catch { throw new InvalidTokenException(); } } } } |
Geldik bir makalenin daha sonuna . Bu makalede istenen bir kolon için yapılan işaretleme ile, içeriğinin şifrelenerek kaydedilmesi sağlanmıştır. Burada önemli olan, [CryptoData] ile işaretlenen tüm kolonların şifrelenerek DB’ye kaydedilmesidir. Böylece herhangi bir DBContext’deki, istenen bir kolonun, sadece işaretlenerek Repository katmanında işaret tipine göre istenen işlemin yapılabileceğini hep beraber gördük.
Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Source:
Selam hocam elinize sağlık,
Peki bu şifreli alanlarda arama yapmak istersek nasıl yapacağız?
Selamlar ve teşekkürler,
Sql’de yaparsan, bir tane decrypt function’ı yazman gerekiyor. Sonra where & like koşulunda şifreli kolonu, bu function() içinde yazman gerekiyor.
Kod tarafında çözümü için d,e linq ile aynı işlemi yapman lazım ama bu sefer sql function yerine decrypt eden C# methodu() çağırman gerekiyor.
İyi çalışmalar.
Yanıtınız için teşekkürler.
Performans açısından bir yavaşlık olmaz mı?
Mesela 1M kayıt içerisinde arama yaparken
Selamlar,
Hocam Ef Core da bulunan value conversion özelliğini kullanmamanızın özel bi sebebi varmı. Bu özelliğin bu konuda çok daha efektif olduğunu düşünüyorum. Hem linq sorguları esnasındada otomatik çalışıyor. Kod içerisinde çalışan yazılımcının encrypt decrypt işleminden haberi olmadan çalışmasına imkan tanıyor. Cevabınızın belki projeyi Entity Framework bağımlılığından kurtarmak için olabileceğini düşünüyorum. Görüşünüz nedir ? Paylaşım için teşekkürler