I'm trying to use the MigrationEncryptionProvider in order to encrypt already existing data that are not encrypted.
So I used the following:
var destinationProvider = new AesProvider(_encryptionKey, _encryptionIV);
MigrationEncryptionProvider migrationEncryptionProvider = new(null, destinationProvider);
dbContext.EncryptionProvider = migrationEncryptionProvider;
await dbContext.MigrateAsync();
Unfortunately the MigrateAsync method throws the following exception:
System.AggregateException
HResult=0x80131500
Message=One or more errors occurred. (The type initializer for 'Microsoft.EntityFrameworkCore.DataEncryption.Migration.EncryptionMigrator' threw an exception.)
Source=System.Private.CoreLib
StackTrace:
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) in /_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:line 1883
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) in /_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:line 2733
at System.Threading.Tasks.Task.Wait() in /_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs:line 2607
at MyMainProject.Startup.Configure(IApplicationBuilder app, ILogger`1 logger, IServiceProvider serviceProvider, WalletDbContext dbContext) in C:\MyMainProject\Startup.cs:line 364
at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) in /_/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs:line 435
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder) in /_/src/Hosting/Hosting/src/Internal/ConfigureBuilder.cs:line 32
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.<>c__DisplayClass4_0.<Build>b__0(IApplicationBuilder builder) in /_/src/Hosting/Hosting/src/Internal/ConfigureBuilder.cs:line 21
at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass15_0.<UseStartup>b__1(IApplicationBuilder app) in /_/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs:line 333
at Microsoft.AspNetCore.Mvc.Filters.MiddlewareFilterBuilderStartupFilter.<>c__DisplayClass0_0.<Configure>g__MiddlewareFilterBuilder|0(IApplicationBuilder builder) in /_/src/Mvc/Mvc.Core/src/Filters/MiddlewareFilterBuilderStartupFilter.cs:line 23
at Microsoft.AspNetCore.HostFilteringStartupFilter.<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder app) in /_/src/DefaultBuilder/src/HostFilteringStartupFilter.cs:line 18
at Microsoft.AspNetCore.Hosting.GenericWebHostService.<StartAsync>d__37.MoveNext() in /_/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs:line 105
at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>d__12.MoveNext() in /_/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs:line 68
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.<RunAsync>d__4.MoveNext() in /_/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs:line 64
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.<RunAsync>d__4.MoveNext() in /_/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs:line 76
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host) in /_/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs:line 51
at MyMainProject.Program.Main(String[] args) in C:\MyMainProject\Program.cs:line 9
This exception was originally thrown at this call stack:
System.RuntimeType.GetMethodImplCommon(string, int, System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[]) in RuntimeType.CoreCLR.cs
System.Type.GetMethod(string, System.Reflection.BindingFlags) in Type.cs
System.Type.GetMethod(string) in Type.cs
Microsoft.EntityFrameworkCore.DataEncryption.Migration.EncryptionMigrator.EncryptionMigrator() in EncryptionMigrator.cs
Inner Exception 1:
TypeInitializationException: The type initializer for 'Microsoft.EntityFrameworkCore.DataEncryption.Migration.EncryptionMigrator' threw an exception.
Inner Exception 2:
AmbiguousMatchException: Ambiguous match found.
This is caused by the extension method Set of the DbContext class done in the EncryptionMigrator class:
private static IQueryable<object> Set(this DbContext context, IEntityType entityType)
{
var method = SetMethod.MakeGenericMethod(entityType.ClrType);
var result = method.Invoke(context, null);
return (IQueryable<object>)result;
}
I thought it was a problam cause by the fact I didn't use properly the MigrationEncryptionProvider, but then I tried to run the unit test MigrateOriginalToEncryptedTest implemented here and I found somethig strange.
The test passes but looking at the SQLite db used for the test, the fields expected to be encrypted (for example FirstName of the AuthorEntity) are in plain text!
So I started debugging the code and I realized that the line of code (included in the EncryptionMigrator class) causing the exception to be thrown:
var set = context.Set(entityType);
is never executed!
After some hours of debug I found the problem.
In the Execute method of the MigratorBaseTest class we have the following:
// Feed database with data.
using (var contextFactory = new DatabaseContextFactory(databaseName))
{
await using var context = contextFactory.CreateContext<DatabaseContext>(provider.SourceEncryptionProvider);
await context.Authors.AddRangeAsync(Authors);
await context.SaveChangesAsync();
}
// Process data migration
using (var contextFactory = new DatabaseContextFactory(databaseName))
{
await using var context = contextFactory.CreateContext<DatabaseContext>(provider);
await context.MigrateAsync();
}
// Assert if the context has been decrypted
using (var contextFactory = new DatabaseContextFactory(databaseName))
{
await using var context = contextFactory.CreateContext<DatabaseContext>(provider.DestinationEncryptionProvider);
IEnumerable<AuthorEntity> authors = await context.Authors.Include(x => x.Books).ToListAsync();
foreach (AuthorEntity author in authors)
{
AuthorEntity original = Authors.FirstOrDefault(x => x.UniqueId == author.UniqueId);
AssertAuthor(original, author);
}
}
Here we can see that the db context is created three times but debugging the code I realized that the UseEncryption method is called only the first time:
await using var context = contextFactory.CreateContext<DatabaseContext>(provider.SourceEncryptionProvider);
This is caused by the GetModel method of the class Microsoft.EntityFrameworkCore.Infrastructure.ModelSource
if (!cache.TryGetValue(cacheKey, out IModel model))
{
// call CreateModel
}
return model;
As we can see a cache is used to check if the CreateModel method has to be called.
Since the UseEncryption is called only with the SourceEncryptionProvider, the data are not migrated at all in this test, because in the MigrationAsyncCore method the encrypted properties are obtained by the filter:
var encryptedProperties = entityType.GetProperties()
.Select(p => (property: p, encryptionProvider: (p.GetValueConverter() as IEncryptionValueConverter)?.EncryptionProvider))
.Where(p => p.encryptionProvider is MigrationEncryptionProvider { IsEmpty: false })
.Select(p => p.property)
.ToList();
but they are always 0 because the provider is always the SourceEncryptionProvider that is not of type MigrationEncryptionProvider.
That's why the exception is not thrown durin the execution of this test. The same happens also for the other tests related to the migration.
I'm trying to use the MigrationEncryptionProvider in order to encrypt already existing data that are not encrypted. So I used the following:
Unfortunately the MigrateAsync method throws the following exception:
This is caused by the extension method Set of the DbContext class done in the EncryptionMigrator class:
I thought it was a problam cause by the fact I didn't use properly the MigrationEncryptionProvider, but then I tried to run the unit test MigrateOriginalToEncryptedTest implemented here and I found somethig strange. The test passes but looking at the SQLite db used for the test, the fields expected to be encrypted (for example FirstName of the AuthorEntity) are in plain text! So I started debugging the code and I realized that the line of code (included in the EncryptionMigrator class) causing the exception to be thrown:
is never executed! After some hours of debug I found the problem.
In the Execute method of the MigratorBaseTest class we have the following:
Here we can see that the db context is created three times but debugging the code I realized that the UseEncryption method is called only the first time:
This is caused by the GetModel method of the class Microsoft.EntityFrameworkCore.Infrastructure.ModelSource
As we can see a cache is used to check if the CreateModel method has to be called. Since the UseEncryption is called only with the SourceEncryptionProvider, the data are not migrated at all in this test, because in the MigrationAsyncCore method the encrypted properties are obtained by the filter:
but they are always 0 because the provider is always the SourceEncryptionProvider that is not of type MigrationEncryptionProvider. That's why the exception is not thrown durin the execution of this test. The same happens also for the other tests related to the migration.