--HG--
branch : 1.x
This commit is contained in:
bertrandleroy
2011-01-13 16:08:34 -08:00
12 changed files with 168 additions and 86 deletions

View File

@@ -8,3 +8,6 @@ a6b8d094848d4efee67c787e52e5f7d358e3f0c1 0.5
083d09cd94a7c9175272fba453a156673be9db78 0.8 083d09cd94a7c9175272fba453a156673be9db78 0.8
523f3564d2c053ff068970eab8c64eaf74e3dcec perf baseline 523f3564d2c053ff068970eab8c64eaf74e3dcec perf baseline
d2cca954cbf94750946a6325e5ec23e267430bd2 perf snapshot 1 d2cca954cbf94750946a6325e5ec23e267430bd2 perf snapshot 1
83536b451ffe01a2d94867894f2b284cea2e68aa 1.0
83536b451ffe01a2d94867894f2b284cea2e68aa 1.0
5cc01ad95f11fcd241163685effaf01ef4b6cb02 1.0

View File

@@ -99,8 +99,11 @@ namespace Orchard.Azure.Environment.Configuration {
case "EncryptionKey": case "EncryptionKey":
shellSettings.EncryptionKey = value; shellSettings.EncryptionKey = value;
break; break;
case "EncryptionIV": case "HashAlgorithm":
shellSettings.EncryptionIV = value; shellSettings.HashAlgorithm = value;
break;
case "HashKey":
shellSettings.HashKey = value;
break; break;
} }
} }
@@ -113,7 +116,7 @@ namespace Orchard.Azure.Environment.Configuration {
if (settings == null) if (settings == null)
return ""; return "";
return string.Format("Name: {0}\r\nDataProvider: {1}\r\nDataConnectionString: {2}\r\nDataPrefix: {3}\r\nRequestUrlHost: {4}\r\nRequestUrlPrefix: {5}\r\nState: {6}\r\nEncryptionAlgorithm: {7}\r\nEncryptionKey: {8}\r\nEncryptionIV: {9}\r\n", return string.Format("Name: {0}\r\nDataProvider: {1}\r\nDataConnectionString: {2}\r\nDataPrefix: {3}\r\nRequestUrlHost: {4}\r\nRequestUrlPrefix: {5}\r\nState: {6}\r\nEncryptionAlgorithm: {7}\r\nEncryptionKey: {8}\r\nHashAlgorithm: {9}\r\nHashKey: {10}\r\n",
settings.Name, settings.Name,
settings.DataProvider, settings.DataProvider,
settings.DataConnectionString ?? EmptyValue, settings.DataConnectionString ?? EmptyValue,
@@ -123,7 +126,8 @@ namespace Orchard.Azure.Environment.Configuration {
settings.State != null ? settings.State.ToString() : String.Empty, settings.State != null ? settings.State.ToString() : String.Empty,
settings.EncryptionAlgorithm ?? EmptyValue, settings.EncryptionAlgorithm ?? EmptyValue,
settings.EncryptionKey ?? EmptyValue, settings.EncryptionKey ?? EmptyValue,
settings.EncryptionIV ?? EmptyValue settings.HashAlgorithm ?? EmptyValue,
settings.HashKey ?? EmptyValue
); );
} }
} }

View File

@@ -5,22 +5,20 @@ using Orchard.Utility.Extensions;
namespace Orchard.Tests.Modules.Users { namespace Orchard.Tests.Modules.Users {
public class ShellSettingsUtility { public class ShellSettingsUtility {
public static ShellSettings CreateEncryptionEnabled() { public static ShellSettings CreateEncryptionEnabled() {
// generate random keys for encryption
var key = new byte[32]; const string encryptionAlgorithm = "AES";
var iv = new byte[16]; const string hashAlgorithm = "HMACSHA256";
using ( var random = new RNGCryptoServiceProvider() ) {
random.GetBytes(key);
random.GetBytes(iv);
}
return new ShellSettings { return new ShellSettings {
Name = "Alpha", Name = "Alpha",
RequestUrlHost = "wiki.example.com", RequestUrlHost = "wiki.example.com",
RequestUrlPrefix = "~/foo", RequestUrlPrefix = "~/foo",
EncryptionAlgorithm = "AES", EncryptionAlgorithm = encryptionAlgorithm,
EncryptionKey = key.ToHexString(), EncryptionKey = SymmetricAlgorithm.Create(encryptionAlgorithm).Key.ToHexString(),
EncryptionIV = iv.ToHexString() HashAlgorithm = hashAlgorithm,
HashKey = HMAC.Create(hashAlgorithm).Key.ToHexString()
}; };
} }
} }
} }

View File

@@ -70,7 +70,7 @@ namespace Orchard.Tests.Environment.Configuration {
[Test] [Test]
public void EncryptionSettingsAreStoredAndReadable() { public void EncryptionSettingsAreStoredAndReadable() {
IShellSettingsManager loader = new ShellSettingsManager(_appDataFolder, new Mock<IShellSettingsManagerEventHandler>().Object); IShellSettingsManager loader = new ShellSettingsManager(_appDataFolder, new Mock<IShellSettingsManagerEventHandler>().Object);
var foo = new ShellSettings { Name = "Foo", DataProvider = "Bar", DataConnectionString = "Quux", EncryptionAlgorithm = "AES", EncryptionKey = "ABCDEFG", EncryptionIV= "HIJKL" }; var foo = new ShellSettings { Name = "Foo", DataProvider = "Bar", DataConnectionString = "Quux", EncryptionAlgorithm = "AES", EncryptionKey = "ABCDEFG", HashAlgorithm = "HMACSHA256", HashKey = "HIJKLMN" };
loader.SaveSettings(foo); loader.SaveSettings(foo);
Assert.That(loader.LoadSettings().Count(), Is.EqualTo(1)); Assert.That(loader.LoadSettings().Count(), Is.EqualTo(1));
@@ -78,7 +78,8 @@ namespace Orchard.Tests.Environment.Configuration {
Assert.That(settings.EncryptionAlgorithm, Is.EqualTo("AES")); Assert.That(settings.EncryptionAlgorithm, Is.EqualTo("AES"));
Assert.That(settings.EncryptionKey, Is.EqualTo("ABCDEFG")); Assert.That(settings.EncryptionKey, Is.EqualTo("ABCDEFG"));
Assert.That(settings.EncryptionIV, Is.EqualTo("HIJKL")); Assert.That(settings.HashAlgorithm, Is.EqualTo("HMACSHA256"));
Assert.That(settings.HashKey, Is.EqualTo("HIJKLMN"));
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Security.Cryptography; using System;
using System.Security.Cryptography;
using System.Text; using System.Text;
using Autofac; using Autofac;
using NUnit.Framework; using NUnit.Framework;
@@ -10,36 +11,33 @@ using Orchard.Utility.Extensions;
namespace Orchard.Tests.Security { namespace Orchard.Tests.Security {
[TestFixture] [TestFixture]
public class DefaultEncryptionServiceTests { public class DefaultEncryptionServiceTests {
private IContainer container; private IContainer _container;
[SetUp] [SetUp]
public void Init() { public void Init() {
var key = new byte[32]; const string encryptionAlgorithm = "AES";
var iv = new byte[16]; const string hashAlgorithm = "HMACSHA256";
using ( var random = new RNGCryptoServiceProvider() ) {
random.GetBytes(key);
random.GetBytes(iv);
}
var shellSettings = new ShellSettings { var shellSettings = new ShellSettings {
Name = "Foo", Name = "Foo",
DataProvider = "Bar", DataProvider = "Bar",
DataConnectionString = "Quux", DataConnectionString = "Quux",
EncryptionAlgorithm = "AES", EncryptionAlgorithm = encryptionAlgorithm,
EncryptionKey = key.ToHexString(), EncryptionKey = SymmetricAlgorithm.Create(encryptionAlgorithm).Key.ToHexString(),
EncryptionIV = iv.ToHexString() HashAlgorithm = hashAlgorithm,
HashKey = HMAC.Create(hashAlgorithm).Key.ToHexString()
}; };
var builder = new ContainerBuilder(); var builder = new ContainerBuilder();
builder.RegisterInstance(shellSettings); builder.RegisterInstance(shellSettings);
builder.RegisterType<DefaultEncryptionService>().As<IEncryptionService>(); builder.RegisterType<DefaultEncryptionService>().As<IEncryptionService>();
container = builder.Build(); _container = builder.Build();
} }
[Test] [Test]
public void CanEncodeAndDecodeData() { public void CanEncodeAndDecodeData() {
var encryptionService = container.Resolve<IEncryptionService>(); var encryptionService = _container.Resolve<IEncryptionService>();
var secretData = Encoding.Unicode.GetBytes("this is secret data"); var secretData = Encoding.Unicode.GetBytes("this is secret data");
var encrypted = encryptionService.Encode(secretData); var encrypted = encryptionService.Encode(secretData);
@@ -49,5 +47,42 @@ namespace Orchard.Tests.Security {
Assert.That(decrypted, Is.EqualTo(secretData)); Assert.That(decrypted, Is.EqualTo(secretData));
} }
[Test]
public void ShouldDetectTamperedData() {
var encryptionService = _container.Resolve<IEncryptionService>();
var secretData = Encoding.Unicode.GetBytes("this is secret data");
var encrypted = encryptionService.Encode(secretData);
try {
// tamper the data
encrypted[encrypted.Length - 1] ^= 66;
var decrypted = encryptionService.Decode(encrypted);
}
catch {
return;
}
Assert.Fail();
}
[Test]
public void SuccessiveEncodeCallsShouldNotReturnTheSameData() {
var encryptionService = _container.Resolve<IEncryptionService>();
var secretData = Encoding.Unicode.GetBytes("this is secret data");
byte[] previousEncrypted = null;
for (int i = 0; i < 10; i++) {
var encrypted = encryptionService.Encode(secretData);
var decrypted = encryptionService.Decode(encrypted);
Assert.That(encrypted, Is.Not.EqualTo(decrypted));
Assert.That(decrypted, Is.EqualTo(secretData));
if(previousEncrypted != null) {
Assert.That(encrypted, Is.Not.EqualTo(previousEncrypted));
}
previousEncrypted = encrypted;
}
}
} }
} }

View File

@@ -79,7 +79,8 @@ namespace Orchard.Blogs.Controllers {
if (!ModelState.IsValid) { if (!ModelState.IsValid) {
Services.TransactionManager.Cancel(); Services.TransactionManager.Cancel();
return View(model); // Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
return View((object)model);
} }
conditionallyPublish(blogPost.ContentItem); conditionallyPublish(blogPost.ContentItem);
@@ -151,7 +152,8 @@ namespace Orchard.Blogs.Controllers {
var model = Services.ContentManager.UpdateEditor(blogPost, this); var model = Services.ContentManager.UpdateEditor(blogPost, this);
if (!ModelState.IsValid) { if (!ModelState.IsValid) {
Services.TransactionManager.Cancel(); Services.TransactionManager.Cancel();
return View(model); // Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
return View((object)model);
} }
conditionallyPublish(blogPost.ContentItem); conditionallyPublish(blogPost.ContentItem);

View File

@@ -72,7 +72,8 @@ namespace Orchard.Experimental.Controllers {
// get at the first input? // get at the first input?
model.Fieldsets[0][0].Attributes(new {autofocus = "autofocus"}); // <-- could be applied by some other behavior - need to be able to modify attributes instead of clobbering them like this model.Fieldsets[0][0].Attributes(new {autofocus = "autofocus"}); // <-- could be applied by some other behavior - need to be able to modify attributes instead of clobbering them like this
return View(model); // Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
return View((object)model);
} }
[HttpPost, ActionName("FormShapes")] [HttpPost, ActionName("FormShapes")]
@@ -104,7 +105,8 @@ namespace Orchard.Experimental.Controllers {
ViewBag.Page.Messages.Add(new HtmlString("<hr/>abuse<hr/>")); ViewBag.Page.Messages.Add(new HtmlString("<hr/>abuse<hr/>"));
ViewBag.Page.Messages.Add("<hr/>encoded<hr/>"); ViewBag.Page.Messages.Add("<hr/>encoded<hr/>");
return View(model); // Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
return View((object)model);
} }
public static string Break(dynamic view) { public static string Break(dynamic view) {

View File

@@ -126,17 +126,14 @@ namespace Orchard.Setup.Services {
#region Encryption Settings #region Encryption Settings
// generate random keys for encryption
var key = new byte[32];
var iv = new byte[16];
using ( var random = new RNGCryptoServiceProvider() ) {
random.GetBytes(key);
random.GetBytes(iv);
}
shellSettings.EncryptionAlgorithm = "AES"; shellSettings.EncryptionAlgorithm = "AES";
shellSettings.EncryptionKey = key.ToHexString(); // randomly generated key
shellSettings.EncryptionIV = iv.ToHexString(); shellSettings.EncryptionKey = SymmetricAlgorithm.Create(shellSettings.EncryptionAlgorithm).Key.ToHexString();
shellSettings.HashAlgorithm = "HMACSHA256";
// randomly generated key
shellSettings.HashKey = HMAC.Create(shellSettings.HashAlgorithm).Key.ToHexString();
#endregion #endregion
var shellDescriptor = new ShellDescriptor { var shellDescriptor = new ShellDescriptor {

View File

@@ -134,7 +134,8 @@ namespace Orchard.Widgets.Controllers {
var model = Services.ContentManager.UpdateEditor(widgetPart, this); var model = Services.ContentManager.UpdateEditor(widgetPart, this);
if (!ModelState.IsValid) { if (!ModelState.IsValid) {
Services.TransactionManager.Cancel(); Services.TransactionManager.Cancel();
return View(model); // Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
return View((object)model);
} }
Services.Notifier.Information(T("Your {0} has been created.", widgetPart.TypeDefinition.DisplayName)); Services.Notifier.Information(T("Your {0} has been created.", widgetPart.TypeDefinition.DisplayName));
@@ -179,7 +180,8 @@ namespace Orchard.Widgets.Controllers {
if (!ModelState.IsValid) { if (!ModelState.IsValid) {
Services.TransactionManager.Cancel(); Services.TransactionManager.Cancel();
return View(model); // Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
return View((object)model);
} }
Services.Notifier.Information(T("Your {0} has been created.", layerPart.TypeDefinition.DisplayName)); Services.Notifier.Information(T("Your {0} has been created.", layerPart.TypeDefinition.DisplayName));
@@ -226,7 +228,8 @@ namespace Orchard.Widgets.Controllers {
if (!ModelState.IsValid) { if (!ModelState.IsValid) {
Services.TransactionManager.Cancel(); Services.TransactionManager.Cancel();
return View(model); // Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
return View((object)model);
} }
Services.Notifier.Information(T("Your {0} has been saved.", layerPart.TypeDefinition.DisplayName)); Services.Notifier.Information(T("Your {0} has been saved.", layerPart.TypeDefinition.DisplayName));
@@ -297,7 +300,8 @@ namespace Orchard.Widgets.Controllers {
var model = Services.ContentManager.UpdateEditor(widgetPart, this); var model = Services.ContentManager.UpdateEditor(widgetPart, this);
if (!ModelState.IsValid) { if (!ModelState.IsValid) {
Services.TransactionManager.Cancel(); Services.TransactionManager.Cancel();
return View(model); // Casting to avoid invalid (under medium trust) reflection over the protected View method and force a static invocation.
return View((object)model);
} }
Services.Notifier.Information(T("Your {0} has been saved.", widgetPart.TypeDefinition.DisplayName)); Services.Notifier.Information(T("Your {0} has been saved.", widgetPart.TypeDefinition.DisplayName));

View File

@@ -18,7 +18,8 @@
RequestUrlPrefix = settings.RequestUrlPrefix; RequestUrlPrefix = settings.RequestUrlPrefix;
EncryptionAlgorithm = settings.EncryptionAlgorithm; EncryptionAlgorithm = settings.EncryptionAlgorithm;
EncryptionKey = settings.EncryptionKey; EncryptionKey = settings.EncryptionKey;
EncryptionIV = settings.EncryptionIV; HashAlgorithm = settings.HashAlgorithm;
HashKey = settings.HashKey;
State = settings.State; State = settings.State;
} }
@@ -33,7 +34,9 @@
public string EncryptionAlgorithm { get; set; } public string EncryptionAlgorithm { get; set; }
public string EncryptionKey { get; set; } public string EncryptionKey { get; set; }
public string EncryptionIV { get; set; }
public string HashAlgorithm { get; set; }
public string HashKey { get; set; }
public TenantState State { get; set; } public TenantState State { get; set; }
} }

View File

@@ -49,15 +49,13 @@ namespace Orchard.Environment.Configuration {
} }
} }
static ShellSettings ParseSettings(string text) static ShellSettings ParseSettings(string text) {
{
var shellSettings = new ShellSettings(); var shellSettings = new ShellSettings();
if (String.IsNullOrEmpty(text)) if (String.IsNullOrEmpty(text))
return shellSettings; return shellSettings;
var settings = text.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); var settings = text.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var setting in settings) foreach (var setting in settings) {
{
var separatorIndex = setting.IndexOf(Separator); var separatorIndex = setting.IndexOf(Separator);
if (separatorIndex == -1) if (separatorIndex == -1)
{ {
@@ -97,8 +95,11 @@ namespace Orchard.Environment.Configuration {
case "EncryptionKey": case "EncryptionKey":
shellSettings.EncryptionKey = value; shellSettings.EncryptionKey = value;
break; break;
case "EncryptionIV": case "HashAlgorithm":
shellSettings.EncryptionIV = value; shellSettings.HashAlgorithm = value;
break;
case "HashKey":
shellSettings.HashKey = value;
break; break;
} }
} }
@@ -107,12 +108,11 @@ namespace Orchard.Environment.Configuration {
return shellSettings; return shellSettings;
} }
static string ComposeSettings(ShellSettings settings) static string ComposeSettings(ShellSettings settings) {
{
if (settings == null) if (settings == null)
return ""; return "";
return string.Format("Name: {0}\r\nDataProvider: {1}\r\nDataConnectionString: {2}\r\nDataPrefix: {3}\r\nRequestUrlHost: {4}\r\nRequestUrlPrefix: {5}\r\nState: {6}\r\nEncryptionAlgorithm: {7}\r\nEncryptionKey: {8}\r\nEncryptionIV: {9}\r\n", return string.Format("Name: {0}\r\nDataProvider: {1}\r\nDataConnectionString: {2}\r\nDataPrefix: {3}\r\nRequestUrlHost: {4}\r\nRequestUrlPrefix: {5}\r\nState: {6}\r\nEncryptionAlgorithm: {7}\r\nEncryptionKey: {8}\r\nHashAlgorithm: {9}\r\nHashKey: {10}\r\n",
settings.Name, settings.Name,
settings.DataProvider, settings.DataProvider,
settings.DataConnectionString ?? EmptyValue, settings.DataConnectionString ?? EmptyValue,
@@ -122,7 +122,8 @@ namespace Orchard.Environment.Configuration {
settings.State != null ? settings.State.ToString() : String.Empty, settings.State != null ? settings.State.ToString() : String.Empty,
settings.EncryptionAlgorithm ?? EmptyValue, settings.EncryptionAlgorithm ?? EmptyValue,
settings.EncryptionKey ?? EmptyValue, settings.EncryptionKey ?? EmptyValue,
settings.EncryptionIV ?? EmptyValue settings.HashAlgorithm ?? EmptyValue,
settings.HashKey ?? EmptyValue
); );
} }
} }

View File

@@ -3,59 +3,91 @@ using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using Orchard.Environment.Configuration; using Orchard.Environment.Configuration;
using Orchard.Utility.Extensions; using Orchard.Utility.Extensions;
using System;
namespace Orchard.Security.Providers { namespace Orchard.Security.Providers {
public class DefaultEncryptionService : IEncryptionService { public class DefaultEncryptionService : IEncryptionService {
private readonly ShellSettings _shellSettings; private readonly ShellSettings _shellSettings;
private const int SaltSize = 16;
public DefaultEncryptionService(ShellSettings shellSettings ) { public DefaultEncryptionService(ShellSettings shellSettings ) {
_shellSettings = shellSettings; _shellSettings = shellSettings;
} }
public byte[] Decode(byte[] encodedData) { public byte[] Decode(byte[] encodedData) {
using ( var ms = new MemoryStream() ) { // extract parts of the encoded data
using (var algorithm = CreateAlgorithm()) { using (var symmetricAlgorithm = CreateSymmetricAlgorithm()) {
using ( var cs = new CryptoStream(ms, algorithm.CreateDecryptor(), CryptoStreamMode.Write) ) { using (var hashAlgorithm = CreateHashAlgorithm()) {
cs.Write(encodedData, 0, encodedData.Length); var iv = new byte[symmetricAlgorithm.BlockSize / 8];
cs.FlushFinalBlock(); var signature = new byte[hashAlgorithm.HashSize / 8];
var data = new byte[encodedData.Length - iv.Length - signature.Length];
Array.Copy(encodedData, 0, iv, 0, iv.Length);
Array.Copy(encodedData, iv.Length, data, 0, data.Length);
Array.Copy(encodedData, iv.Length + data.Length, signature, 0, signature.Length);
// validate the signature
var mac = hashAlgorithm.ComputeHash(iv.Concat(data).ToArray());
if (!mac.SequenceEqual(signature)) {
// message has been tampered
throw new ArgumentException();
} }
// remove the salt part symmetricAlgorithm.IV = iv;
return ms.ToArray().Skip(SaltSize).ToArray();
using (var ms = new MemoryStream()) {
using (var cs = new CryptoStream(ms, symmetricAlgorithm.CreateDecryptor(), CryptoStreamMode.Write)) {
cs.Write(data, 0, data.Length);
cs.FlushFinalBlock();
}
return ms.ToArray();
}
} }
} }
} }
public byte[] Encode(byte[] data) { public byte[] Encode(byte[] data) {
var salt = new byte[SaltSize]; // cipherText ::= IV || ENC(EncryptionKey, IV, plainText) || HMAC(SigningKey, IV || ENC(EncryptionKey, IV, plainText))
byte[] encryptedData;
byte[] iv;
// generate a random salt to happend to encoded data
using ( var random = new RNGCryptoServiceProvider() ) {
random.GetBytes(salt);
}
using ( var ms = new MemoryStream() ) { using ( var ms = new MemoryStream() ) {
using (var algorithm = CreateAlgorithm()) { using (var symmetricAlgorithm = CreateSymmetricAlgorithm()) {
using ( var cs = new CryptoStream(ms, algorithm.CreateEncryptor(), CryptoStreamMode.Write) ) { // generate a new IV each time the Encode is called
// append the salt to the data and encrypt symmetricAlgorithm.GenerateIV();
var salted = salt.Concat(data).ToArray(); iv = symmetricAlgorithm.IV;
cs.Write(salted, 0, salted.Length);
using (var cs = new CryptoStream(ms, symmetricAlgorithm.CreateEncryptor(), CryptoStreamMode.Write)) {
cs.Write(data, 0, data.Length);
cs.FlushFinalBlock(); cs.FlushFinalBlock();
} }
return ms.ToArray(); encryptedData = ms.ToArray();
} }
} }
byte[] signedData;
// signing IV || encrypted data
using (var hashAlgorithm = CreateHashAlgorithm()) {
signedData = hashAlgorithm.ComputeHash(iv.Concat(encryptedData).ToArray());
}
return iv.Concat(encryptedData).Concat(signedData).ToArray();
} }
private SymmetricAlgorithm CreateAlgorithm() { private SymmetricAlgorithm CreateSymmetricAlgorithm() {
var encryptionAlgorithm = SymmetricAlgorithm.Create(_shellSettings.EncryptionAlgorithm); var algorithm = SymmetricAlgorithm.Create(_shellSettings.EncryptionAlgorithm);
algorithm.Key = _shellSettings.EncryptionKey.ToByteArray();
encryptionAlgorithm.Key = _shellSettings.EncryptionKey.ToByteArray(); return algorithm;
encryptionAlgorithm.IV = _shellSettings.EncryptionIV.ToByteArray();
return encryptionAlgorithm;
} }
private HMAC CreateHashAlgorithm() {
var algorithm = HMAC.Create(_shellSettings.HashAlgorithm);
algorithm.Key = _shellSettings.HashKey.ToByteArray();
return algorithm;
}
} }
} }