Skip to content

Commit f1baf52

Browse files
committed
Fixed decrypt process for byte arrays and improve unit tests
1 parent e82c5be commit f1baf52

File tree

7 files changed

+157
-89
lines changed

7 files changed

+157
-89
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
This library has been developed initialy for a personal project of mine. It provides a simple way to encrypt column data.
1313

14-
I **do not** take responsability if you use this in a production environment and loose your encryption key.
14+
I **do not** take responsability if you use this in a production environment and loose your encryption key or corrupt your data.
1515

1616
## How to install
1717

src/EntityFrameworkCore.DataEncryption/EntityFrameworkCore.DataEncryption.csproj

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
<GenerateDocumentationFile>true</GenerateDocumentationFile>
1818
<PackageTags>entity-framework-core, extensions, dotnet-core, dotnet, encryption, fluent-api</PackageTags>
1919
<PackageIcon>icon.png</PackageIcon>
20-
<Copyright>Filipe GOMES PEIXOTO © 2019 - 2022</Copyright>
20+
<Copyright>Filipe GOMES PEIXOTO © 2019 - 2023</Copyright>
2121
<Description>A plugin for Microsoft.EntityFrameworkCore to add support of encrypted fields using built-in or custom encryption providers.</Description>
2222
<PackageLicenseFile>LICENSE</PackageLicenseFile>
23-
<PackageReleaseNotes>Add support for .NET 5 and .NET 6</PackageReleaseNotes>
23+
<PackageReleaseNotes>https://github.com/Eastrall/EntityFrameworkCore.DataEncryption/releases/tag/v4.0.0</PackageReleaseNotes>
2424
<PackageReadmeFile>README.md</PackageReadmeFile>
2525
</PropertyGroup>
2626

@@ -34,11 +34,8 @@
3434
<ItemGroup Condition="('$(TargetFramework)' == 'netcoreapp3.1')">
3535
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="[3.1,6)" />
3636
</ItemGroup>
37-
<ItemGroup Condition="('$(TargetFramework)' == 'netstandard2.1')">
38-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="[5,)" />
39-
</ItemGroup>
4037
<ItemGroup Condition="('$(TargetFramework)' == 'net6.0')">
41-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="[6,)" />
38+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="[6,7)" />
4239
</ItemGroup>
4340
<ItemGroup Condition="('$(TargetFramework)' == 'net7.0')">
4441
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="[7,)" />

src/EntityFrameworkCore.DataEncryption/Providers/AesProvider.cs

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.IO;
1+
using System;
2+
using System.IO;
23
using System.Security.Cryptography;
34

45
namespace Microsoft.EntityFrameworkCore.DataEncryption.Providers;
@@ -19,9 +20,9 @@ public class AesProvider : IEncryptionProvider
1920
public const int InitializationVectorSize = 16;
2021

2122
private readonly byte[] _key;
23+
private readonly byte[] _iv;
2224
private readonly CipherMode _mode;
2325
private readonly PaddingMode _padding;
24-
private readonly byte[] _iv;
2526

2627
/// <summary>
2728
/// Creates a new <see cref="AesProvider"/> instance used to perform symmetric encryption and decryption on strings.
@@ -32,8 +33,8 @@ public class AesProvider : IEncryptionProvider
3233
/// <param name="padding">Padding mode used in the symmetric encryption.</param>
3334
public AesProvider(byte[] key, byte[] initializationVector, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
3435
{
35-
_key = key;
36-
_iv = initializationVector;
36+
_key = key ?? throw new ArgumentNullException(nameof(key), "");
37+
_iv = initializationVector ?? throw new ArgumentNullException(nameof(initializationVector), "");
3738
_mode = mode;
3839
_padding = padding;
3940
}
@@ -46,25 +47,16 @@ public byte[] Encrypt(byte[] input)
4647
return null;
4748
}
4849

49-
using var aes = CreateCryptographyProvider(_key, _mode, _padding);
50-
using var memoryStream = new MemoryStream();
51-
52-
byte[] initializationVector = _iv;
53-
if (initializationVector is null)
54-
{
55-
aes.GenerateIV();
56-
initializationVector = aes.IV;
57-
memoryStream.Write(initializationVector, 0, initializationVector.Length);
58-
}
50+
using Aes aes = CreateCryptographyProvider(_key, _iv, _mode, _padding);
51+
using ICryptoTransform transform = aes.CreateEncryptor(aes.Key, aes.IV);
52+
using MemoryStream memoryStream = new();
53+
using CryptoStream cryptoStream = new(memoryStream, transform, CryptoStreamMode.Write);
5954

60-
using var transform = aes.CreateEncryptor(_key, initializationVector);
61-
using var crypto = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write);
62-
crypto.Write(input, 0, input.Length);
63-
crypto.FlushFinalBlock();
55+
cryptoStream.Write(input, 0, input.Length);
56+
cryptoStream.FlushFinalBlock();
57+
memoryStream.Seek(0L, SeekOrigin.Begin);
6458

65-
memoryStream.Seek(0, SeekOrigin.Begin);
66-
67-
return memoryStream.ToArray();
59+
return StreamToBytes(memoryStream);
6860
}
6961

7062
/// <inheritdoc />
@@ -75,39 +67,46 @@ public byte[] Decrypt(byte[] input)
7567
return null;
7668
}
7769

78-
using var memoryStream = new MemoryStream(input);
70+
using Aes aes = CreateCryptographyProvider(_key, _iv, _mode, _padding);
71+
using ICryptoTransform transform = aes.CreateDecryptor(aes.Key, aes.IV);
72+
using MemoryStream memoryStream = new(input);
73+
using CryptoStream cryptoStream = new(memoryStream, transform, CryptoStreamMode.Read);
74+
75+
return StreamToBytes(cryptoStream);
76+
}
7977

80-
byte[] initializationVector = _iv;
81-
if (initializationVector is null)
78+
/// <summary>
79+
/// Converts a <see cref="Stream"/> into a byte array.
80+
/// </summary>
81+
/// <param name="stream">Stream.</param>
82+
/// <returns>The stream's content as a byte array.</returns>
83+
internal static byte[] StreamToBytes(Stream stream)
84+
{
85+
if (stream is MemoryStream ms)
8286
{
83-
initializationVector = new byte[InitializationVectorSize];
84-
memoryStream.Read(initializationVector, 0, initializationVector.Length);
87+
return ms.ToArray();
8588
}
8689

87-
using var aes = CreateCryptographyProvider(_key, _mode, _padding);
88-
using var transform = aes.CreateDecryptor(_key, initializationVector);
89-
90-
using var outputStream = new MemoryStream();
91-
using var crypto = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read);
92-
93-
crypto.CopyTo(outputStream);
94-
95-
return outputStream.ToArray();
90+
using var output = new MemoryStream();
91+
stream.CopyTo(output);
92+
return output.ToArray();
9693
}
9794

9895
/// <summary>
9996
/// Generates an AES cryptography provider.
10097
/// </summary>
10198
/// <returns></returns>
102-
private static Aes CreateCryptographyProvider(byte[] key, CipherMode mode, PaddingMode padding)
99+
private static Aes CreateCryptographyProvider(byte[] key, byte[] iv, CipherMode mode, PaddingMode padding)
103100
{
104101
var aes = Aes.Create();
105102

106-
aes.BlockSize = AesBlockSize;
107103
aes.Mode = mode;
104+
aes.KeySize = key.Length * 8;
105+
aes.BlockSize = AesBlockSize;
106+
aes.FeedbackSize = AesBlockSize;
108107
aes.Padding = padding;
109108
aes.Key = key;
110-
aes.KeySize = key.Length * 8;
109+
aes.IV = iv;
111110

112111
return aes;
113112
}

test/EntityFrameworkCore.DataEncryption.Test/Context/AuthorEntity.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.Collections.Generic;
33
using System.ComponentModel.DataAnnotations;
44
using System.ComponentModel.DataAnnotations.Schema;
5-
using System.Security;
65

76
namespace Microsoft.EntityFrameworkCore.DataEncryption.Test.Context;
87

@@ -19,14 +18,13 @@ public sealed class AuthorEntity
1918
public string FirstName { get; set; }
2019

2120
[Required]
22-
[Encrypted]
21+
[Encrypted(StorageFormat.Binary)]
22+
[Column(TypeName = "BLOB")]
2323
public string LastName { get; set; }
2424

2525
[Required]
2626
public int Age { get; set; }
2727

28-
//public SecureString Password { get; set; }
29-
3028
public IList<BookEntity> Books { get; set; }
3129

3230
public AuthorEntity(string firstName, string lastName, int age)

test/EntityFrameworkCore.DataEncryption.Test/Context/BookEntity.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,16 @@ public sealed class BookEntity
2929
[Column(TypeName = "TEXT")]
3030
public byte[] Content { get; set; }
3131

32-
public BookEntity(string name, int numberOfPages)
32+
[Encrypted]
33+
[Column(TypeName = "BLOB")]
34+
public byte[] Summary { get; set; }
35+
36+
public BookEntity(string name, int numberOfPages, byte[] content, byte[] summary)
3337
{
3438
Name = name;
3539
NumberOfPages = numberOfPages;
3640
UniqueId = Guid.NewGuid();
41+
Content = content;
42+
Summary = summary;
3743
}
3844
}

test/EntityFrameworkCore.DataEncryption.Test/PropertyBuilderExtensionsTest.cs

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
using Microsoft.EntityFrameworkCore.DataEncryption;
1+
using Bogus;
2+
using Microsoft.EntityFrameworkCore.DataEncryption;
23
using Microsoft.EntityFrameworkCore.DataEncryption.Internal;
34
using Microsoft.EntityFrameworkCore.DataEncryption.Providers;
45
using Microsoft.EntityFrameworkCore.DataEncryption.Test.Context;
56
using Microsoft.EntityFrameworkCore.Infrastructure;
67
using Microsoft.EntityFrameworkCore.Metadata;
78
using System;
89
using System.ComponentModel.DataAnnotations;
10+
using System.Linq;
911
using Xunit;
1012

1113
namespace Microsoft.EntityFrameworkCore.Encryption.Test;
1214

1315
public class PropertyBuilderExtensionsTest
1416
{
17+
private static readonly Faker _faker = new();
18+
1519
[Fact]
1620
public void PropertyBuilderShouldNeverBeNullTest()
1721
{
@@ -24,29 +28,84 @@ public void PropertyShouldHaveEncryptionAnnotationsTest()
2428
AesKeyInfo encryptionKeyInfo = AesProvider.GenerateKey(AesKeySize.AES256Bits);
2529
var provider = new AesProvider(encryptionKeyInfo.Key, encryptionKeyInfo.IV);
2630

31+
string name = _faker.Name.FullName();
32+
byte[] bytes = _faker.Random.Bytes(_faker.Random.Int(10, 30));
33+
34+
UserEntity user = new()
35+
{
36+
Name = name,
37+
NameAsBytes = name,
38+
ExtraData = bytes,
39+
ExtraDataAsBytes = bytes
40+
};
41+
2742
using var contextFactory = new DatabaseContextFactory();
28-
using var context = contextFactory.CreateContext<FluentDbContext>(provider);
29-
Assert.NotNull(context);
43+
using (var context = contextFactory.CreateContext<FluentDbContext>(provider))
44+
{
45+
Assert.NotNull(context);
46+
47+
IEntityType entityType = context.GetUserEntityType();
48+
Assert.NotNull(entityType);
3049

31-
IEntityType entityType = context.GetUserEntityType();
32-
Assert.NotNull(entityType);
50+
AssertPropertyAnnotations(entityType.GetProperty(nameof(UserEntity.Name)), true, StorageFormat.Default);
51+
AssertPropertyAnnotations(entityType.GetProperty(nameof(UserEntity.NameAsBytes)), true, StorageFormat.Binary);
52+
AssertPropertyAnnotations(entityType.GetProperty(nameof(UserEntity.ExtraData)), true, StorageFormat.Base64);
53+
AssertPropertyAnnotations(entityType.GetProperty(nameof(UserEntity.ExtraDataAsBytes)), true, StorageFormat.Binary);
54+
AssertPropertyAnnotations(entityType.GetProperty(nameof(UserEntity.Id)), false, StorageFormat.Default);
3355

34-
IProperty property = entityType.GetProperty("Name");
56+
context.Users.Add(user);
57+
context.SaveChanges();
58+
}
59+
60+
using (var context = contextFactory.CreateContext<FluentDbContext>(provider))
61+
{
62+
UserEntity u = context.Users.First();
63+
64+
Assert.NotNull(u);
65+
Assert.Equal(name, u.Name);
66+
Assert.Equal(name, u.NameAsBytes);
67+
Assert.Equal(bytes, u.ExtraData);
68+
Assert.Equal(bytes, u.ExtraDataAsBytes);
69+
}
70+
}
71+
72+
private static void AssertPropertyAnnotations(IProperty property, bool shouldBeEncrypted, StorageFormat expectedStorageFormat)
73+
{
3574
Assert.NotNull(property);
3675

3776
IAnnotation encryptedAnnotation = property.FindAnnotation(PropertyAnnotations.IsEncrypted);
38-
IAnnotation formatAnnotation = property.FindAnnotation(PropertyAnnotations.StorageFormat);
39-
Assert.NotNull(encryptedAnnotation);
40-
Assert.True((bool)encryptedAnnotation.Value);
41-
Assert.NotNull(formatAnnotation);
42-
Assert.Equal(StorageFormat.Default, formatAnnotation.Value);
77+
78+
if (shouldBeEncrypted)
79+
{
80+
Assert.NotNull(encryptedAnnotation);
81+
Assert.True((bool)encryptedAnnotation.Value);
82+
83+
IAnnotation formatAnnotation = property.FindAnnotation(PropertyAnnotations.StorageFormat);
84+
Assert.NotNull(formatAnnotation);
85+
Assert.Equal(expectedStorageFormat, formatAnnotation.Value);
86+
}
87+
else
88+
{
89+
Assert.Null(encryptedAnnotation);
90+
Assert.Null(property.FindAnnotation(PropertyAnnotations.StorageFormat));
91+
}
4392
}
4493

4594
private class UserEntity
4695
{
4796
public int Id { get; set; }
4897

98+
// Encrypted as default (Base64)
4999
public string Name { get; set; }
100+
101+
// Encrypted as raw byte array.
102+
public string NameAsBytes { get; set; }
103+
104+
// Encrypted as Base64 string
105+
public byte[] ExtraData { get; set; }
106+
107+
// Encrypted as raw byte array.
108+
public byte[] ExtraDataAsBytes { get; set; }
50109
}
51110

52111
private class FluentDbContext : DbContext
@@ -72,6 +131,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
72131
userEntityBuilder.HasKey(x => x.Id);
73132
userEntityBuilder.Property(x => x.Id).IsRequired().ValueGeneratedOnAdd();
74133
userEntityBuilder.Property(x => x.Name).IsRequired().IsEncrypted();
134+
userEntityBuilder.Property(x => x.NameAsBytes).IsRequired().HasColumnType("BLOB").IsEncrypted(StorageFormat.Binary);
135+
userEntityBuilder.Property(x => x.ExtraData).IsRequired().HasColumnType("TEXT").IsEncrypted(StorageFormat.Base64);
136+
userEntityBuilder.Property(x => x.ExtraDataAsBytes).IsRequired().HasColumnType("BLOB").IsEncrypted(StorageFormat.Binary);
75137

76138
modelBuilder.UseEncryption(_encryptionProvider);
77139
}

0 commit comments

Comments
 (0)