From bd12c4702b2eb9fca768fef26ff2e7e4ff8c23cf Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Fri, 9 Oct 2026 00:16:02 +0200 Subject: [PATCH] Bouncy Castle cryptography: .NET 10, OAEP, 2048-bit DSA, derived Blowfish key - Retarget both projects to net10.0; BouncyCastle.Cryptography 2.3.1 to 2.7.0; Microsoft.NET.Test.Sdk 18.10.1, coverlet.collector 10.1.0, MSTest 4.5.1. - RSA: Pkcs1Encoding to OaepEncoding over SHA-256. - DSA: 2048-bit parameters through DsaParameterGenerationParameters (the Init(size, certainty) overload stops at 1024 bits). - Blowfish: derive the key from the password with PBKDF2 (HMAC-SHA256, random salt) instead of using the password bytes as the key; static class, file-scoped namespace, redundant usings removed. - Program.cs: hash the article's input string and print the DSA signature instead of the Blowfish ciphertext. - Tests: fix the "pasword" typo, add MD5 and SHA-256 known-answer tests, discard unused out values. --- .../AsymmetricEncryption/DsaEncryptor.cs | 7 +- .../AsymmetricEncryption/RsaEncryptor.cs | 7 +- .../BouncyCastleCryptography.csproj | 4 +- .../BouncyCastleCryptography/Program.cs | 9 +-- .../SymmetricEncryption/AesEncryptor.cs | 4 +- .../SymmetricEncryption/BlowfishEncryptor.cs | 65 ++++++++++--------- .../BouncyCastleCryptographyTests.csproj | 10 +-- .../HashingUnitTests.cs | 28 ++++++-- .../SymmetricEncryptionUnitTests.cs | 8 +-- 9 files changed, 85 insertions(+), 57 deletions(-) diff --git a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/AsymmetricEncryption/DsaEncryptor.cs b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/AsymmetricEncryption/DsaEncryptor.cs index f2029ca11f..5f45d5165b 100644 --- a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/AsymmetricEncryption/DsaEncryptor.cs +++ b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/AsymmetricEncryption/DsaEncryptor.cs @@ -1,4 +1,5 @@ -using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Security; @@ -11,9 +12,9 @@ public static class DsaEncryptor public static AsymmetricCipherKeyPair GenerateDsaKeyPair() { // Create DSA parameters - var dsaParamsGenerator = new DsaParametersGenerator(); + var dsaParamsGenerator = new DsaParametersGenerator(new Sha256Digest()); var random = new SecureRandom(); - dsaParamsGenerator.Init(1024, 80, random); // key size: 1024 bits, certainty: 80 + dsaParamsGenerator.Init(new DsaParameterGenerationParameters(2048, 256, 80, random)); // key size: 2048 bits, q size: 256 bits, certainty: 80 // Generate DSA key pair var dsaParams = dsaParamsGenerator.GenerateParameters(); diff --git a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/AsymmetricEncryption/RsaEncryptor.cs b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/AsymmetricEncryption/RsaEncryptor.cs index 720e7e5ea6..f10c91b560 100644 --- a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/AsymmetricEncryption/RsaEncryptor.cs +++ b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/AsymmetricEncryption/RsaEncryptor.cs @@ -1,4 +1,5 @@ -using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Encodings; using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto; @@ -21,7 +22,7 @@ public static byte[] RsaEncrypt(string input, AsymmetricKeyParameter publicKey) { var inputBytes = Encoding.UTF8.GetBytes(input); - var cipher = new Pkcs1Encoding(new RsaEngine()); + var cipher = new OaepEncoding(new RsaEngine(), new Sha256Digest()); cipher.Init(true, publicKey); @@ -30,7 +31,7 @@ public static byte[] RsaEncrypt(string input, AsymmetricKeyParameter publicKey) public static string RsaDecrypt(byte[] encryptedBytes, AsymmetricKeyParameter privateKey) { - var cipher = new Pkcs1Encoding(new RsaEngine()); + var cipher = new OaepEncoding(new RsaEngine(), new Sha256Digest()); // Initialize the cipher for decryption with the private key cipher.Init(false, privateKey); diff --git a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/BouncyCastleCryptography.csproj b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/BouncyCastleCryptography.csproj index 02341e438a..610cd0d701 100644 --- a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/BouncyCastleCryptography.csproj +++ b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/BouncyCastleCryptography.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 enable enable @@ -14,7 +14,7 @@ - + diff --git a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/Program.cs b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/Program.cs index f946250fda..8ed4dc0866 100644 --- a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/Program.cs +++ b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/Program.cs @@ -3,17 +3,18 @@ using BouncyCastleCryptography.SymmetricEncryption; var input = "Hello, Bouncy Castle!"; +var secretValue = "This is my password! Dont read me!"; // HASHING Console.WriteLine("Hashing:"); -var hash = Md5Hasher.Md5Hash(input); +var hash = Md5Hasher.Md5Hash(secretValue); var hashAsStr = Convert.ToBase64String(hash); Console.WriteLine("MD5 hash result: " + hashAsStr); -hash = ShaHasher.ShaHash(input); +hash = ShaHasher.ShaHash(secretValue); hashAsStr = Convert.ToBase64String(hash); @@ -32,7 +33,7 @@ var password = "mysecretpassword"; -encryptedData = BlowfishEncryptor.BlowfishEncrypt(input, password, out byte[] iv2); +encryptedData = BlowfishEncryptor.BlowfishEncrypt(input, password, out byte[] salt, out byte[] iv2); encryptedString = Convert.ToBase64String(encryptedData); @@ -55,7 +56,7 @@ var signature = DsaEncryptor.DsaSign(input, keyPair2.Private); -var signatureAsStr = Convert.ToBase64String(encryptedData); +var signatureAsStr = Convert.ToBase64String(signature); var isSignatureValid = DsaEncryptor.DsaVerify(input, signature, keyPair2.Public); diff --git a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/SymmetricEncryption/AesEncryptor.cs b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/SymmetricEncryption/AesEncryptor.cs index feacb6bfaa..31fc430c60 100644 --- a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/SymmetricEncryption/AesEncryptor.cs +++ b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/SymmetricEncryption/AesEncryptor.cs @@ -35,8 +35,6 @@ public static string AesDecrypt(byte[] key, byte[] iv, byte[] encryptedBytes) // Decrypt the encrypted data var decryptedBytes = cipher.DoFinal(encryptedBytes); - var decryptedString = Encoding.UTF8.GetString(decryptedBytes); - - return decryptedString; + return Encoding.UTF8.GetString(decryptedBytes); } } \ No newline at end of file diff --git a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/SymmetricEncryption/BlowfishEncryptor.cs b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/SymmetricEncryption/BlowfishEncryptor.cs index 3ca5078c87..31a520dc26 100644 --- a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/SymmetricEncryption/BlowfishEncryptor.cs +++ b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptography/SymmetricEncryption/BlowfishEncryptor.cs @@ -1,46 +1,53 @@ -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; -namespace BouncyCastleCryptography.SymmetricEncryption +namespace BouncyCastleCryptography.SymmetricEncryption; + +public static class BlowfishEncryptor { - public class BlowfishEncryptor + private const int Iterations = 600_000; + + public static byte[] BlowfishEncrypt(string input, string password, out byte[] salt, out byte[] iv) { - public static byte[] BlowfishEncrypt(string input, string password, out byte[] iv) - { - var inputBytes = Encoding.UTF8.GetBytes(input); + var inputBytes = Encoding.UTF8.GetBytes(input); + + var random = new SecureRandom(); + salt = new byte[16]; + random.NextBytes(salt); + iv = new byte[8]; // Blowfish uses an 8-byte (64-bit) IV + random.NextBytes(iv); + + var cipher = CipherUtilities.GetCipher("Blowfish/CBC/PKCS7Padding"); - var random = new SecureRandom(); - iv = new byte[8]; // Blowfish uses an 8-byte (64-bit) IV - random.NextBytes(iv); + var keyParam = DeriveKey(password, salt); - var cipher = CipherUtilities.GetCipher("Blowfish/CBC/PKCS7Padding"); + cipher.Init(true, new ParametersWithIV(keyParam, iv)); - var keyBytes = Encoding.UTF8.GetBytes(password); - var keyParam = new KeyParameter(keyBytes); + return cipher.DoFinal(inputBytes); + } - cipher.Init(true, new ParametersWithIV(keyParam, iv)); + public static string BlowfishDecrypt(byte[] encryptedBytes, string password, byte[] salt, byte[] iv) + { + var cipher = CipherUtilities.GetCipher("Blowfish/CBC/PKCS7Padding"); - return cipher.DoFinal(inputBytes); - } + var keyParam = DeriveKey(password, salt); - public static string BlowfishDecrypt(byte[] encryptedBytes, string password, byte[] iv) - { - var cipher = CipherUtilities.GetCipher("Blowfish/CBC/PKCS7Padding"); + cipher.Init(false, new ParametersWithIV(keyParam, iv)); - var keyBytes = Encoding.UTF8.GetBytes(password); - var keyParam = new KeyParameter(keyBytes); + var plainBytes = cipher.DoFinal(encryptedBytes); - cipher.Init(false, new ParametersWithIV(keyParam, iv)); + return Encoding.UTF8.GetString(plainBytes); + } - var plainBytes = cipher.DoFinal(encryptedBytes); + private static KeyParameter DeriveKey(string password, byte[] salt) + { + // PBKDF2 with HMAC-SHA256 stretches the password and salt into a 128-bit key + var generator = new Pkcs5S2ParametersGenerator(new Sha256Digest()); + generator.Init(Encoding.UTF8.GetBytes(password), salt, Iterations); - return Encoding.UTF8.GetString(plainBytes); - } + return (KeyParameter)generator.GenerateDerivedParameters("Blowfish", 128); } } diff --git a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptographyTests/BouncyCastleCryptographyTests.csproj b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptographyTests/BouncyCastleCryptographyTests.csproj index 2544f81751..9ecff50130 100644 --- a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptographyTests/BouncyCastleCryptographyTests.csproj +++ b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptographyTests/BouncyCastleCryptographyTests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable @@ -10,10 +10,10 @@ - - - - + + + + diff --git a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptographyTests/HashingUnitTests.cs b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptographyTests/HashingUnitTests.cs index 71411fff46..b90879f48e 100644 --- a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptographyTests/HashingUnitTests.cs +++ b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptographyTests/HashingUnitTests.cs @@ -8,7 +8,7 @@ public class HashingUnitTests [TestMethod] public void GivenSecretData_WhenMd5IsUsed_ThenSecretIsHashed() { - var secretValue = "This is my pasword! Dont read me!"; + var secretValue = "This is my password! Dont read me!"; var hash = Md5Hasher.Md5Hash(secretValue); @@ -18,7 +18,7 @@ public void GivenSecretData_WhenMd5IsUsed_ThenSecretIsHashed() [TestMethod] public void GivenSecretData_WhenMd5HashingMultipleTimes_ThenResultIsDeterministic() { - var secretValue = "This is my pasword! Dont read me!"; + var secretValue = "This is my password! Dont read me!"; var hash = Md5Hasher.Md5Hash(secretValue); @@ -30,7 +30,7 @@ public void GivenSecretData_WhenMd5HashingMultipleTimes_ThenResultIsDeterministi [TestMethod] public void GivenSecretData_WhenShaIsUsed_ThenSecretIsHashed() { - var secretValue = "This is my pasword! Dont read me!"; + var secretValue = "This is my password! Dont read me!"; var hash = ShaHasher.ShaHash(secretValue); @@ -40,7 +40,7 @@ public void GivenSecretData_WhenShaIsUsed_ThenSecretIsHashed() [TestMethod] public void GivenSecretData_WhenShaHashingMultipleTimes_ThenResultIsDeterministic() { - var secretValue = "This is my pasword! Dont read me!"; + var secretValue = "This is my password! Dont read me!"; var hash = ShaHasher.ShaHash(secretValue); @@ -48,4 +48,24 @@ public void GivenSecretData_WhenShaHashingMultipleTimes_ThenResultIsDeterministi CollectionAssert.AreEqual(hash, hash2); } + + [TestMethod] + public void GivenSecretData_WhenMd5IsUsed_ThenHashMatchesKnownValue() + { + var secretValue = "This is my password! Dont read me!"; + + var hash = Md5Hasher.Md5Hash(secretValue); + + Assert.AreEqual("l5aMGrOOPOxm6i6cRK/sqA==", Convert.ToBase64String(hash)); + } + + [TestMethod] + public void GivenSecretData_WhenShaIsUsed_ThenHashMatchesKnownValue() + { + var secretValue = "This is my password! Dont read me!"; + + var hash = ShaHasher.ShaHash(secretValue); + + Assert.AreEqual("SSNYFZG3vQSGtXqZVaSZry4QBm4uueLwegd/gd6H8ns=", Convert.ToBase64String(hash)); + } } \ No newline at end of file diff --git a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptographyTests/SymmetricEncryptionUnitTests.cs b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptographyTests/SymmetricEncryptionUnitTests.cs index 752d0ea580..f944c48020 100644 --- a/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptographyTests/SymmetricEncryptionUnitTests.cs +++ b/authorization-dotnet/BouncyCastleCryptography/BouncyCastleCryptographyTests/SymmetricEncryptionUnitTests.cs @@ -10,7 +10,7 @@ public void GivenSecretData_WhenAesEncrypting_ThenDataIsEncrypted() { var input = "Hello, Bouncy Castle!"; - var encryptedData = AesEncryptor.AesEncrypt(input, out byte[] iv, out byte[] key); + var encryptedData = AesEncryptor.AesEncrypt(input, out _, out _); var encryptedString = Convert.ToBase64String(encryptedData); @@ -39,7 +39,7 @@ public void GivenSecretData_WhenBlowfishEncrypting_ThenDataIsEncrypted() var input = "Hello, Bouncy Castle!"; var password = "mysecretpassword"; - var encryptedBytes = BlowfishEncryptor.BlowfishEncrypt(input, password, out byte[] iv); + var encryptedBytes = BlowfishEncryptor.BlowfishEncrypt(input, password, out _, out _); var encryptedString = Convert.ToBase64String(encryptedBytes); @@ -53,9 +53,9 @@ public void GivenSecretData_WhenBlowfishEncrypting_ThenDataCanBeDecrypted() var input = "Hello, Bouncy Castle!"; var password = "mysecretpassword"; - var encryptedBytes = BlowfishEncryptor.BlowfishEncrypt(input, password, out byte[] iv); + var encryptedBytes = BlowfishEncryptor.BlowfishEncrypt(input, password, out byte[] salt, out byte[] iv); - var myDecryptedStr = BlowfishEncryptor.BlowfishDecrypt(encryptedBytes, password, iv); + var myDecryptedStr = BlowfishEncryptor.BlowfishDecrypt(encryptedBytes, password, salt, iv); Assert.IsNotNull(myDecryptedStr); Assert.AreEqual(input, myDecryptedStr);