From 66fcaf9ebf56afec7f9f936b7458bf34c0c92d0c Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Thu, 13 Dec 2018 11:37:47 +0300 Subject: [PATCH] Resolved #645: Inttroduce IStringEncryptionService --- framework/Volo.Abp.sln | 9 +- .../Encryption/IStringEncryptionService.cs | 31 ++++++ .../Encryption/StringEncryptionOptions.cs | 44 ++++++++ .../Encryption/StringEncryptionService.cs | 103 ++++++++++++++++++ .../Volo.Abp.Security.Tests.csproj | 20 ++++ .../Abp/Security/AbpSecurityTestModule.cs | 13 +++ .../StringEncryptionService_Tests.cs | 26 +++++ 7 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/IStringEncryptionService.cs create mode 100644 framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/StringEncryptionOptions.cs create mode 100644 framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/StringEncryptionService.cs create mode 100644 framework/test/Volo.Abp.Security.Tests/Volo.Abp.Security.Tests.csproj create mode 100644 framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/AbpSecurityTestModule.cs create mode 100644 framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Encryption/StringEncryptionService_Tests.cs diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index d6518a0af3..7cae777e00 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -212,7 +212,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EntityFrameworkCore.PostgreSql", "src\Volo.Abp.EntityFrameworkCore.PostgreSql\Volo.Abp.EntityFrameworkCore.PostgreSql.csproj", "{882E82F1-1A57-4BB9-B126-4CBF700C8F0C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Localization.Abstractions", "src\Volo.Abp.Localization.Abstractions\Volo.Abp.Localization.Abstractions.csproj", "{20513A4E-FAC7-4106-8976-5D79A3BDFED1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Localization.Abstractions", "src\Volo.Abp.Localization.Abstractions\Volo.Abp.Localization.Abstractions.csproj", "{20513A4E-FAC7-4106-8976-5D79A3BDFED1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Security.Tests", "test\Volo.Abp.Security.Tests\Volo.Abp.Security.Tests.csproj", "{7CE07034-7E02-4C78-B981-F1039412CA5E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -604,6 +606,10 @@ Global {20513A4E-FAC7-4106-8976-5D79A3BDFED1}.Debug|Any CPU.Build.0 = Debug|Any CPU {20513A4E-FAC7-4106-8976-5D79A3BDFED1}.Release|Any CPU.ActiveCfg = Release|Any CPU {20513A4E-FAC7-4106-8976-5D79A3BDFED1}.Release|Any CPU.Build.0 = Release|Any CPU + {7CE07034-7E02-4C78-B981-F1039412CA5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CE07034-7E02-4C78-B981-F1039412CA5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CE07034-7E02-4C78-B981-F1039412CA5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CE07034-7E02-4C78-B981-F1039412CA5E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -707,6 +713,7 @@ Global {77A621CF-9562-411B-A707-C7C02CC3B8FA} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {882E82F1-1A57-4BB9-B126-4CBF700C8F0C} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {20513A4E-FAC7-4106-8976-5D79A3BDFED1} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {7CE07034-7E02-4C78-B981-F1039412CA5E} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/IStringEncryptionService.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/IStringEncryptionService.cs new file mode 100644 index 0000000000..bad4e33251 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/IStringEncryptionService.cs @@ -0,0 +1,31 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.Security.Encryption +{ + /// + /// Can be used to simply encrypt/decrypt texts. + /// Use to configure default values. + /// + public interface IStringEncryptionService + { + /// + /// Encrypts a text. + /// + /// The text in plain format + /// A phrase to use as the encryption key (optional, uses default if not provided) + /// Salt value (optional, uses default if not provided) + /// Enrypted text + [CanBeNull] + string Encrypt([CanBeNull] string plainText, string passPhrase = null, byte[] salt = null); + + /// + /// Decrypts a text that is encrypted by the method. + /// + /// The text in encrypted format + /// A phrase to use as the encryption key (optional, uses default if not provided) + /// Salt value (optional, uses default if not provided) + /// Decrypted text + [CanBeNull] + string Decrypt([CanBeNull] string cipherText, string passPhrase = null, byte[] salt = null); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/StringEncryptionOptions.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/StringEncryptionOptions.cs new file mode 100644 index 0000000000..91768d37e8 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/StringEncryptionOptions.cs @@ -0,0 +1,44 @@ +using System.Text; + +namespace Volo.Abp.Security.Encryption +{ + /// + /// Options used by . + /// + public class StringEncryptionOptions + { + /// + /// This constant is used to determine the keysize of the encryption algorithm. + /// Default value: 256. + /// + public int Keysize { get; set; } + + /// + /// Default password to encrypt/decrypt texts. + /// It's recommented to set to another value for security. + /// Default value: "gsKnGZ041HLL4IM8" + /// + public string DefaultPassPhrase { get; set; } + + /// + /// This constant string is used as a "salt" value for the PasswordDeriveBytes function calls. + /// This size of the IV (in bytes) must = (keysize / 8). Default keysize is 256, so the IV must be + /// 32 bytes long. Using a 16 character string here gives us 32 bytes when converted to a byte array. + /// Default value: Encoding.ASCII.GetBytes("jkE49230Tf093b42") + /// + public byte[] InitVectorBytes { get; set; } + + /// + /// Default value: Encoding.ASCII.GetBytes("hgt!16kl") + /// + public byte[] DefaultSalt { get; set; } + + public StringEncryptionOptions() + { + Keysize = 256; + DefaultPassPhrase = "gsKnGZ041HLL4IM8"; + InitVectorBytes = Encoding.ASCII.GetBytes("jkE49230Tf093b42"); + DefaultSalt = Encoding.ASCII.GetBytes("hgt!16kl"); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/StringEncryptionService.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/StringEncryptionService.cs new file mode 100644 index 0000000000..1d235d1d84 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/StringEncryptionService.cs @@ -0,0 +1,103 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Security.Encryption +{ + /// + /// Can be used to simply encrypt/decrypt texts. + /// + public class StringEncryptionService : IStringEncryptionService, ITransientDependency + { + protected StringEncryptionOptions Options { get; } + + public StringEncryptionService(IOptions options) + { + Options = options.Value; + } + + public virtual string Encrypt(string plainText, string passPhrase = null, byte[] salt = null) + { + if (plainText == null) + { + return null; + } + + if (passPhrase == null) + { + passPhrase = Options.DefaultPassPhrase; + } + + if (salt == null) + { + salt = Options.DefaultSalt; + } + + var plainTextBytes = Encoding.UTF8.GetBytes(plainText); + using (var password = new Rfc2898DeriveBytes(passPhrase, salt)) + { + var keyBytes = password.GetBytes(Options.Keysize / 8); + using (var symmetricKey = Aes.Create()) + { + symmetricKey.Mode = CipherMode.CBC; + using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, Options.InitVectorBytes)) + { + using (var memoryStream = new MemoryStream()) + { + using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) + { + cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); + cryptoStream.FlushFinalBlock(); + var cipherTextBytes = memoryStream.ToArray(); + return Convert.ToBase64String(cipherTextBytes); + } + } + } + } + } + } + + public virtual string Decrypt(string cipherText, string passPhrase = null, byte[] salt = null) + { + if (string.IsNullOrEmpty(cipherText)) + { + return null; + } + + if (passPhrase == null) + { + passPhrase = Options.DefaultPassPhrase; + } + + if (salt == null) + { + salt = Options.DefaultSalt; + } + + var cipherTextBytes = Convert.FromBase64String(cipherText); + using (var password = new Rfc2898DeriveBytes(passPhrase, salt)) + { + var keyBytes = password.GetBytes(Options.Keysize / 8); + using (var symmetricKey = Aes.Create()) + { + symmetricKey.Mode = CipherMode.CBC; + using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, Options.InitVectorBytes)) + { + using (var memoryStream = new MemoryStream(cipherTextBytes)) + { + using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) + { + var plainTextBytes = new byte[cipherTextBytes.Length]; + var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); + return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Security.Tests/Volo.Abp.Security.Tests.csproj b/framework/test/Volo.Abp.Security.Tests/Volo.Abp.Security.Tests.csproj new file mode 100644 index 0000000000..e2f96d8223 --- /dev/null +++ b/framework/test/Volo.Abp.Security.Tests/Volo.Abp.Security.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.1 + Volo.Abp.Security.Tests + Volo.Abp.Security.Tests + true + false + false + false + + + + + + + + + + diff --git a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/AbpSecurityTestModule.cs b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/AbpSecurityTestModule.cs new file mode 100644 index 0000000000..b71a0a214a --- /dev/null +++ b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/AbpSecurityTestModule.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.Security +{ + [DependsOn( + typeof(AbpSecurityModule), + typeof(AbpTestBaseModule) + )] + public class AbpSecurityTestModule : AbpModule + { + + } +} diff --git a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Encryption/StringEncryptionService_Tests.cs b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Encryption/StringEncryptionService_Tests.cs new file mode 100644 index 0000000000..455e0863bb --- /dev/null +++ b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/Encryption/StringEncryptionService_Tests.cs @@ -0,0 +1,26 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.Security.Encryption +{ + public class StringEncryptionService_Tests : AbpIntegratedTest + { + private readonly IStringEncryptionService _stringEncryptionService; + + public StringEncryptionService_Tests() + { + _stringEncryptionService = GetRequiredService(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("This is a plain text!")] + public void Should_Enrypt_And_Decrpyt_With_Default_Options(string plainText) + { + _stringEncryptionService + .Decrypt(_stringEncryptionService.Encrypt(plainText)) + .ShouldBe(plainText); + } + } +}