Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 4.0.0 #45

Merged
merged 14 commits into from
Jan 18, 2023
39 changes: 0 additions & 39 deletions .editorconfig

This file was deleted.

28 changes: 23 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,22 @@ on:
jobs:
build-library:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Checkout repository
uses: actions/checkout@v3

- name: Setup .NET
uses: actions/setup-dotnet@v1
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.0.x
dotnet-version: |
3.1.x
5.0.x
6.0.x
7.0.x

- name: Display .NET version
run: dotnet --version

- name: Restore dependencies
run: dotnet restore
Expand All @@ -24,4 +33,13 @@ jobs:
run: dotnet build EntityFrameworkCore.DataEncryption.sln --configuration Release -f net6.0 --no-restore

- name: Run unit tests
run: dotnet test EntityFrameworkCore.DataEncryption.sln --configuration Release /p:CollectCoverage=true /p:Exclude="[xunit*]*" /p:CoverletOutputFormat=opencover /p:CoverletOutput="../TestResults/TestResults.xml" /maxcpucount:1 -f net6.0 --no-build --verbosity normal
run: dotnet test --configuration Release --collect:"XPlat Code Coverage" --settings ./test/EntityFrameworkCore.DataEncryption.Test/runsettings.xml

- name: Copy coverage results
run: cp ./test/EntityFrameworkCore.DataEncryption.Test/TestResults/**/*.xml ./test/EntityFrameworkCore.DataEncryption.Test/TestResults/

- name: Upload code coverage
uses: codecov/codecov-action@v3
with:
files: ./test/EntityFrameworkCore.DataEncryption.Test/TestResults/coverage.opencover.xml
fail_ci_if_error: true
25 changes: 14 additions & 11 deletions EntityFrameworkCore.DataEncryption.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30804.86
# Visual Studio Version 17
VisualStudioVersion = 17.3.32901.215
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3EC10767-1816-46B2-A78E-9856071CCFDB}"
EndProject
Expand All @@ -22,17 +22,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{64C3
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AesSample", "samples\AesSample\AesSample.csproj", "{8AA1E576-4016-4623-96C8-90330F05F9A8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".azure", ".azure", "{073FEA06-67CF-47F8-8CE4-2B153A7D8443}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{EEF46CDC-C438-48FC-BEF7-83AEE26C63F7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pipelines", "pipelines", "{68558245-F605-413F-A1D9-A4F60D489D68}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{4F549FEF-C57B-4A34-A2C7-8A632762DF85}"
ProjectSection(SolutionItems) = preProject
.azure\pipelines\azure-pipelines.yml = .azure\pipelines\azure-pipelines.yml
EndProjectSection
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5EE4E8BE-6B15-49DB-A4A8-D2CD63D5E90C}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.github\workflows\build.yml = .github\workflows\build.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AesSample.Fluent", "samples\AesSample.Fluent\AesSample.Fluent.csproj", "{CF04DE64-713F-4ED3-9C14-B7C11D22454C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -51,6 +49,10 @@ Global
{8AA1E576-4016-4623-96C8-90330F05F9A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8AA1E576-4016-4623-96C8-90330F05F9A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8AA1E576-4016-4623-96C8-90330F05F9A8}.Release|Any CPU.Build.0 = Release|Any CPU
{CF04DE64-713F-4ED3-9C14-B7C11D22454C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CF04DE64-713F-4ED3-9C14-B7C11D22454C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CF04DE64-713F-4ED3-9C14-B7C11D22454C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CF04DE64-713F-4ED3-9C14-B7C11D22454C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -59,8 +61,9 @@ Global
{D037F8D0-E606-4C5A-8669-DB6AAE7B056B} = {3EC10767-1816-46B2-A78E-9856071CCFDB}
{5E023B6A-0B47-4EC2-90B9-2DF998E58ADB} = {E4089551-AF4E-41B3-A6F8-2501A3BE0E0C}
{8AA1E576-4016-4623-96C8-90330F05F9A8} = {64C3D7D1-67B8-4070-AE67-C71B761535CC}
{073FEA06-67CF-47F8-8CE4-2B153A7D8443} = {3A8D800E-77BD-44EF-82DB-C672281ECAAA}
{68558245-F605-413F-A1D9-A4F60D489D68} = {073FEA06-67CF-47F8-8CE4-2B153A7D8443}
{EEF46CDC-C438-48FC-BEF7-83AEE26C63F7} = {3A8D800E-77BD-44EF-82DB-C672281ECAAA}
{4F549FEF-C57B-4A34-A2C7-8A632762DF85} = {EEF46CDC-C438-48FC-BEF7-83AEE26C63F7}
{CF04DE64-713F-4ED3-9C14-B7C11D22454C} = {64C3D7D1-67B8-4070-AE67-C71B761535CC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4997BAE9-29BF-4D79-AE5E-5605E7A0F049}
Expand Down
89 changes: 71 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
[![.NET](https://github.com/Eastrall/EntityFrameworkCore.DataEncryption/actions/workflows/build.yml/badge.svg)](https://github.com/Eastrall/EntityFrameworkCore.DataEncryption/actions/workflows/build.yml)
[![codecov](https://codecov.io/gh/Eastrall/EntityFrameworkCore.DataEncryption/branch/master/graph/badge.svg)](https://codecov.io/gh/Eastrall/EntityFrameworkCore.DataEncryption)
[![Nuget](https://img.shields.io/nuget/v/EntityFrameworkCore.DataEncryption.svg)](https://www.nuget.org/packages/EntityFrameworkCore.DataEncryption)
[![Nuget Downloads](https://img.shields.io/nuget/dt/EntityFrameworkCore.DataEncryption)](https://www.nuget.org/packages/EntityFrameworkCore.DataEncryption)

`EntityFrameworkCore.DataEncryption` is a [Microsoft Entity Framework Core](https://github.com/aspnet/EntityFrameworkCore) extension to add support of encrypted fields using built-in or custom encryption providers.

## Disclaimer

This library has been developed initialy for a personal project of mine. It provides a simple way to encrypt column data.
<h4 align="center">:warning: This project is **not** affiliated with Microsoft. :warning:</h4><br>

I **do not** take responsability if you use this in a production environment and loose your encryption key.
This library has been developed initialy for a personal project of mine which suits my use case. It provides a simple way to encrypt column data.

I **do not** take responsability if you use/deploy this in a production environment and loose your encryption key or corrupt your data.

## How to install

Expand All @@ -19,17 +22,29 @@ Install the package from [NuGet](https://www.nuget.org/) or from the `Package Ma
PM> Install-Package EntityFrameworkCore.DataEncryption
```

## How to use
## Supported types

To use `EntityFrameworkCore.DataEncryption`, you will need to decorate your `string` properties of your entities with the `[Encrypted]` attribute and enable the encryption on the `ModelBuilder`.
| Type | Default storage type |
|------|----------------------|
| `string` | Base64 string |
| `byte[]` | BINARY |

To enable the encryption correctly, you will need to use an **encryption provider**, there is a list of the available providers:
## Built-in providers

| Name | Class | Extra |
|------|-------|-------|
| [AES](https://docs.microsoft.com/en-US/dotnet/api/system.security.cryptography.aes?view=netcore-2.2) | [AesProvider](https://github.com/Eastrall/EntityFrameworkCore.DataEncryption/blob/master/src/EntityFrameworkCore.DataEncryption/Providers/AesProvider.cs) | Can use a 128bits, 192bits or 256bits key |
| [AES](https://learn.microsoft.com/en-US/dotnet/api/system.security.cryptography.aes?view=net-6.0) | [AesProvider](https://github.com/Eastrall/EntityFrameworkCore.DataEncryption/blob/main/src/EntityFrameworkCore.DataEncryption/Providers/AesProvider.cs) | Can use a 128bits, 192bits or 256bits key |

## How to use

`EntityFrameworkCore.DataEncryption` supports 2 differents initialization methods:
* Attribute
* Fluent configuration

### Example with `AesProvider`
Depending on the initialization method you will use, you will need to decorate your `string` or `byte[]` properties of your entities with the `[Encrypted]` attribute or use the fluent `IsEncrypted()` method in your model configuration process.
To use an encryption provider on your EF Core model, and enable the encryption on the `ModelBuilder`.

### Example with `AesProvider` and attribute

```csharp
public class UserEntity
Expand Down Expand Up @@ -58,34 +73,72 @@ public class DatabaseContext : DbContext
public DatabaseContext(DbContextOptions options)
: base(options)
{
this._provider = new AesProvider(this._encryptionKey, this._encryptionIV);
_provider = new AesProvider(this._encryptionKey, this._encryptionIV);
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.UseEncryption(this._provider);
modelBuilder.UseEncryption(_provider);
}
}
```
The code bellow creates a new `AesEncryption` provider and gives it to the current model. It will encrypt every `string` fields of your model that has the `[Encrypted]` attribute when saving changes to database. As for the decrypt process, it will be done when reading the `DbSet<T>` of your `DbContext`.
The code bellow creates a new [`AesProvider`](https://github.com/Eastrall/EntityFrameworkCore.DataEncryption/blob/main/src/EntityFrameworkCore.DataEncryption/Providers/AesProvider.cs) and gives it to the current model. It will encrypt every `string` fields of your model that has the `[Encrypted]` attribute when saving changes to database. As for the decrypt process, it will be done when reading the `DbSet<T>` of your `DbContext`.

## Create an encryption provider
### Example with `AesProvider` and fluent configuration

```csharp
public class UserEntity
{
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public int Age { get; set; }
}

> :warning: This section is outdated and doesn't work for V3.0.0 and will be updated soon.
public class DatabaseContext : DbContext
{
// Get key and IV from a Base64String or any other ways.
// You can generate a key and IV using "AesProvider.GenerateKey()"
private readonly byte[] _encryptionKey = ...;
private readonly byte[] _encryptionIV = ...;
private readonly IEncryptionProvider _provider;

public DbSet<UserEntity> Users { get; set; }

public DatabaseContext(DbContextOptions options)
: base(options)
{
_provider = new AesProvider(this._encryptionKey, this._encryptionIV);
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Entities builder *MUST* be called before UseEncryption().
var userEntityBuilder = modelBuilder.Entity<UserEntity>();

userEntityBuilder.Property(x => x.Username).IsRequired().IsEncrypted();
userEntityBuilder.Property(x => x.Password).IsRequired().IsEncrypted();

modelBuilder.UseEncryption(_provider);
}
}
```

## Create an encryption provider

`EntityFrameworkCore.DataEncryption` gives the possibility to create your own encryption providers. To do so, create a new class and make it inherit from `IEncryptionProvider`. You will need to implement the `Encrypt(string)` and `Decrypt(string)` methods.

```csharp
public class MyCustomEncryptionProvider : IEncryptionProvider
{
public string Encrypt(string dataToEncrypt)
public byte[] Encrypt(byte[] input)
{
// Encrypt data and return as Base64 string
// Encrypt the given input and return the encrypted data as a byte[].
}

public string Decrypt(string dataToDecrypt)
public byte[] Decrypt(byte[] input)
{
// Decrypt a Base64 string to plain string
// Decrypt the given input and return the decrypted data as a byte[].
}
}
```
Expand All @@ -99,12 +152,12 @@ public class DatabaseContext : DbContext
public DatabaseContext(DbContextOptions options)
: base(options)
{
this._provider = new MyCustomEncryptionProvider();
_provider = new MyCustomEncryptionProvider();
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.UseEncryption(this._provider);
modelBuilder.UseEncryption(_provider);
}
}
```
Expand Down
19 changes: 19 additions & 0 deletions samples/AesSample.Fluent/AesSample.Fluent.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.10" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\EntityFrameworkCore.DataEncryption\EntityFrameworkCore.DataEncryption.csproj" />
</ItemGroup>

</Project>
40 changes: 40 additions & 0 deletions samples/AesSample.Fluent/DatabaseContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.DataEncryption;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System.ComponentModel.DataAnnotations;

namespace AesSample.Fluent;

public class DatabaseContext : DbContext
{
private readonly IEncryptionProvider _encryptionProvider;

public DbSet<UserEntity> Users { get; set; }

public DatabaseContext(DbContextOptions<DatabaseContext> options, IEncryptionProvider encryptionProvider)
: base(options)
{
_encryptionProvider = encryptionProvider;
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
EntityTypeBuilder<UserEntity> userEntityBuilder = modelBuilder.Entity<UserEntity>();

userEntityBuilder.HasKey(x => x.Id);
userEntityBuilder.Property(x => x.Id).IsRequired().ValueGeneratedOnAdd();
userEntityBuilder.Property(x => x.FirstName).IsRequired();
userEntityBuilder.Property(x => x.LastName).IsRequired();
userEntityBuilder.Property(x => x.Email).IsRequired().IsEncrypted();
userEntityBuilder.Property(x => x.Notes).IsRequired().HasColumnType("BLOB").IsEncrypted(StorageFormat.Binary);
userEntityBuilder.Property(x => x.EncryptedData).IsRequired().IsEncrypted();
userEntityBuilder.Property(x => x.EncryptedDataAsString).IsRequired().HasColumnType("TEXT").IsEncrypted(StorageFormat.Base64);

if (_encryptionProvider is not null)
{
modelBuilder.UseEncryption(_encryptionProvider);
}

base.OnModelCreating(modelBuilder);
}
}
11 changes: 11 additions & 0 deletions samples/AesSample.Fluent/EncryptedDatabaseContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Microsoft.EntityFrameworkCore;

namespace AesSample.Fluent;

public class EncryptedDatabaseContext : DatabaseContext
{
public EncryptedDatabaseContext(DbContextOptions<DatabaseContext> options)
: base(options, null)
{
}
}
Loading