From 85e8954402bf698ae842c91572968421eabeeb56 Mon Sep 17 00:00:00 2001 From: Kori Francis Date: Wed, 2 Oct 2024 15:30:24 -0400 Subject: [PATCH 1/5] Use a Buffer Pool Instead of allocating new byte arrays each time you perform a cryptographic operation (such as signing, encryption, or converting data to/from Base64), you can use a buffer pool. A buffer pool maintains a set of preallocated buffers that can be reused, reducing the need for frequent allocations. In .NET, the ArrayPool class is ideal for this purpose. --- .../Security/Cryptography/KeyFactory.cs | 42 +++++++++++++++---- .../Standard.Licensing.csproj | 6 +++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/Standard.Licensing/Security/Cryptography/KeyFactory.cs b/src/Standard.Licensing/Security/Cryptography/KeyFactory.cs index f4f01b7..2c0a90d 100644 --- a/src/Standard.Licensing/Security/Cryptography/KeyFactory.cs +++ b/src/Standard.Licensing/Security/Cryptography/KeyFactory.cs @@ -24,10 +24,15 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using Org.BouncyCastle.Asn1.Pkcs; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Security; using Org.BouncyCastle.X509; using System; +using System.Buffers; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + +#if NET6_0_OR_GREATER +using System.Security.Cryptography; // Use native cryptography in .NET 6.0+ +#endif namespace Standard.Licensing.Security.Cryptography { @@ -43,14 +48,33 @@ internal static class KeyFactory /// The encrypted private key. public static string ToEncryptedPrivateKeyString(AsymmetricKeyParameter key, string passPhrase) { - var salt = new byte[16]; - var secureRandom = SecureRandom.GetInstance("SHA256PRNG"); - secureRandom.SetSeed(secureRandom.GenerateSeed(16)); //See Bug #135 - secureRandom.NextBytes(salt); + // Rent a buffer for the salt (16 bytes for the salt) + var salt = ArrayPool.Shared.Rent(16); - return - Convert.ToBase64String(PrivateKeyFactory.EncryptKey(keyEncryptionAlgorithm, passPhrase.ToCharArray(), - salt, 10, key)); + try + { +#if NET6_0_OR_GREATER + // Use built-in cryptography (e.g., RNGCryptoServiceProvider) in .NET 6.0+ + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(salt, 0, 16); + } +#else + // Use BouncyCastle for .NET Standard 2.0 or other versions + var secureRandom = SecureRandom.GetInstance("SHA256PRNG"); + secureRandom.SetSeed(secureRandom.GenerateSeed(16)); // Seed generation + secureRandom.NextBytes(salt, 0, 16); +#endif + // Encrypt the key (BouncyCastle library used for all versions) + var encryptedKey = PrivateKeyFactory.EncryptKey(keyEncryptionAlgorithm, passPhrase.ToCharArray(), salt, 10, key); + + return Convert.ToBase64String(encryptedKey); + } + finally + { + // Return the rented buffer to the pool + ArrayPool.Shared.Return(salt); + } } /// diff --git a/src/Standard.Licensing/Standard.Licensing.csproj b/src/Standard.Licensing/Standard.Licensing.csproj index bfa9104..681cbb7 100644 --- a/src/Standard.Licensing/Standard.Licensing.csproj +++ b/src/Standard.Licensing/Standard.Licensing.csproj @@ -119,4 +119,10 @@ 2.4.0 + + + + 4.5.1 + + From d6d3b7bd2bef8e4b3b77e7b094452e0e52743ccf Mon Sep 17 00:00:00 2001 From: Kori Francis Date: Wed, 2 Oct 2024 15:42:43 -0400 Subject: [PATCH 2/5] Optimized XML parsing and writing The code has been updated to use XmlReader and XmlWriter for efficient XML parsing and writing. This change improves the performance of loading and saving licenses by ignoring unnecessary whitespace during reading, and enabling indentation during writing. --- src/Standard.Licensing/License.cs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Standard.Licensing/License.cs b/src/Standard.Licensing/License.cs index 397e10e..06382cb 100644 --- a/src/Standard.Licensing/License.cs +++ b/src/Standard.Licensing/License.cs @@ -279,7 +279,12 @@ public static License Load(string xmlString) /// A populated from the that contains XML. public static License Load(Stream stream) { - return new License(XElement.Load(stream, LoadOptions.None)); + // Use XmlReader for efficient XML parsing + using (var reader = XmlReader.Create(stream, new XmlReaderSettings { IgnoreWhitespace = true })) + { + var xmlData = XElement.Load(reader); + return new License(xmlData); + } } /// @@ -301,7 +306,9 @@ public static License Load(TextReader reader) /// A populated from the that contains XML. public static License Load(XmlReader reader) { - return new License(XElement.Load(reader, LoadOptions.None)); + // Directly load the XML from the XmlReader + var xmlData = XElement.Load(reader); + return new License(xmlData); } /// @@ -311,7 +318,11 @@ public static License Load(XmlReader reader) /// will be written to. public void Save(Stream stream) { - xmlData.Save(stream); + // Use XmlWriter for efficient XML writing + using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) + { + xmlData.WriteTo(writer); + } } /// @@ -321,7 +332,10 @@ public void Save(Stream stream) /// will be written to. public void Save(TextWriter textWriter) { - xmlData.Save(textWriter); + using (var writer = XmlWriter.Create(textWriter, new XmlWriterSettings { Indent = true })) + { + xmlData.WriteTo(writer); + } } /// From b30cec8b170e9dcc76ef406f516f8e94a843e150 Mon Sep 17 00:00:00 2001 From: Kori Francis Date: Wed, 2 Oct 2024 15:45:59 -0400 Subject: [PATCH 3/5] Optimized XML parsing in License class Introduced a static XmlReaderSettings object with pre-configured settings to optimize XML parsing in the License class. This change reduces unnecessary processing by ignoring whitespace, comments, and processing instructions during XML reading. The new settings are now used when creating an XmlReader instance for loading licenses from a stream. --- src/Standard.Licensing/License.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Standard.Licensing/License.cs b/src/Standard.Licensing/License.cs index 06382cb..ae8d8c7 100644 --- a/src/Standard.Licensing/License.cs +++ b/src/Standard.Licensing/License.cs @@ -42,6 +42,12 @@ public class License { private readonly XElement xmlData; private readonly string signatureAlgorithm = X9ObjectIdentifiers.ECDsaWithSha512.Id; + private static XmlReaderSettings defaultXmlReaderSettings = new XmlReaderSettings + { + IgnoreWhitespace = true, // Ignore unnecessary whitespace nodes + IgnoreComments = true, // Skip comment nodes to reduce processing + IgnoreProcessingInstructions = true // Skip processing instructions + }; /// /// Initializes a new instance of the class. @@ -280,7 +286,7 @@ public static License Load(string xmlString) public static License Load(Stream stream) { // Use XmlReader for efficient XML parsing - using (var reader = XmlReader.Create(stream, new XmlReaderSettings { IgnoreWhitespace = true })) + using (var reader = XmlReader.Create(stream, defaultXmlReaderSettings)) { var xmlData = XElement.Load(reader); return new License(xmlData); From b8e3dbeacd5ef035efe7f6ca126dee7bb814b83d Mon Sep 17 00:00:00 2001 From: Kori Francis Date: Thu, 3 Oct 2024 10:06:04 -0400 Subject: [PATCH 4/5] include buffers --- src/Standard.Licensing.sln | 2 +- src/Standard.Licensing/Standard.Licensing.csproj | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Standard.Licensing.sln b/src/Standard.Licensing.sln index 106691d..4b212ee 100644 --- a/src/Standard.Licensing.sln +++ b/src/Standard.Licensing.sln @@ -4,7 +4,7 @@ VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Standard.Licensing", "Standard.Licensing\Standard.Licensing.csproj", "{C658755A-2F60-430B-A41E-9C11C817B909}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Standard.Licensing.Tests", "Standard.Licensing.Tests\Standard.Licensing.Tests.csproj", "{3BF56CDC-AA67-4BBC-A7F6-422920E0E2F9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Standard.Licensing.Tests", "Standard.Licensing.Tests\Standard.Licensing.Tests.csproj", "{3BF56CDC-AA67-4BBC-A7F6-422920E0E2F9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Standard.Licensing/Standard.Licensing.csproj b/src/Standard.Licensing/Standard.Licensing.csproj index 3f53dad..ddf1a87 100644 --- a/src/Standard.Licensing/Standard.Licensing.csproj +++ b/src/Standard.Licensing/Standard.Licensing.csproj @@ -47,6 +47,7 @@ v1.1.9 + From 2755e5a7c58985d45aff2a2fe74aee0f70ebf803 Mon Sep 17 00:00:00 2001 From: Kori Francis Date: Wed, 6 Nov 2024 20:39:45 -0500 Subject: [PATCH 5/5] Use Fill --- src/Standard.Licensing/Security/Cryptography/KeyFactory.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Standard.Licensing/Security/Cryptography/KeyFactory.cs b/src/Standard.Licensing/Security/Cryptography/KeyFactory.cs index 2c0a90d..77d7de8 100644 --- a/src/Standard.Licensing/Security/Cryptography/KeyFactory.cs +++ b/src/Standard.Licensing/Security/Cryptography/KeyFactory.cs @@ -55,10 +55,7 @@ public static string ToEncryptedPrivateKeyString(AsymmetricKeyParameter key, str { #if NET6_0_OR_GREATER // Use built-in cryptography (e.g., RNGCryptoServiceProvider) in .NET 6.0+ - using (var rng = RandomNumberGenerator.Create()) - { - rng.GetBytes(salt, 0, 16); - } + RandomNumberGenerator.Fill(salt.AsSpan(0, 16)); #else // Use BouncyCastle for .NET Standard 2.0 or other versions var secureRandom = SecureRandom.GetInstance("SHA256PRNG");