Skip to content

Add transparent data encryption to Entity Framework Core inspired Always Encrypted from MS SQL Server.

License

Notifications You must be signed in to change notification settings

mdima/Harrison314.EntityFrameworkCore.Encryption

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

57 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Harrison314.EntityFrameworkCore.Encryption

NuGet Status

Add transparent data encryption to Entity Framework Core inspired Always Encrypted from MS SQL Server.

Harrison314.EntityFrameworkCore.Encryption is a Microsoft Entity Framework Core extension to add support of encrypted fields using built-in or custom encryption providers.

Features

  • Database agnostic (tested on Sqlite, MS SQL Server, PostgreSQL, Azure CosmosDB).
  • Key rotation.
  • Simple for use.
  • Deterministic or randomized encryption.
  • Build using standard cryptographic algorithms (AES, HMAC SHA2, SP800-108, PBKDF-2,...).
  • Data compression (GZip, LZW).
  • Internal or external (out of process or on another server e.c. Azure Key Valut) encryption providers.
  • Emergency KillSwitch (e.g. for wipe private keys and exiting application when the smartcard is ripped off).
  • Encryption providers:
    • Password (only for testyng),
    • Ceratiricate (Recomandedt use with Windows store with non exportable private keys or SmartCard.),
    • PKCS11 data objects (in contrib library),
    • Remote provider (in contrib library),
    • DPAPI provider (in contrib library),
    • Custom provider (custom implementation of IDbContextEncryptedCryptoProvider).

High-level application architecture

High-level application architecture

Cryptography

The following dataflow diagrams shows the encryption and derivation of the key for a record property.

With randomized encryption mode:

Dataflow encryption randomized

With deterministic encryption mode:

Dataflow encryption randomized

How to use

Sample data model

Original data model:

 public class Patient
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string SocialSecurityNumber { get; set; }
    public string OtherId { get; set; }
    public bool Alert { get; set; }
    public string Notes { get; set; }
    public virtual ICollection<Visist> Visists { get; set; }
}

public class Visist
{
    public int Id { get; set; }
    public DateTime Date { get; set; }
    public string Note { get; set; }
    // and other
}

public class SampleDbContext : DbContext
{
    public DbSet<Patient> Patients
    {
        get;
        set;
    }

    public DbSet<Visist> Visits
    {
        get;
        set;
    }

    public SampleDbContext(DbContextOptions options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Patient>(p =>
        {
            p.HasKey(t => t.Id);
            p.Property(t => t.FirstName).IsRequired().HasMaxLength(150);
            p.Property(t => t.LastName).IsRequired().HasMaxLength(150);
            p.Property(t => t.SocialSecurityNumber).IsRequired().HasMaxLength(150);
            p.Property(t => t.Notes).HasDefaultValue(string.Empty);
            p.HasMany(t => t.Visists).WithOne(t => t.Patient).HasForeignKey(t => t.PatientId);
        });

        modelBuilder.Entity<Visist>(p =>
        {
            p.HasKey(t => t.Id);
        });
    }
}

1. Install nuget package

Run in project folder:

dotnet add package Harrison314.EntityFrameworkCore.Encryption

2. Register encryption services to DI

In Startup.cs register DbContext and Encryption Context for DbContext:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<SampleDbContext>(builder =>
    {
        builder.UseSqlServer(this.Configuration.GetConnectionString("PrimaryDb"));
    });

    services.AddEncryptedContext<SampleDbContext>()
        .WithPasswordEncryptionProvider("Passw0rd*"); // Password provider is only for testing, use other provider
}

3. Update DbContext

Insert AddEncryptionContext to ModelBuilder and mark encrypted properies with purpose, algorithm and mode.

  • purpose - must by uniq string for property in context. Do not change after deploy to production!
  • algorithm - chiper and AEAD algorithm.
  • mode - Deterministic or randomized.
  • compressionMode - Compression mode for compression plaintext data.
public class SampleDbContext : DbContext
{
    public DbSet<Patient> Patients
    {
        get;
        set;
    }

    public DbSet<Visist> Visits
    {
        get;
        set;
    }

    public SampleDbContext(DbContextOptions options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.AddEncryptionContext();

        modelBuilder.Entity<Patient>(p =>
        {
            p.HasKey(t => t.Id);
            p.Property(t => t.FirstName)
              .HasEncrypted("Patient.FirstName", 
                  EncrypetionType.AEAD_AES_256_CBC_HMAC_SHA_256,
                  EncryptionMode.Randomized,
                  CompressionMode.None)
              .IsRequired().HasMaxLength(150);
            p.Property(t => t.LastName)
              .HasEncrypted("Patient.LastName",
                  EncrypetionType.AEAD_AES_256_CBC_HMAC_SHA_256,
                  EncryptionMode.Randomized,
                  CompressionMode.None)
              .IsRequired().HasMaxLength(150);
            p.Property(t => t.SocialSecurityNumber)
              .HasEncrypted("Patient.SocialSecurityNumber",
                  EncrypetionType.AEAD_AES_256_CBC_HMAC_SHA_256,
                  EncryptionMode.Deterministic,
                  CompressionMode.None)
              .IsRequired().HasMaxLength(150);
            p.Property(t => t.Notes).HasDefaultValue(string.Empty);
            p.HasMany(t => t.Visists).WithOne(t => t.Patient).HasForeignKey(t => t.PatientId);
        });

        modelBuilder.Entity<Visist>(p =>
        {
            p.HasKey(t => t.Id);
        });
    }
}

3. Work with sensitive data

Only in encrypted scope is poosible working with encrypted properties.

public class PatientController : Controller
{
    private readonly SampleDbContext context;
    private readonly IDbContextEncryptedProvider<SampleDbContext> encryptionProvider;

    public PatientController(SampleDbContext context, IDbContextEncryptedProvider<SampleDbContext> encryptionProvider)
    {
        this.context = context;
        this.encryptionProvider = encryptionProvider;
    }

    [HttpGet(Name = "GetAllPatients")]
    [ProducesResponseType(typeof(List<PatientInfo>), 200)]
    public async Task<IActionResult> GetAll()
    {
        IEncryptedScopeCreator scopeProvider = await this.encryptionProvider.EnshureEncrypted();
        using IDisposable encryptedScope = scopeProvider.IntoScope();

        List<PatientInfo> result = await this.context.Patients.OrderBy(t => t.Id).Select(t => new PatientInfo()
        {
            FirstName = t.FirstName,
            Id = t.Id,
            LastName = t.LastName
        })
        .ToListAsync();

        return this.Ok(result);
    }
}

Find records by encrypted property:

IEncryptedScopeCreator scopeProvider = await this.encryptionProvider.EnshureEncrypted();
using IDisposable encryptedScope = scopeProvider.IntoScope();

Patient patient = await this.context.Patients.Where(t => t.SocialSecurityNumber == ssn).SingleOrDefaultAsync();

Update non-encrypted column without encrypt and decrypt:

using IDisposable defaultValuesScope = this.encryptionProvider.EnshureDefaultValues().IntoScope();

Patient patient = await this.context.Patients.FindAsync(id);
patient.Notes = model.Notes ?? string.Empty;
await this.context.SaveChangesAsync();

Other software

  1. Software Ideas Modeler

About

Add transparent data encryption to Entity Framework Core inspired Always Encrypted from MS SQL Server.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C# 99.8%
  • PowerShell 0.2%