diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..bdc7981 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,263 @@ +# CryptoNet - AI Coding Agent Instructions + +## Project Overview +CryptoNet is a cross-platform .NET cryptography library providing RSA, AES, and DSA implementations. The core library targets .NET Standard 2.0 for maximum compatibility, while tests and examples use .NET 8.0. + +## Architecture + +### Project Structure +- **CryptoNet** (netstandard2.0): Core library with `CryptoNetRsa`, `CryptoNetAes`, `CryptoNetDsa` classes +- **CryptoNet.Models** (netstandard2.0): Shared models (`CryptoNetInfo`, `RsaDetail`, `AesDetail`, `DsaDetail`, `KeyType`, `EncryptionType`) +- **CryptoNet.ExtShared** (netstandard2.0): Internal utilities for certificate handling and byte operations +- **CryptoNet.ExtPack** (net8.0): Extension methods for MD5 hashing, PEM export, and content validation +- **CryptoNet.UnitTests** (net8.0): NUnit tests with NUnit, Moq, Shouldly assertions + +### Key Dependencies +All projects use **Central Package Management** via `Directory.Packages.props`. Never add package versions directly to `.csproj` files—only add `` without versions. + +### NuGet Packaging Pattern +The main `CryptoNet.csproj` uses `PrivateAssets="all"` for project references to `ExtShared` and `Models`, then explicitly includes their DLLs in the package via `IncludeReferencedDllsInPackage` target. This creates a self-contained NuGet package. + +## Development Workflows + +### Building & Testing +```powershell +# Standard build and test +dotnet build +dotnet test --configuration Release --no-build + +# Code coverage with HTML report +.\Scripts\run_codecoverage.ps1 # Runs tests, generates coverage, opens in Edge + +# Docker build +.\Scripts\run_docker_build.ps1 +# or: docker build . --file .\Dockerfile --tag cryptonet-service:latest +``` + +### Documentation Generation +Documentation uses **DocFX** to generate API docs from XML comments: +```powershell +.\Scripts\run_docs.ps1 # Cleans, builds, serves on localhost:8080 + +# Setup (one-time): dotnet tool install -g docfx +``` + +The `index.md` is kept in sync with `README.md`: +```powershell +.\Scripts\run_update_index.ps1 # Adds YAML header and appends README content +``` + +### Release Process +Releases are tag-based with version format `vX.Y.Z` or `vX.Y.Z-previewYYYYMMDDHmm`: +```powershell +# Preview release +.\Scripts\run_release.ps1 -VersionNumber 3.0.0 -IsPreview $true + +# Production release +.\Scripts\run_release.ps1 -VersionNumber 3.0.0 -IsPreview $false +``` + +Pushing tags triggers GitHub Actions workflows (`cd-release.yml`, `cd-release-preview.yml`) that build, test, pack, and publish to NuGet. + +## Coding Conventions + +### Code Quality Standards +- **Nullable reference types enabled**: Use `?` for nullable types, handle nulls explicitly +- **TreatWarningsAsErrors**: All warnings are errors—code must be warning-free +- **XML documentation required**: Public APIs must have `` tags (enforced by `GenerateDocumentationFile`) +- **Deterministic builds**: `true` in `Directory.Build.Props` + +### Interface Pattern +All main crypto classes implement interfaces (`ICryptoNetRsa`, `ICryptoNetAes`, `ICryptoNetDsa`) which inherit from `ICryptoNet`. This base interface defines: +```csharp +CryptoNetInfo Info { get; } +byte[] EncryptFromString(string content); +string DecryptToString(byte[] bytes); +byte[] EncryptFromBytes(byte[] bytes); +byte[] DecryptToBytes(byte[] bytes); +``` + +### Constructor Overloading Pattern +Crypto classes follow a consistent constructor pattern: +```csharp +// Self-generated keys +public CryptoNetRsa(int keySize = 2048) + +// String key (XML or PEM) +public CryptoNetRsa(string key, int keySize = 2048) + +// File-based key +public CryptoNetRsa(FileInfo fileInfo, int keySize = 2048) + +// X509 certificate +public CryptoNetRsa(X509Certificate2? certificate, KeyType keyType, int keySize = 2048) +``` + +### Testing Patterns +Tests use **NUnit** with **Shouldly** for assertions: +```csharp +result.ShouldBe(expected); // NOT Assert.AreEqual +ExtensionPack.CheckContent(original, decrypted).ShouldBeTrue(); // MD5-based content comparison +``` + +Test resource files are in `Resources/` with `Always`. + +## Common Pitfalls + +### Version Management +- **Never** hardcode versions in individual `.csproj` files +- Update versions in `Directory.Build.Props` (global) and `Directory.Packages.props` (package versions) +- Release versions must match the format `^\d+\.\d+\.\d+$` in `run_release.ps1` + +### Cross-Platform Compatibility +- Core library must remain .NET Standard 2.0 compatible +- Avoid .NET 8+ specific APIs in `CryptoNet`, `CryptoNet.Models`, `CryptoNet.ExtShared` +- Use `System.Security.Cryptography` types from .NET Standard 2.0 + +### GitHub Actions Workflows +- **Never add `runs-on` to caller jobs** when using reusable workflows (see `CONTRIBUTING.md`) +- Reusable workflows declare `on: workflow_call` and define their own `runs-on` +- Example: `cd-build-test-pack.yml` is a reusable workflow called by release workflows + +### File Header Conventions +All C# files include copyright headers: +```csharp +// +// Copyright (c) 2021 All Rights Reserved +// +// Maytham Fahmi +// DD-MM-YYYY HH:MM:SS +// part of CryptoNet project +``` + +## Key Files +- `Directory.Build.Props`: Global MSBuild properties (version, warnings, symbols) +- `Directory.Packages.props`: Central package version management +- `docfx.json`: DocFX configuration for API documentation +- `RELEASE-NOTES`: Changelog file read during NuGet packing (see `PrepareReleaseNotes` target) +- `coverlet.runsettings`: Code coverage configuration + +## Development Tips +- Use `ExtShared.LoadFileToString()` for reading key files consistently +- Use `ExtensionPack.CheckContent()` for MD5-based content validation in tests +- Key files are stored/loaded using `SaveKey(FileInfo, bool isPrivate)` and constructor overloads +- The `Info` property exposes algorithm details, key types, and loaded keys for inspection + +## Migration Guide: Old API → Latest CryptoNet v3+ + +### API Changes Summary +- **Removed**: `ExportKey()`, `ExportKeyAndSave()` methods +- **Replaced with**: `GetKey()` and `SaveKey()` methods +- **Namespace change**: `CryptoNetUtils` → `ExtShared.ExtShared` +- All crypto classes now expose `Info` property with algorithm details + +### AES Examples + +#### Old: Encrypt/Decrypt with Symmetric Key +```csharp +// OLD API (v2.x) +ICryptoNet cryptoNet = new CryptoNetAes(); +var key = cryptoNet.ExportKey(); +ICryptoNet encryptClient = new CryptoNetAes(key); +var encrypt = encryptClient.EncryptFromString(data); +ICryptoNet decryptClient = new CryptoNetAes(key); +var decrypt = decryptClient.DecryptToString(encrypt); +``` + +#### New: Encrypt/Decrypt with Symmetric Key +```csharp +// NEW API (v3.x) +ICryptoNetAes cryptoNet = new CryptoNetAes(); +var key = cryptoNet.GetKey(); // Changed from ExportKey() + +ICryptoNetAes encryptClient = new CryptoNetAes(key); +var encrypt = encryptClient.EncryptFromString(data); + +ICryptoNetAes decryptClient = new CryptoNetAes(key); +var decrypt = decryptClient.DecryptToString(encrypt); +``` + +#### Old: Export and Import Symmetric Key from File +```csharp +// OLD API (v2.x) +ICryptoNet cryptoNet = new CryptoNetAes(); +var file = new FileInfo("symmetric.key"); +cryptoNet.ExportKeyAndSave(file); +var encrypt = cryptoNet.EncryptFromString(data); +ICryptoNet cryptoNetImport = new CryptoNetAes(file); +var decrypt = cryptoNetImport.DecryptToString(encrypt); +``` + +#### New: Save and Load Symmetric Key from File +```csharp +// NEW API (v3.x) +ICryptoNetAes cryptoNet = new CryptoNetAes(); +var file = new FileInfo("symmetric.key"); +cryptoNet.SaveKey(file); // Changed from ExportKeyAndSave() + +var encrypt = cryptoNet.EncryptFromString(data); + +ICryptoNetAes cryptoNetImport = new CryptoNetAes(file); +var decrypt = cryptoNetImport.DecryptToString(encrypt); +``` + +### RSA Examples + +#### Old: Generate RSA Key Pair and Save +```csharp +// OLD API (v2.x) +ICryptoNet cryptoNet = new CryptoNetRsa(); +cryptoNet.ExportKeyAndSave(new FileInfo("private.key"), true); +cryptoNet.ExportKeyAndSave(new FileInfo("public.key"), false); +ICryptoNet pubKeyCrypto = new CryptoNetRsa(new FileInfo("public.key")); +var encrypt = pubKeyCrypto.EncryptFromString(data); +ICryptoNet priKeyCrypto = new CryptoNetRsa(new FileInfo("private.key")); +var decrypt = priKeyCrypto.DecryptToString(encrypt); +``` + +#### New: Generate RSA Key Pair and Save +```csharp +// NEW API (v3.x) +ICryptoNetRsa cryptoNet = new CryptoNetRsa(); +cryptoNet.SaveKey(new FileInfo("private.key"), true); // Changed from ExportKeyAndSave() +cryptoNet.SaveKey(new FileInfo("public.key"), false); // Changed from ExportKeyAndSave() + +ICryptoNetRsa pubKeyCrypto = new CryptoNetRsa(new FileInfo("public.key")); +var encrypt = pubKeyCrypto.EncryptFromString(data); + +ICryptoNetRsa priKeyCrypto = new CryptoNetRsa(new FileInfo("private.key")); +var decrypt = priKeyCrypto.DecryptToString(encrypt); +``` + +### X509 Certificate Examples + +#### Old: Use X509 Certificate for Encryption +```csharp +// OLD API (v2.x) +X509Certificate2? cert = CryptoNetUtils.GetCertificateFromStore("CN=Maytham"); +ICryptoNet publicKeyCrypto = new CryptoNetRsa(cert, KeyType.PublicKey); +var encrypt = publicKeyCrypto.EncryptFromString(data); +ICryptoNet privateKeyCrypto = new CryptoNetRsa(cert, KeyType.PrivateKey); +var decrypt = privateKeyCrypto.DecryptToString(encrypt); +``` + +#### New: Use X509 Certificate for Encryption +```csharp +// NEW API (v3.x) +using CryptoNet.ExtShared; // Namespace change: CryptoNetUtils → ExtShared + +X509Certificate2? cert = ExtShared.GetCertificateFromStore("CN=Maytham"); // Changed from CryptoNetUtils + +ICryptoNetRsa publicKeyCrypto = new CryptoNetRsa(cert, KeyType.PublicKey); +var encrypt = publicKeyCrypto.EncryptFromString(data); + +ICryptoNetRsa privateKeyCrypto = new CryptoNetRsa(cert, KeyType.PrivateKey); +var decrypt = privateKeyCrypto.DecryptToString(encrypt); +``` + +### Key Migration Points +1. **Method renames**: `ExportKey()` → `GetKey()`, `ExportKeyAndSave()` → `SaveKey()` +2. **Namespace**: `CryptoNetUtils` → `CryptoNet.ExtShared.ExtShared` +3. **Interface specificity**: Prefer `ICryptoNetRsa`, `ICryptoNetAes`, `ICryptoNetDsa` over generic `ICryptoNet` +4. **Info property**: Access algorithm details via `cryptoNet.Info` (includes key types, sizes, loaded keys) +5. **File operations**: Both `FileInfo` and `string` filename overloads available for `SaveKey()` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a965657..144eac0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,18 +18,18 @@ contributors must follow to avoid common build and packaging failures. ### Example: Caller (no `runs-on`) -``` yaml +~~~ yaml jobs: call-build: uses: ./.github/workflows/cd-build-test-pack.yml with: version: ${{ github.ref_name }} configuration: Release -``` +~~~ ### Example: Reusable Workflow -``` yaml +~~~ yaml on: workflow_call: inputs: @@ -47,8 +47,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - # … -``` + # � +~~~ ## Versioning and Git Tag Guidelines @@ -78,7 +78,7 @@ jobs: ### Sanitization Example -``` yaml +~~~ yaml - name: Sanitize version id: sanitize run: | @@ -86,7 +86,7 @@ jobs: v="${v#v}" v="${v#V}" echo "sanitized_version=$v" >> "$GITHUB_OUTPUT" -``` +~~~ Use via: `${{ steps.sanitize.outputs.sanitized_version }}` @@ -98,3 +98,15 @@ Use via: `${{ steps.sanitize.outputs.sanitized_version }}` ## Maintenance - Keep conventions aligned with GitHub Actions and NuGet rules. + +## Contributing + +You are more than welcome to contribute in one of the following ways: + +1. Basic: Give input, and suggestions for improvement by creating an issue and labeling it https://github.com/itbackyard/CryptoNet/issues +2. Advance: if you have good knowledge of C# and Cryptography just grab one of the issues, or features, or create a new one and refactor and add a pull request. +3. Documentation: Add, update, or improve documentation, by making a pull request. + +### How to contribute: + +[Here](https://www.dataschool.io/how-to-contribute-on-github/) is a link to learn how to contribute if you are not aware of how to do it. diff --git a/CryptoNet.Examples/CryptoNet.Examples.csproj b/CryptoNet.Examples/CryptoNet.Examples.csproj new file mode 100644 index 0000000..aefb1c2 --- /dev/null +++ b/CryptoNet.Examples/CryptoNet.Examples.csproj @@ -0,0 +1,29 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + Always + + + Always + + + Always + + + Always + + + + diff --git a/CryptoNet.Examples/ExampleAes.cs b/CryptoNet.Examples/ExampleAes.cs new file mode 100644 index 0000000..64a0dc9 --- /dev/null +++ b/CryptoNet.Examples/ExampleAes.cs @@ -0,0 +1,157 @@ +// +// Copyright (c) 2021 All Rights Reserved +// +// Maytham Fahmi +// 17-12-2021 12:18:44 +// part of CryptoNet project + +using System.Diagnostics; +using System.Security.Cryptography; +using System.Text; +using CryptoNet.Models; + +namespace CryptoNet.Examples; + +/// +/// Example usage scenarios for AES symmetric encryption using the CryptoNet library. +/// Demonstrates generating, saving/loading symmetric keys and encrypting/decrypting +/// both content and files. +/// +public static class ExampleAes +{ + private const string ConfidentialDummyData = @"Some Secret Data"; + + private static readonly string BaseDirectory = AppDomain.CurrentDomain.BaseDirectory; + private static readonly string SymmetricKeyFilePath = Path.Combine(BaseDirectory, $"{KeyType.SymmetricKey}.xml"); + + /// + /// Generate a symmetric key, encrypt and decrypt a string using self-generated key. + /// + public static void EncryptDecryptWithSelfGeneratedSymmetricKey() + { + ICryptoNetAes cryptoNet = new CryptoNetAes(); + string key = cryptoNet.GetKey(); + + ICryptoNet encryptClient = new CryptoNetAes(key); + byte[] encrypted = encryptClient.EncryptFromString(ConfidentialDummyData); + + ICryptoNet decryptClient = new CryptoNetAes(key); + string decrypted = decryptClient.DecryptToString(encrypted); + + Debug.Assert(ConfidentialDummyData == decrypted); + } + + /// + /// Generate a symmetric key and save it to disk, then load it to decrypt previously encrypted content. + /// + public static void GenerateAndSaveSymmetricKey() + { + ICryptoNetAes cryptoNet = new CryptoNetAes(); + FileInfo keyFile = new FileInfo(SymmetricKeyFilePath); + cryptoNet.SaveKey(keyFile); + + Debug.Assert(File.Exists(keyFile.FullName)); + + byte[] encrypted = cryptoNet.EncryptFromString(ConfidentialDummyData); + + ICryptoNet cryptoNetImported = new CryptoNetAes(keyFile); + string decrypted = cryptoNetImported.DecryptToString(encrypted); + + Debug.Assert(ConfidentialDummyData == decrypted); + } + + /// + /// Encrypt and decrypt using a provided raw symmetric key and IV. + /// + public static void EncryptDecryptWithProvidedSymmetricKey() + { + string symmetricKey = "12345678901234567890123456789012"; + if (symmetricKey.Length != 32) + { + Console.WriteLine("Key should be 32 characters long"); + Environment.Exit(0); + } + + string secret = "1234567890123456"; + if (secret.Length != 16) + { + Console.WriteLine("IV should be 16 characters long"); + Environment.Exit(1); + } + + byte[] key = Encoding.UTF8.GetBytes(symmetricKey); + byte[] iv = Encoding.UTF8.GetBytes(secret); + + ICryptoNet encryptClient = new CryptoNetAes(key, iv); + byte[] encrypted = encryptClient.EncryptFromString(ConfidentialDummyData); + + ICryptoNet decryptClient = new CryptoNetAes(key, iv); + string decrypted = decryptClient.DecryptToString(encrypted); + + Debug.Assert(ConfidentialDummyData == decrypted); + } + + /// + /// Generate a human-readable key/secret pair and use them for encryption/decryption. + /// + public static void EncryptDecryptWithHumanReadableKeySecret() + { + string symmetricKey = GenerateUniqueKey("symmetricKey"); + string secret = new string(GenerateUniqueKey("password").Take(16).ToArray()); + + byte[] key = Encoding.UTF8.GetBytes(symmetricKey); + byte[] iv = Encoding.UTF8.GetBytes(secret); + + ICryptoNet encryptClient = new CryptoNetAes(key, iv); + byte[] encrypted = encryptClient.EncryptFromString(ConfidentialDummyData); + + ICryptoNet decryptClient = new CryptoNetAes(key, iv); + string decrypted = decryptClient.DecryptToString(encrypted); + + Debug.Assert(ConfidentialDummyData == decrypted); + } + + /// + /// Encrypt and decrypt a file using a self-generated symmetric key and verify content equality. + /// + /// Relative path under TestFiles directory to the file to encrypt. + public static void EncryptAndDecryptFileWithSymmetricKeyTest(string filename) + { + ICryptoNetAes cryptoNet = new CryptoNetAes(); + string key = cryptoNet.GetKey(); + + FileInfo fileInfo = new FileInfo(filename); + + ICryptoNet encryptClient = new CryptoNetAes(key); + string sourceFilePath = Path.Combine(BaseDirectory, filename); + byte[] fileBytes = File.ReadAllBytes(sourceFilePath); + byte[] encrypted = encryptClient.EncryptFromBytes(fileBytes); + + ICryptoNet decryptClient = new CryptoNetAes(key); + byte[] decrypted = decryptClient.DecryptToBytes(encrypted); + string decryptedFilePath = $"TestFiles\\{Path.GetFileNameWithoutExtension(fileInfo.Name)}-decrypted{fileInfo.Extension}"; + File.WriteAllBytes(decryptedFilePath, decrypted); + + bool isIdenticalFile = ExtShared.ExtShared.ByteArrayCompare(fileBytes, decrypted); + Debug.Assert(isIdenticalFile); + } + + /// + /// Create a deterministic unique key string from input using MD5 (example helper). + /// + /// Input string to derive the key from. + /// Hexadecimal string representation of MD5 hash. + public static string GenerateUniqueKey(string input) + { + byte[] inputBytes = Encoding.ASCII.GetBytes(input); + byte[] hash = MD5.HashData(inputBytes); + + StringBuilder sb = new StringBuilder(); + foreach (byte b in hash) + { + sb.Append(b.ToString("X2")); + } + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/CryptoNet.Examples/ExampleDsa.cs b/CryptoNet.Examples/ExampleDsa.cs new file mode 100644 index 0000000..88f6b61 --- /dev/null +++ b/CryptoNet.Examples/ExampleDsa.cs @@ -0,0 +1,69 @@ +// +// Copyright (c) 2021 All Rights Reserved +// +// Maytham Fahmi +// 07-12-2025 20:04:00 +// part of CryptoNet project + +using System.Diagnostics; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using CryptoNet.Models; + +namespace CryptoNet.Examples; + +/// +/// Example usage scenarios for DSA operations using the CryptoNet library. +/// Demonstrates signing, verification and saving/loading DSA keys. +/// +public static class ExampleDsa +{ + private const string ConfidentialDummyData = @"Some Secret Data"; + private static readonly string BaseDirectory = AppDomain.CurrentDomain.BaseDirectory; + + internal static readonly string PrivateKeyFilePath = Path.Combine(BaseDirectory, "privateKey"); + internal static readonly string PublicKeyFilePath = Path.Combine(BaseDirectory, "publicKey.pub"); + + /// + /// Demonstrates signing content with a self-generated DSA key and validating the signature. + /// + public static void SignAndValidateWithSelfGeneratedKey() + { + ICryptoNetDsa client = new CryptoNetDsa(); + string privateKeyXml = client.GetKey(true); + + ICryptoNetDsa signatureClient = new CryptoNetDsa(privateKeyXml); + byte[] signature = signatureClient.CreateSignature(ConfidentialDummyData); + + ICryptoNetDsa verifyClient = new CryptoNetDsa(privateKeyXml); + byte[] contentBytes = ExtShared.ExtShared.StringToBytes(ConfidentialDummyData); + + bool isVerified = verifyClient.IsContentVerified(contentBytes, signature); + + Debug.Assert(isVerified == true); + } + + /// + /// Demonstrates generating DSA key pair, saving them to files, and verifying signatures using loaded keys. + /// + public static void GenerateAndSaveDsaKeyPair() + { + ICryptoNetDsa cryptoNet = new CryptoNetDsa(); + + cryptoNet.SaveKey(new FileInfo(PrivateKeyFilePath), true); + cryptoNet.SaveKey(new FileInfo(PublicKeyFilePath), false); + + Debug.Assert(File.Exists(new FileInfo(PrivateKeyFilePath).FullName)); + Debug.Assert(File.Exists(new FileInfo(PublicKeyFilePath).FullName)); + + ICryptoNetDsa dsaWithPrivateKey = new CryptoNetDsa(new FileInfo(PrivateKeyFilePath)); + byte[] signature = dsaWithPrivateKey.CreateSignature(ConfidentialDummyData); + + ICryptoNetDsa dsaWithPublicKey = new CryptoNetDsa(new FileInfo(PublicKeyFilePath)); + byte[] confidentialBytes = ExtShared.ExtShared.StringToBytes(ConfidentialDummyData); + bool isVerified = dsaWithPublicKey.IsContentVerified(confidentialBytes, signature); + + Debug.Assert(isVerified == true); + } +} \ No newline at end of file diff --git a/CryptoNet.Examples/ExampleRsa.cs b/CryptoNet.Examples/ExampleRsa.cs new file mode 100644 index 0000000..6baa1c0 --- /dev/null +++ b/CryptoNet.Examples/ExampleRsa.cs @@ -0,0 +1,240 @@ +// +// Copyright (c) 2021 All Rights Reserved +// +// Maytham Fahmi +// 17-12-2021 12:18:44 +// part of CryptoNet project + +using System.Diagnostics; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using CryptoNet.Models; + +namespace CryptoNet.Examples; + +/// +/// Example usage scenarios for RSA operations using the CryptoNet library. +/// Methods demonstrate generating keys, saving/loading keys, encrypting/decrypting +/// content and working with X509 certificates and PEM formats. +/// +public static class ExampleRsa +{ + private const string ConfidentialDummyData = @"Some Secret Data"; + private static readonly string BaseDirectory = AppDomain.CurrentDomain.BaseDirectory; + + internal static readonly string PrivateKeyFilePath = Path.Combine(BaseDirectory, "privateKey"); + internal static readonly string PublicKeyFilePath = Path.Combine(BaseDirectory, "publicKey.pub"); + + /// + /// Demonstrates generating a self-contained RSA key pair and using them to + /// encrypt and decrypt a string. + /// + public static void EncryptDecryptWithSelfGeneratedKey() + { + ICryptoNetRsa cryptoNet = new CryptoNetRsa(); + + string privateKeyXml = cryptoNet.GetKey(true); + string publicKeyXml = cryptoNet.GetKey(false); + + ICryptoNet encryptClient = new CryptoNetRsa(publicKeyXml); + byte[] encrypted = encryptClient.EncryptFromString(ConfidentialDummyData); + + ICryptoNet decryptClient = new CryptoNetRsa(privateKeyXml); + string decrypted = decryptClient.DecryptToString(encrypted); + + Debug.Assert(ConfidentialDummyData == decrypted); + } + + /// + /// Demonstrates generating keys and saving them to files, then loading + /// those files to perform encryption/decryption. + /// + public static void GenerateAndSaveAsymmetricKey() + { + ICryptoNetRsa cryptoNet = new CryptoNetRsa(); + + cryptoNet.SaveKey(new FileInfo(PrivateKeyFilePath), true); + cryptoNet.SaveKey(new FileInfo(PublicKeyFilePath), false); + + Debug.Assert(File.Exists(new FileInfo(PrivateKeyFilePath).FullName)); + Debug.Assert(File.Exists(new FileInfo(PublicKeyFilePath).FullName)); + + ICryptoNet publicKeyClient = new CryptoNetRsa(new FileInfo(PublicKeyFilePath)); + byte[] encrypted = publicKeyClient.EncryptFromString(ConfidentialDummyData); + + ICryptoNet privateKeyClient = new CryptoNetRsa(new FileInfo(PrivateKeyFilePath)); + string decrypted = privateKeyClient.DecryptToString(encrypted); + + Debug.Assert(ConfidentialDummyData == decrypted); + } + + /// + /// Encrypts with a public key file and decrypts with a private key file. + /// + public static void EncryptWithPublicKeyAndDecryptWithPrivateKey() + { + ICryptoNet publicKeyClient = new CryptoNetRsa(new FileInfo(PublicKeyFilePath)); + byte[] encrypted = publicKeyClient.EncryptFromString(ConfidentialDummyData); + + ICryptoNet privateKeyClient = new CryptoNetRsa(new FileInfo(PrivateKeyFilePath)); + string decrypted = privateKeyClient.DecryptToString(encrypted); + + Debug.Assert(ConfidentialDummyData == decrypted); + } + + /// + /// Demonstrates using an X509 certificate's public/private key for encryption/decryption. + /// Replace the subject name with a certificate available on your machine. + /// + public static void UseX509Certificate() + { + // Replace subject with your certificate subject (for example "CN=localhost") + X509Certificate2? certificate = ExtShared.ExtShared.GetCertificateFromStore("CN=localhost"); + + ICryptoNet publicKeyClient = new CryptoNetRsa(certificate, KeyType.PublicKey); + byte[] encrypted = publicKeyClient.EncryptFromString(ConfidentialDummyData); + + ICryptoNet privateKeyClient = new CryptoNetRsa(certificate, KeyType.PrivateKey); + string decrypted = privateKeyClient.DecryptToString(encrypted); + + Debug.Assert(ConfidentialDummyData == decrypted); + } + + /// + /// Exports the public key portion from an X509 certificate using the CryptoNetRsa GetKey API. + /// + public static void ExportPublicKeyFromX509Certificate() + { + // Replace subject with your certificate subject (for example "CN=localhost") + X509Certificate2? certificate = ExtShared.ExtShared.GetCertificateFromStore("CN=localhost"); + + ICryptoNetRsa certClient = new CryptoNetRsa(certificate, KeyType.PublicKey); + string publicKeyXml = certClient.GetKey(false); + + Debug.Assert(!string.IsNullOrEmpty(publicKeyXml)); + } + + /// + /// Demonstrates encrypting/decrypting content that includes special (emoji) characters. + /// Ensures UTF-8 byte-level operations are used. + /// + public static void WorkWithSpecialCharacterText() + { + string confidentialWithSpecialChars = "Top secret 😃😃"; + + ICryptoNetRsa cryptoNet = new CryptoNetRsa(); + string privateKeyXml = cryptoNet.GetKey(true); + string publicKeyXml = cryptoNet.GetKey(false); + + ICryptoNet encryptClient = new CryptoNetRsa(publicKeyXml); + byte[] encrypted = encryptClient.EncryptFromBytes(Encoding.UTF8.GetBytes(confidentialWithSpecialChars)); + + ICryptoNet decryptClient = new CryptoNetRsa(privateKeyXml); + byte[] decryptedBytes = decryptClient.DecryptToBytes(encrypted); + string decryptedString = Encoding.UTF8.GetString(decryptedBytes); + + Debug.Assert(confidentialWithSpecialChars == decryptedString); + } + + /// + /// Work-in-progress: demonstrates PEM export/import and encrypted PEM import with password. + /// + public static void CustomizePemExamples() + { + X509Certificate2? certificate = ExtShared.ExtShared.GetCertificateFromStore("CN=localhost"); + + char[] pubKeyPem = ExportPemKey(certificate!, privateKey: false); + char[] priKeyPem = ExportPemKey(certificate!); + + string password = "password"; + byte[] encryptedPriKeyBytes = ExportPemKeyWithPassword(certificate!, password); + + ICryptoNet decryptClientWithPassword = ImportPemKeyWithPassword(encryptedPriKeyBytes, password); + byte[] encrypted1 = decryptClientWithPassword.EncryptFromString(ConfidentialDummyData); + + ICryptoNet encryptClientFromPubPem = ImportPemKey(pubKeyPem); + byte[] encrypted2 = encryptClientFromPubPem.EncryptFromString(ConfidentialDummyData); + + ICryptoNet decryptClientFromPriPem = ImportPemKey(priKeyPem); + string decrypted2 = decryptClientFromPriPem.DecryptToString(encrypted2); + + Debug.Assert(ConfidentialDummyData == decrypted2); + + string decrypted1 = decryptClientFromPriPem.DecryptToString(encrypted1); + + Debug.Assert(ConfidentialDummyData == decrypted1); + } + + /// + /// Export a certificate as PEM-encoded character array. + /// + /// Certificate to export. + /// PEM characters for the certificate. + public static char[] ExportPemCertificate(X509Certificate2 cert) + { + byte[] certBytes = cert!.RawData; + char[] certPem = PemEncoding.Write("CERTIFICATE", certBytes); + return certPem; + } + + /// + /// Export a certificate's RSA key as PEM (public or private). + /// + /// Certificate containing the RSA key. + /// True to export private key, false for public key. + /// PEM characters for the key. + public static char[] ExportPemKey(X509Certificate2 cert, bool privateKey = true) + { + AsymmetricAlgorithm rsa = cert.GetRSAPrivateKey()!; + + if (privateKey) + { + byte[] privateKeyBytes = rsa.ExportPkcs8PrivateKey(); + return PemEncoding.Write("PRIVATE KEY", privateKeyBytes); + } + + byte[] publicKeyBytes = rsa.ExportSubjectPublicKeyInfo(); + return PemEncoding.Write("PUBLIC KEY", publicKeyBytes); + } + + /// + /// Import a PEM key into a newly created CryptoNetRsa instance. + /// + /// PEM characters representing the key. + /// ICryptoNet instance with the imported key. + public static ICryptoNet ImportPemKey(char[] key) + { + ICryptoNet cryptoNet = new CryptoNetRsa(); + cryptoNet.Info.RsaDetail!.Rsa?.ImportFromPem(key); + return cryptoNet; + } + + /// + /// Export an encrypted PKCS#8 private key using a password and AES-256-CBC PBES. + /// + /// Certificate containing the private key to export. + /// Password to encrypt the private key. + /// Encrypted private key bytes. + public static byte[] ExportPemKeyWithPassword(X509Certificate2 cert, string password) + { + AsymmetricAlgorithm rsa = cert.GetRSAPrivateKey()!; + byte[] passwordBytes = Encoding.UTF8.GetBytes(password); + byte[] encryptedPrivateKey = rsa.ExportEncryptedPkcs8PrivateKey(passwordBytes, + new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, iterationCount: 100_000)); + return encryptedPrivateKey; + } + + /// + /// Import an encrypted PKCS#8 private key using the provided password into a new CryptoNetRsa instance. + /// + /// Encrypted PKCS#8 private key bytes. + /// Password used to decrypt the private key. + /// ICryptoNet instance with imported private key. + public static ICryptoNet ImportPemKeyWithPassword(byte[] encryptedPrivateKey, string password) + { + ICryptoNet cryptoNet = new CryptoNetRsa(); + cryptoNet.Info.RsaDetail?.Rsa?.ImportEncryptedPkcs8PrivateKey(password, encryptedPrivateKey, out _); + return cryptoNet; + } +} \ No newline at end of file diff --git a/CryptoNet.Examples/Program.cs b/CryptoNet.Examples/Program.cs new file mode 100644 index 0000000..61117ff --- /dev/null +++ b/CryptoNet.Examples/Program.cs @@ -0,0 +1,24 @@ +using CryptoNet.Examples; + +ExampleDsa.SignAndValidateWithSelfGeneratedKey(); +ExampleDsa.GenerateAndSaveDsaKeyPair(); + +ExampleAes.EncryptDecryptWithSelfGeneratedSymmetricKey(); +ExampleAes.GenerateAndSaveSymmetricKey(); +ExampleAes.EncryptDecryptWithProvidedSymmetricKey(); +ExampleAes.EncryptDecryptWithHumanReadableKeySecret(); +ExampleAes.EncryptAndDecryptFileWithSymmetricKeyTest("TestFiles\\test.docx"); +ExampleAes.EncryptAndDecryptFileWithSymmetricKeyTest("TestFiles\\test.xlsx"); +ExampleAes.EncryptAndDecryptFileWithSymmetricKeyTest("TestFiles\\test.pdf"); +ExampleAes.EncryptAndDecryptFileWithSymmetricKeyTest("TestFiles\\test.png"); + +// Updated RSA example method names to follow PascalCase and remove underscores +ExampleRsa.EncryptDecryptWithSelfGeneratedKey(); +ExampleRsa.GenerateAndSaveAsymmetricKey(); +ExampleRsa.EncryptWithPublicKeyAndDecryptWithPrivateKey(); +ExampleRsa.UseX509Certificate(); +ExampleRsa.ExportPublicKeyFromX509Certificate(); +ExampleRsa.WorkWithSpecialCharacterText(); +ExampleRsa.CustomizePemExamples(); + + diff --git a/CryptoNet.Examples/TestFiles/test.docx b/CryptoNet.Examples/TestFiles/test.docx new file mode 100644 index 0000000..4852773 Binary files /dev/null and b/CryptoNet.Examples/TestFiles/test.docx differ diff --git a/CryptoNet.Examples/TestFiles/test.pdf b/CryptoNet.Examples/TestFiles/test.pdf new file mode 100644 index 0000000..9b88b35 Binary files /dev/null and b/CryptoNet.Examples/TestFiles/test.pdf differ diff --git a/CryptoNet.Examples/TestFiles/test.png b/CryptoNet.Examples/TestFiles/test.png new file mode 100644 index 0000000..29c6eb4 Binary files /dev/null and b/CryptoNet.Examples/TestFiles/test.png differ diff --git a/CryptoNet.Examples/TestFiles/test.xlsx b/CryptoNet.Examples/TestFiles/test.xlsx new file mode 100644 index 0000000..9575466 Binary files /dev/null and b/CryptoNet.Examples/TestFiles/test.xlsx differ diff --git a/CryptoNet.ExtPack/CryptoNet.ExtPack.csproj b/CryptoNet.ExtPack/CryptoNet.ExtPack.csproj index 5b1df01..59a81bb 100644 --- a/CryptoNet.ExtPack/CryptoNet.ExtPack.csproj +++ b/CryptoNet.ExtPack/CryptoNet.ExtPack.csproj @@ -33,7 +33,7 @@ true <_SkipUpgradeNetAnalyzersNuGetWarning>true true - True + true diff --git a/CryptoNet.ExtPack/ExtensionPack.cs b/CryptoNet.ExtPack/ExtensionPack.cs index 4d1bf31..636ee18 100644 --- a/CryptoNet.ExtPack/ExtensionPack.cs +++ b/CryptoNet.ExtPack/ExtensionPack.cs @@ -24,7 +24,7 @@ public static bool CheckContent(string originalContent, string decryptedContent) return originalContent == decryptedContent; } - return CalculateMd5(originalContent).Equals(CalculateMd5(decryptedContent)); + return CalculateMd5(originalContent).Equals(CalculateMd5(decryptedContent), StringComparison.Ordinal); } /// @@ -34,8 +34,8 @@ public static bool CheckContent(string originalContent, string decryptedContent) /// The MD5 hash of the content as a lowercase hexadecimal string. public static string CalculateMd5(string content) { - var hash = MD5.HashData(Encoding.UTF8.GetBytes(content)); - return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); + byte[] hash = MD5.HashData(Encoding.UTF8.GetBytes(content)); + return Convert.ToHexString(hash).ToLowerInvariant(); } /// diff --git a/CryptoNet.ExtShared/ExtShared.cs b/CryptoNet.ExtShared/ExtShared.cs index 88263c6..a4b3f28 100644 --- a/CryptoNet.ExtShared/ExtShared.cs +++ b/CryptoNet.ExtShared/ExtShared.cs @@ -106,23 +106,23 @@ public static RSAParameters GetParameters(X509Certificate2? certificate, KeyType } /// - /// Converts a byte array to an ASCII-encoded string. + /// Converts a byte array to an UTF8-encoded string. /// /// The byte array to convert. - /// An ASCII-encoded string representation of the byte array. + /// An UTF8-encoded string representation of the byte array. public static string BytesToString(byte[] bytes) { - return Encoding.ASCII.GetString(bytes); + return Encoding.UTF8.GetString(bytes); } /// - /// Converts an ASCII-encoded string to a byte array. + /// Converts an UTF8-encoded string to a byte array. /// /// The string to convert. - /// A byte array representing the ASCII-encoded string. + /// A byte array representing the UTF8-encoded string. public static byte[] StringToBytes(string content) { - return Encoding.ASCII.GetBytes(content); + return Encoding.UTF8.GetBytes(content); } /// diff --git a/CryptoNet.sln b/CryptoNet.sln index ca2ae6a..821f3f6 100644 --- a/CryptoNet.sln +++ b/CryptoNet.sln @@ -20,7 +20,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DevOps", "DevOps", "{1733D1 Directory.Build.Props = Directory.Build.Props DockerBuild.ps1 = DockerBuild.ps1 Dockerfile = Dockerfile - RELEASE-NOTES = RELEASE-NOTES run_codecoverage.ps1 = run_codecoverage.ps1 run_codecoverage.txt = run_codecoverage.txt run_release.ps1 = run_release.ps1 @@ -28,6 +27,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DevOps", "DevOps", "{1733D1 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Info", "Info", "{6976E2ED-A139-48C5-A390-639F56A8B348}" ProjectSection(SolutionItems) = preProject + CONTRIBUTING.md = CONTRIBUTING.md LICENSE = LICENSE README.md = README.md EndProjectSection @@ -53,6 +53,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DSAExample", "Examples\DSAE EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CryptoNet.ConsumerTest", "CryptoNet.ConsumerTest\CryptoNet.ConsumerTest.csproj", "{6BBC4487-140E-44E1-9703-F9F48E805589}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CryptoNet.Examples", "CryptoNet.Examples\CryptoNet.Examples.csproj", "{CAEB7F60-5632-2F90-05B7-D72D7AD177FA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{B3B9ABB3-DBB7-4E0F-970C-761B0B1A4B81}" + ProjectSection(SolutionItems) = preProject + docs\examples.md = docs\examples.md + docs\getting-started.md = docs\getting-started.md + docs\introduction.md = docs\introduction.md + docs\migration.md = docs\migration.md + docs\toc.yml = docs\toc.yml + docs\tooling.md = docs\tooling.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -99,6 +111,10 @@ Global {6BBC4487-140E-44E1-9703-F9F48E805589}.Debug|Any CPU.Build.0 = Debug|Any CPU {6BBC4487-140E-44E1-9703-F9F48E805589}.Release|Any CPU.ActiveCfg = Release|Any CPU {6BBC4487-140E-44E1-9703-F9F48E805589}.Release|Any CPU.Build.0 = Release|Any CPU + {CAEB7F60-5632-2F90-05B7-D72D7AD177FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CAEB7F60-5632-2F90-05B7-D72D7AD177FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CAEB7F60-5632-2F90-05B7-D72D7AD177FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAEB7F60-5632-2F90-05B7-D72D7AD177FA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index b8275ee..ba525d4 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ It does not depend on other libraries. You can install the CryptoNet NuGet package via [NuGet](https://www.nuget.org/packages/CryptoNet). -## Website +## Documentation https://itbackyard.github.io/CryptoNet @@ -41,136 +41,3 @@ CryptoNet Extensions v3: Please report issues [here](https://github.com/itbackyard/CryptoNet/issues). -## How to use -Ref to docs. - -## Build and Testing -You have different options to build and run the unit tests from: - 1. Visual Studio 2019/2022. - 2. Visual Studio Code. - 3. dotnet command line. - 4. dotnet commands are preserved in a PowerShell script ```build.ps1```. - 5. Docker, run the following command from the solution folder: - -``` -docker build . --file .\Dockerfile --tag cryptonet-service:latest -``` - -or run preserved PowerShell: - -```powershell -./run_docker_build.ps1 -``` - -## How to release a new version? - -Preview -``` -.\run_release.ps1 -VersionNumber 3.0.0 -IsPreview $true -``` - -Release -``` -.\run_release.ps1 -VersionNumber 3.0.0 -IsPreview $false -``` - -## Documentation Creation - -There are static and dynamically generated documentation. Both are published automatically in the pipeline called `5-static.yml`. - -To work with the documentation locally, the following might be relevant: - -- **Static base documentation** is located in the `docs` folder. -- **Dynamically generated documentation** is created from code using a tool called **DocFX**. - -### Running Documentation Creation Locally - -To generate the documentation locally on your computer, run: - -```powershell -.\run_docs.ps1 -``` - -### Setup - -1. Install the DocFX tool (only needs to be done once): - -``` -dotnet tool install -g docfx -``` - -2. The following step is already configured in this repository. However, if you need to start over, run the following to initialize and configure DocFX: - -``` -docfx init -y -``` - -## Update `index.md` with `README.md` - -To update `index.md` with the latest content from `README.md`, run the following command: - -```powershell -./run_update_index.ps1 -``` -This script will: - -1. Add the following header to the top of `index.md`: - -```yaml ---- -_layout: landing ---- -``` - -2. Append the content of `README.md` to `index.md`. - -This ensures that index.md is always up to date with the latest changes in `README.md`. - -## Code Coverage - -Code coverage ensures that your tests adequately cover your codebase, improving overall quality, reliability, and maintainability. Follow these steps to set up and generate code coverage reports. - -### Running Code Coverage Locally - -To generate code coverage reports locally on your computer, run the following command in Windows: - -```powershell -.\run_codecoverage.ps1 -``` - -### Setup - -If the required tools and packages are not set up locally, follow the steps below to configure them: - -1. Navigate to your test project directory (e.g., `CryptoNet.UnitTests`): - -```bash -cd .\CryptoNet.UnitTests\ -``` - -2. Add the necessary coverage packages to your test project: - -```bash -dotnet add package coverlet.collector -dotnet add package coverlet.msbuild -``` - -3. Install the report generator tool (only needs to be done once): - -```bash -dotnet tool install --global dotnet-reportgenerator-globaltool -``` - -Once set up, you can use these tools to analyze and generate detailed code coverage reports to ensure thorough testing of your application. - -## Contributing - -You are more than welcome to contribute in one of the following ways: - -1. Basic: Give input, and suggestions for improvement by creating an issue and labeling it https://github.com/itbackyard/CryptoNet/issues -2. Advance: if you have good knowledge of C# and Cryptography just grab one of the issues, or features, or create a new one and refactor and add a pull request. -3. Documentation: Add, update, or improve documentation, by making a pull request. - -### How to contribute: - -[Here](https://www.dataschool.io/how-to-contribute-on-github/) is a link to learn how to contribute if you are not aware of how to do it. diff --git a/RELEASE-NOTES b/RELEASE-NOTES deleted file mode 100644 index 8b13789..0000000 --- a/RELEASE-NOTES +++ /dev/null @@ -1 +0,0 @@ - diff --git a/run_build.ps1 b/Scripts/run_build.ps1 similarity index 100% rename from run_build.ps1 rename to Scripts/run_build.ps1 diff --git a/run_codecoverage.ps1 b/Scripts/run_codecoverage.ps1 similarity index 100% rename from run_codecoverage.ps1 rename to Scripts/run_codecoverage.ps1 diff --git a/run_docker_build.ps1 b/Scripts/run_docker_build.ps1 similarity index 100% rename from run_docker_build.ps1 rename to Scripts/run_docker_build.ps1 diff --git a/run_docs.ps1 b/Scripts/run_docs.ps1 similarity index 100% rename from run_docs.ps1 rename to Scripts/run_docs.ps1 diff --git a/run_nuget_validation.ps1 b/Scripts/run_nuget_validation.ps1 similarity index 100% rename from run_nuget_validation.ps1 rename to Scripts/run_nuget_validation.ps1 diff --git a/run_release.ps1 b/Scripts/run_release.ps1 similarity index 100% rename from run_release.ps1 rename to Scripts/run_release.ps1 diff --git a/run_update_index.ps1 b/Scripts/run_update_index.ps1 similarity index 100% rename from run_update_index.ps1 rename to Scripts/run_update_index.ps1 diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 0000000..31fd84c --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,243 @@ +# CryptoNet – How to Use (AES, DSA, RSA) + +This document shows **how to use CryptoNet** with the built-in examples for: + +- AES (symmetric encryption) +- DSA (digital signatures) +- RSA (asymmetric encryption + certificates) + +All examples live under: + +~~~csharp +namespace CryptoNet.Examples; +~~~ + +--- + +## 1. AES – Symmetric Encryption (`ExampleAes`) + +### 1.1. Encrypt/Decrypt a String with a Self-Generated Key + +~~~csharp +// EncryptDecryptWithSelfGeneratedKey +ICryptoNetAes cryptoNet = new CryptoNetAes(); +var key = cryptoNet.GetKey(); + +ICryptoNet encryptClient = new CryptoNetAes(key); +var encrypt = encryptClient.EncryptFromString(ConfidentialDummyData); + +ICryptoNet decryptClient = new CryptoNetAes(key); +var decrypt = decryptClient.DecryptToString(encrypt); +~~~ + +### 1.2. Generate & Save AES Key to File + +~~~csharp +ICryptoNetAes cryptoNet = new CryptoNetAes(); +var file = new FileInfo(SymmetricKeyFile); +cryptoNet.SaveKey(file); + +var encrypt = cryptoNet.EncryptFromString(ConfidentialDummyData); + +ICryptoNet cryptoNetKeyImport = new CryptoNetAes(file); +var decrypt = cryptoNetKeyImport.DecryptToString(encrypt); +~~~ + +### 1.3. Encrypt/Decrypt Using Your Own Key and IV + +~~~csharp +var key = Encoding.UTF8.GetBytes("32-char-string-key................"); +var iv = Encoding.UTF8.GetBytes("16-char-secret.."); + +ICryptoNet encryptClient = new CryptoNetAes(key, iv); +var encrypt = encryptClient.EncryptFromString(ConfidentialDummyData); + +ICryptoNet decryptClient = new CryptoNetAes(key, iv); +var decrypt = decryptClient.DecryptToString(encrypt); +~~~ + +### 1.4. Human-Readable Key + IV + +~~~csharp +var symmetricKey = GenerateUniqueKey("symmetricKey"); +var secret = new string(GenerateUniqueKey("password").Take(16).ToArray()); + +ICryptoNet crypto = new CryptoNetAes( + Encoding.UTF8.GetBytes(symmetricKey), + Encoding.UTF8.GetBytes(secret)); +~~~ + +### 1.5. Encrypt/Decrypt File + +~~~csharp +byte[] fileBytes = File.ReadAllBytes(filename); +var encrypt = encryptClient.EncryptFromBytes(fileBytes); +var decrypt = decryptClient.DecryptToBytes(encrypt); +~~~ + +--- + +## 2. DSA – Digital Signatures (`ExampleDsa`) + +### 2.1. Sign & Verify with Self-Generated Key + +~~~csharp +ICryptoNetDsa client = new CryptoNetDsa(); +var privateKey = client.GetKey(true); + +var signature = new CryptoNetDsa(privateKey).CreateSignature(ConfidentialDummyData); + +var verified = new CryptoNetDsa(privateKey) + .IsContentVerified(ExtShared.ExtShared.StringToBytes(ConfidentialDummyData), signature); +~~~ + +### 2.2. Generate, Save, and Reuse Keys + +~~~csharp +cryptoNet.SaveKey(new FileInfo(PrivateKeyFile), true); +cryptoNet.SaveKey(new FileInfo(PublicKeyFile), false); + +var signature = new CryptoNetDsa(new FileInfo(PrivateKeyFile)) + .CreateSignature(ConfidentialDummyData); + +var verified = new CryptoNetDsa(new FileInfo(PublicKeyFile)) + .IsContentVerified(ExtShared.ExtShared.StringToBytes(ConfidentialDummyData), signature); +~~~ + +--- + +## 3. RSA – Asymmetric Encryption (`ExampleRsa`) + +### 3.1. Self-Generated RSA Keys + +~~~csharp +ICryptoNetRsa cryptoNet = new CryptoNetRsa(); +var privateKey = cryptoNet.GetKey(true); +var publicKey = cryptoNet.GetKey(false); + +var encrypt = new CryptoNetRsa(publicKey).EncryptFromString(ConfidentialDummyData); +var decrypt = new CryptoNetRsa(privateKey).DecryptToString(encrypt); +~~~ + +### 3.2. Save RSA Keys & Reuse + +~~~csharp +cryptoNet.SaveKey(new FileInfo(PrivateKeyFile), true); +cryptoNet.SaveKey(new FileInfo(PublicKeyFile), false); + +var encrypt = new CryptoNetRsa(new FileInfo(PublicKeyFile)).EncryptFromString(ConfidentialDummyData); +var decrypt = new CryptoNetRsa(new FileInfo(PrivateKeyFile)).DecryptToString(encrypt); +~~~ + +### 3.3. Encrypt with Public Key, Decrypt with Private Key + +~~~csharp +ICryptoNet pubKeyClient = new CryptoNetRsa(new FileInfo(PublicKeyFile)); +var encrypted = pubKeyClient.EncryptFromString(ConfidentialDummyData); + +ICryptoNet priKeyClient = new CryptoNetRsa(new FileInfo(PrivateKeyFile)); +var decrypted = priKeyClient.DecryptToString(encrypted); +~~~ + +### 3.4. Use X509 Certificate for RSA Encryption + +~~~csharp +var cert = ExtShared.ExtShared.GetCertificateFromStore("CN=localhost"); + +var encrypt = new CryptoNetRsa(cert, KeyType.PublicKey) + .EncryptFromString(ConfidentialDummyData); + +var decrypt = new CryptoNetRsa(cert, KeyType.PrivateKey) + .DecryptToString(encrypt); +~~~ + +### 3.5. Export RSA Public Key from Certificate + +~~~csharp +X509Certificate2? certificate = ExtShared.ExtShared.GetCertificateFromStore("CN=localhost"); + +ICryptoNetRsa certClient = new CryptoNetRsa(certificate, KeyType.PublicKey); +var publicKey = certClient.GetKey(isPrivate: false); +~~~ + +### 3.6. Working with Special Characters + +~~~csharp +string confidentialWithSpecialChars = "Top secret 😃😃"; + +ICryptoNetRsa cryptoNet = new CryptoNetRsa(); +var privateKeyXml = cryptoNet.GetKey(isPrivate: true); +var publicKeyXml = cryptoNet.GetKey(isPrivate: false); + +ICryptoNet encryptClient = new CryptoNetRsa(publicKeyXml); +var encrypted = encryptClient.EncryptFromBytes(Encoding.UTF8.GetBytes(confidentialWithSpecialChars)); + +ICryptoNet decryptClient = new CryptoNetRsa(privateKeyXml); +var decryptedBytes = decryptClient.DecryptToBytes(encrypted); +var decryptedString = Encoding.UTF8.GetString(decryptedBytes); +~~~ + +### 3.7. PEM Import/Export (Advanced) +> Note: This example requires the CryptoNet.Extensions package. and still in development. + +~~~csharp +// Export PEM, encrypted PEM, import PEM (with/without password) +char[] pubPem = ExampleRsa.ExportPemKey(certificate, privateKey: false); +char[] priPem = ExampleRsa.ExportPemKey(certificate); +byte[] encryptedPriPem = ExampleRsa.ExportPemKeyWithPassword(certificate, "password"); + +ICryptoNet importedFromPub = ExampleRsa.ImportPemKey(pubPem); +ICryptoNet importedFromPri = ExampleRsa.ImportPemKey(priPem); +ICryptoNet importedFromEncryptedPri = ExampleRsa.ImportPemKeyWithPassword(encryptedPriPem, "password"); +~~~ + +--- + +## Running All Examples (quick reference) + +~~~csharp +using CryptoNet.Examples; + +// AES +ExampleAes.EncryptDecryptWithSelfGeneratedKey(); +ExampleAes.GenerateAndSaveSymmetricKey(); +ExampleAes.EncryptDecryptWithProvidedSymmetricKey(); +ExampleAes.EncryptDecryptWithHumanReadableKeySecret(); +ExampleAes.EncryptAndDecryptFileWithSymmetricKeyTest("TestFiles\\test.docx"); + +// DSA +ExampleDsa.SignAndValidateWithSelfGeneratedKey(); +ExampleDsa.GenerateAndSaveDsaKeyPair(); + +// RSA +ExampleRsa.EncryptDecryptWithSelfGeneratedKey(); +ExampleRsa.GenerateAndSaveAsymmetricKey(); +ExampleRsa.EncryptWithPublicKeyAndDecryptWithPrivateKey(); +ExampleRsa.UseX509Certificate(); +ExampleRsa.ExportPublicKeyFromX509Certificate(); +ExampleRsa.WorkWithSpecialCharacterText(); +ExampleRsa.CustomizePemExamples(); +~~~ + +--- + +## Examples coverage checklist + +~~~text +[ ] AES - EncryptDecryptWithSelfGeneratedKey +[ ] AES - GenerateAndSaveSymmetricKey +[ ] AES - EncryptDecryptWithProvidedSymmetricKey +[ ] AES - EncryptDecryptWithHumanReadableKeySecret +[ ] AES - EncryptAndDecryptFileWithSymmetricKeyTest +[ ] DSA - SignAndValidateWithSelfGeneratedKey +[ ] DSA - GenerateAndSaveDsaKeyPair +[ ] RSA - EncryptDecryptWithSelfGeneratedKey +[ ] RSA - GenerateAndSaveAsymmetricKey +[ ] RSA - EncryptWithPublicKeyAndDecryptWithPrivateKey +[ ] RSA - UseX509Certificate +[ ] RSA - ExportPublicKeyFromX509Certificate +[ ] RSA - WorkWithSpecialCharacterText +[ ] RSA - CustomizePemExamples +~~~ + +## End \ No newline at end of file diff --git a/docs/getting-started.md b/docs/getting-started.md index 26f533c..20d18ec 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -6,7 +6,7 @@ Here are some examples: ### Example: Encrypt and Decrypt Content With Symmetric Key In this example CryptoNetAes generates a random key and IV, hence we use the same instance we can both encrypt and decrypt. -```csharp +~~~csharp ICryptoNet cryptoNet = new CryptoNetAes(); var key = cryptoNet.ExportKey(); @@ -17,10 +17,10 @@ ICryptoNet decryptClient = new CryptoNetAes(key); var decrypt = decryptClient.DecryptToString(encrypt); Debug.Assert(ConfidentialDummyData == decrypt); -``` +~~~ ### Example: Encrypt and Decrypt Content With Export and Import Self-Generated Symmetric Key -```csharp +~~~csharp ICryptoNet cryptoNet = new CryptoNetAes(); var file = new FileInfo(SymmetricKeyFile); cryptoNet.ExportKeyAndSave(file); @@ -33,10 +33,10 @@ ICryptoNet cryptoNetKeyImport = new CryptoNetAes(file); var decrypt = cryptoNetKeyImport.DecryptToString(encrypt); Debug.Assert(ConfidentialDummyData == decrypt); -``` +~~~ ### Example: Generate Asymmetric RSA key pair, Export Private and Public, use Public key to encrypt with and Use Private key to decrypt with -```csharp +~~~csharp ICryptoNet cryptoNet = new CryptoNetRsa(); cryptoNet.ExportKeyAndSave(new FileInfo(PrivateKeyFile), true); @@ -52,10 +52,10 @@ ICryptoNet cryptoNetPriKey = new CryptoNetRsa(new FileInfo(PrivateKeyFile)); var decrypt = cryptoNetPriKey.DecryptToString(encrypt); Debug.Assert(ConfidentialDummyData == decrypt); -``` +~~~ ### Example: Use X509 certificate to Encrypt with Public Key and later Decrypt with Private Key -```csharp +~~~csharp // Find and replace CN=Maytham with your own certificate X509Certificate2? certificate = CryptoNetUtils.GetCertificateFromStore("CN=Maytham"); @@ -66,4 +66,4 @@ ICryptoNet cryptoNetWithPrivateKey = new CryptoNetRsa(certificate, KeyType.Priva var decryptWithPrivateKey = cryptoNetWithPrivateKey.DecryptToString(encryptWithPublicKey); Debug.Assert(ConfidentialDummyData == decryptWithPrivateKey); -``` +~~~ diff --git a/docs/introduction.md b/docs/introduction.md index 904a79e..0e3a58b 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -1,34 +1,42 @@ # Introduction -### Short Intro +This document gives a short introduction to CryptoNet and how the library is typically used. -The library can be used in two ways: +Supported targets +- Core libraries target: .NET Standard 2.0 (for maximum compatibility). +- Examples and tests target: .NET 8 and .NET 10. -- **Symmetric encryption** -- **Asymmetric encryption** (public key encryption) +Overview -#### Symmetric Encryption +CryptoNet supports two primary cryptographic scenarios: -You use the same key (any secret key) for both encryption and decryption. +- Symmetric encryption — a single shared secret (key) is used for both encryption and decryption. +- Asymmetric encryption — a public/private key pair is used; the public key encrypts and the private key decrypts. -#### Asymmetric Encryption +Symmetric encryption +- Use a secret key (same key for encrypt/decrypt). +- Protect the secret key: anyone with the key can decrypt the data. -With asymmetric encryption, the library can use its own self-generated RSA key pairs (Private/Public keys) to encrypt and decrypt content. +Asymmetric encryption +- The library can generate RSA key pairs (private/public) for you, or it can use keys from an X.509 certificate. +- Typical pattern: + - Use the **Public key** to encrypt data. + - Use the **Private key** to decrypt data. -You can store the private key on one or more machines, while the public key can be easily distributed to all clients. +Key lifecycle and security notes +- Private keys must be kept confidential. Do not distribute private keys. +- If a private key is leaked, an attacker can decrypt any content encrypted with the corresponding public key. Rotate (revoke and reissue) the key pair if compromise is suspected. +- Conversely, if you lose a private key and you do not have a backup, you will not be able to decrypt content that was encrypted for that key—make secure backups as appropriate. -> **Important:** Do not distribute private keys publicly; keep them in a safe place. If a private key is mistakenly exposed, you need to reissue new keys. Content already encrypted with the compromised private key cannot be decrypted with a newly generated private key. Before updating or deleting the old private key, ensure all encrypted content is decrypted, or you risk losing access to that content. +Using X.509 certificates +- CryptoNet can use the public/private keys stored in X.509 certificates as an alternative to self-generated keys. This can simplify key distribution and lifecycle when using enterprise PKI. -Additionally, it is possible to use asymmetric keys from an X.509 certificate instead of generating your own keys. +Further reading +- Asymmetric encryption overview: https://www.cloudflare.com/learning/ssl/what-is-asymmetric-encryption/ +- Examples and usage (see the examples documentation): `docs/examples.md` -The main concept of asymmetric encryption is the use of a Private key and a Public key: -- Use the **Public key** to encrypt content. -- Use the **Private key** to decrypt the content. +Example code lives under the `CryptoNet.Examples` namespace and demonstrates AES (symmetric), DSA (signatures) and RSA (asymmetric) scenarios. -Read more about asymmetric (public key) encryption [here](https://www.cloudflare.com/learning/ssl/what-is-asymmetric-encryption/). - -You can find complete examples for: - -- [RSA asymmetric cryptographic algorithm](https://github.com/maythamfahmi/CryptoNet/blob/main/Examples/RSAExample/RSAExample.cs) -- [AES symmetric cryptographic algorithm](https://github.com/maythamfahmi/CryptoNet/blob/main/Examples/AESExample/AESExample.cs) -- [DSA asymmetric cryptographic algorithm](https://github.com/maythamfahmi/CryptoNet/blob/main/Examples/DSAExample/DSAExample.cs) +See also +- Documentation and generation: `docs/tooling.md` +- Examples reference: `docs/examples.md` \ No newline at end of file diff --git a/docs/migration.md b/docs/migration.md new file mode 100644 index 0000000..1580434 --- /dev/null +++ b/docs/migration.md @@ -0,0 +1,331 @@ +# Migration Guide: CryptoNet v2.4.0 → v3.4.3 + +This document describes the main breaking and behavioral changes for the **AES** and **RSA** examples when migrating from **v2.4.0** to **v3.4.3** of CryptoNet. + +--- +## 1. Common Changes + +### 1.1 Utility changes + +Some helper methods moved from `CryptoNet.Utils` to `ExtShared.ExtShared`. + +- AES: byte array comparison. +- RSA: loading X509 certificates from the store, plus other shared helpers. + +You will typically: +- Remove `using CryptoNet.Utils;` +- Add a reference/using for `ExtShared` (e.g. `using ExtShared;` if needed in your project). + +--- + +## 2. AES Migration (ExampleAes) + +### 2.1 Overview + +**v2.4.0** + +~~~csharp +using CryptoNet.Models; +using CryptoNet.Utils; +~~~ + +**v3.4.3** + +~~~csharp +using CryptoNet.Models; +~~~ + +The AES encryption/decryption API (`ICryptoNet` + `CryptoNetAes`) remains mostly the same. The main breaking changes are related to **key management** and **utility usage**. + +### 2.2 AES key management API changes + +#### 2.2.1 Generating a key in memory + +~~~csharp +// v2.4.0 +ICryptoNet cryptoNet = new CryptoNetAes(); +var key = cryptoNet.ExportKey(); + +// v3.4.3 +ICryptoNetAes cryptoNet = new CryptoNetAes(); +var key = cryptoNet.GetKey(); +~~~ + +**Changes:** +- Interface: `ICryptoNet` → `ICryptoNetAes` for key retrieval. +- Method: `ExportKey()` → `GetKey()`. + +**Action:** +- When you need to retrieve the AES key, use `ICryptoNetAes` and `GetKey()`. + +You still use `ICryptoNet` for encryption/decryption with the retrieved key: + +~~~csharp +ICryptoNet encryptClient = new CryptoNetAes(key); +var encrypt = encryptClient.EncryptFromString(ConfidentialDummyData); + +ICryptoNet decryptClient = new CryptoNetAes(key); +var decrypt = decryptClient.DecryptToString(encrypt); +~~~ + +#### 2.2.2 Saving a symmetric key to file + +~~~csharp +// v2.4.0 +ICryptoNet cryptoNet = new CryptoNetAes(); +var file = new FileInfo(SymmetricKeyFile); +cryptoNet.ExportKeyAndSave(file); + +// v3.4.3 +ICryptoNetAes cryptoNet = new CryptoNetAes(); +var file = new FileInfo(SymmetricKeyFile); +cryptoNet.SaveKey(file); +~~~ + +**Changes:** +- Interface: `ICryptoNet` → `ICryptoNetAes`. +- Method: `ExportKeyAndSave(file)` → `SaveKey(file)`. + +**Action:** +- Update both the interface type and the method name when saving AES keys to disk. + +### 2.3 AES Examples 3 & 4 (custom/human-readable keys) + +Examples 3 and 4 are functionally identical between v2.4.0 and v3.4.3: + +- Example 3: uses user-defined 32-char key and 16-char IV. +- Example 4: generates a human-readable key and IV via `UniqueKeyGenerator`. + +No changes are required for the encryption/decryption code in these examples. + +### 2.4 File encryption example (Example 5) + +#### 2.4.1 Method name + +~~~csharp +// v2.4.0 +public static void Example_5_Encrypt_And_Decrypt_PdfFile_With_SymmetricKey_Test(string filename) + +// v3.4.3 +public static void Example_5_Encrypt_And_Decrypt_File_With_SymmetricKey_Test(string filename) +~~~ + +**Change:** Method name updated to reflect support for arbitrary files, not just PDFs. + +**Action:** Update any callers to use the new method name. + +#### 2.4.2 Key retrieval + +~~~csharp +// v2.4.0 +ICryptoNet cryptoNet = new CryptoNetAes(); +var key = cryptoNet.ExportKey(); + +// v3.4.3 +ICryptoNetAes cryptoNet = new CryptoNetAes(); +var key = cryptoNet.GetKey(); +~~~ + +Same pattern as in 2.2.1. + +#### 2.4.3 Byte array comparison + +~~~csharp +// v2.4.0 +var isIdenticalFile = CryptoNetUtils.ByteArrayCompare(pdfFileBytes, decrypt); + +// v3.4.3 +var isIdenticalFile = ExtShared.ExtShared.ByteArrayCompare(pdfFileBytes, decrypt); +~~~ + +**Change:** The helper moved from `CryptoNetUtils` to `ExtShared.ExtShared`. + +**Action:** Replace the call and ensure `ExtShared` is referenced in your project. + +### 2.5 UniqueKeyGenerator + +The `UniqueKeyGenerator` function is unchanged between versions; no migration is needed here. + +### 2.6 AES Migration Checklist + +- [ ] Update namespace to `CryptoNet.Examples`. +- [ ] Replace AES key export calls: + - `ICryptoNet` → `ICryptoNetAes` for key retrieval/saving. + - `ExportKey()` → `GetKey()`. + - `ExportKeyAndSave(file)` → `SaveKey(file)`. +- [ ] Update Example 5 method name if you reference it. +- [ ] Replace `CryptoNetUtils.ByteArrayCompare` with `ExtShared.ExtShared.ByteArrayCompare`. +- [ ] Run all AES examples and confirm `Debug.Assert` checks still pass. + +--- + +## 3. RSA Migration (ExampleRsa) + +### 3.1 Overview + +**v2.4.0** + +~~~csharp +using CryptoNet.Models; +using CryptoNet.Utils; + +namespace CryptoNet.Cli; +~~~ + +**v3.4.3** + +~~~csharp +using CryptoNet.Models; + +namespace CryptoNet.Examples; +~~~ + +The RSA encryption/decryption calls via `ICryptoNet` remain conceptually the same. The major changes are centralized in **key management APIs** and **certificate utilities**. + +### 3.2 RSA key management API changes + +#### 3.2.1 Generating keys in memory + +~~~csharp +// v2.4.0 +ICryptoNet cryptoNet = new CryptoNetRsa(); + +var privateKey = cryptoNet.ExportKey(true); +var publicKey = cryptoNet.ExportKey(false); + +// v3.4.3 +ICryptoNetRsa cryptoNet = new CryptoNetRsa(); + +var privateKey = cryptoNet.GetKey(true); +var publicKey = cryptoNet.GetKey(false); +~~~ + +**Changes:** +- Interface: `ICryptoNet` → `ICryptoNetRsa` for RSA key retrieval. +- Method: `ExportKey(bool)` → `GetKey(bool)`. + +**Action:** +- When working with RSA keys (as strings), migrate to `ICryptoNetRsa` and `GetKey(...)`. + +Encryption/decryption continues to use `ICryptoNet` with `CryptoNetRsa(key)`: + +~~~csharp +ICryptoNet encryptClient = new CryptoNetRsa(publicKey); +var encrypt = encryptClient.EncryptFromString(ConfidentialDummyData); + +ICryptoNet decryptClient = new CryptoNetRsa(privateKey); +var decrypt = decryptClient.DecryptToString(encrypt); +~~~ + +#### 3.2.2 Saving keys to disk + +~~~csharp +// v2.4.0 +ICryptoNet cryptoNet = new CryptoNetRsa(); + +cryptoNet.ExportKeyAndSave(new FileInfo(PrivateKeyFile), true); +cryptoNet.ExportKeyAndSave(new FileInfo(PublicKeyFile), false); + +// v3.4.3 +ICryptoNetRsa cryptoNet = new CryptoNetRsa(); + +cryptoNet.SaveKey(new FileInfo(PrivateKeyFile), true); +cryptoNet.SaveKey(new FileInfo(PublicKeyFile), false); +~~~ + +**Changes:** +- Interface: `ICryptoNet` → `ICryptoNetRsa`. +- Method: `ExportKeyAndSave(FileInfo, bool)` → `SaveKey(FileInfo, bool)`. + +**Action:** +- Update interface type and method names anywhere you persist RSA keys. + +### 3.3 X509 certificate usage + +#### 3.3.1 Certificate lookup (Examples 4, 5, 7) + +~~~csharp +// v2.4.0 +// Find and replace CN=Maytham with your own certificate +X509Certificate2? certificate = CryptoNetUtils.GetCertificateFromStore("CN=Maytham"); + +// v3.4.3 +// Find and replace CN=localhost with your own certificate +X509Certificate2? certificate = ExtShared.ExtShared.GetCertificateFromStore("CN=localhost"); +~~~ + +**Changes:** +- Utility: `CryptoNetUtils.GetCertificateFromStore` → `ExtShared.ExtShared.GetCertificateFromStore`. +- Example subject string changed from `"CN=Maytham"` → `"CN=localhost"` (documentation/sample only). + +**Action:** +- Replace all `CryptoNetUtils.GetCertificateFromStore` calls with `ExtShared.ExtShared.GetCertificateFromStore`. +- Adjust the subject string to match your certificate (e.g. `"CN=yourcert"`). + +This affects: + +- `Example_4_Using_X509_Certificate` +- `Example_5_Export_Public_Key_For_X509_Certificate` +- `Example_7_Customize` + +#### 3.3.2 Exporting public key from certificate (Example 5) + +~~~csharp +// v2.4.0 +X509Certificate2? certificate = CryptoNetUtils.GetCertificateFromStore("CN=Maytham"); + +ICryptoNet cryptoNetWithPublicKey = new CryptoNetRsa(certificate, KeyType.PublicKey); +var publicKey = cryptoNetWithPublicKey.ExportKey(false); + +// v3.4.3 +X509Certificate2? certificate = ExtShared.ExtShared.GetCertificateFromStore("CN=localhost"); + +ICryptoNetRsa cryptoNetWithPublicKey = new CryptoNetRsa(certificate, KeyType.PublicKey); +var publicKey = cryptoNetWithPublicKey.GetKey(false); +~~~ + +**Changes:** +- Certificate retrieval via `ExtShared.ExtShared` instead of `CryptoNetUtils`. +- Interface: `ICryptoNet` → `ICryptoNetRsa` for key extraction. +- Method: `ExportKey(false)` → `GetKey(false)`. + +**Action:** +- Use `ICryptoNetRsa` + `GetKey(false)` when exporting the RSA public key from an X509 certificate. + +### 3.4 Examples 3 & 7 and helpers + +- `Example_3_Encrypt_With_PublicKey_Decrypt_With_PrivateKey_Of_Content`: + - Constructs `CryptoNetRsa` with key files and uses `ICryptoNet` for encryption/decryption. + - No signature or behavioral change; no migration needed. + +- `Example_7_Customize`: + - `ExportPemCertificate`, `ExportPemKey`, `ImportPemKey`, `ExportPemKeyWithPassword`, `ImportPemKeyWithPassword` are unchanged. + - Only difference is certificate fetching: + - `CryptoNetUtils.GetCertificateFromStore` → `ExtShared.ExtShared.GetCertificateFromStore`. + +### 3.5 RSA Migration Checklist + +- [ ] Update namespace to `CryptoNet.Examples`. +- [ ] Replace RSA key-related usage: + - `ICryptoNet` → `ICryptoNetRsa` for key retrieval and saving. + - `ExportKey(bool)` → `GetKey(bool)`. + - `ExportKeyAndSave(FileInfo, bool)` → `SaveKey(FileInfo, bool)`. +- [ ] Update all certificate retrieval calls: + - `CryptoNetUtils.GetCertificateFromStore` → `ExtShared.ExtShared.GetCertificateFromStore`. +- [ ] For certificate-based public key export (Example 5), use `ICryptoNetRsa` and `GetKey(false)`. +- [ ] Run all RSA examples to confirm `Debug.Assert` checks still pass. + +--- + +## 4. Summary + +When migrating from **v2.4.0** to **v3.4.3**: + +- **Namespaces** moved from `CryptoNet.Cli` to `CryptoNet.Examples` for AES and RSA examples. +- **Key management responsibilities** were split into more specific interfaces: + - `ICryptoNetAes` for AES keys (`GetKey`, `SaveKey`). + - `ICryptoNetRsa` for RSA keys (`GetKey`, `SaveKey`). +- **Utility functions** were consolidated into `ExtShared.ExtShared` (byte array comparison, certificate lookup, etc.). +- Core encryption/decryption usage via `ICryptoNet` and `CryptoNetAes`/`CryptoNetRsa` with provided keys remains largely the same. + +After updating types and method names as outlined, you should be able to build against **v3.4.3** and run the AES and RSA examples successfully. diff --git a/docs/toc.yml b/docs/toc.yml index d7e9ea8..617d121 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -1,4 +1,8 @@ - name: Introduction href: introduction.md - name: Getting Started - href: getting-started.md \ No newline at end of file + href: getting-started.md +- name: Migration Guide from v2.x to v3.x + href: migration.md +- name: Examples of Use + href: examples.md \ No newline at end of file diff --git a/docs/tooling.md b/docs/tooling.md new file mode 100644 index 0000000..d6cb886 --- /dev/null +++ b/docs/tooling.md @@ -0,0 +1,150 @@ +# Developer Tooling and Release Guide + +This document describes the recommended tooling, local workflows and scripts for building, testing, documenting and releasing the CryptoNet repository. + +Note: script and IDE actions are shown as-is; when running scripts from the repository root use the Scripts/ or repository root path as appropriate (for example: __run_docs.ps1__, __run_release.ps1__). + +--- + +## 1. Build & Test (local) + +You can build and run tests using any of the following: + +- Visual Studio: use __Build > Build Solution__ and the Test Explorer. +- Visual Studio Code: use the integrated terminal or Test Explorer extensions. +- dotnet CLI: + +~~~powershell +dotnet build +dotnet test --configuration Release --no-build +~~~ + +There are convenience PowerShell scripts in the repository root (or the Scripts folder): + +~~~powershell +# Build & test wrapper +.\run_build_and_test.ps1 # if present +# Docker-based build +.\run_docker_build.ps1 +~~~ + +Docker (from solution folder): + +~~~ +docker build . --file .\Dockerfile --tag cryptonet-service:latest +~~~ + +Or use the preserved PowerShell wrapper: + +~~~powershell +./run_docker_build.ps1 +~~~ + +--- + +## 2. Release + +Use the release helper script to create preview or production releases: + +Preview: +~~~powershell +.\run_release.ps1 -VersionNumber 3.0.0 -IsPreview $true +~~~ + +Production: +~~~powershell +.\run_release.ps1 -VersionNumber 3.0.0 -IsPreview $false +~~~ + +Tags must conform to the version format expected by CI (see CONTRIBUTING.md and release scripts). + +--- + +## 3. Documentation (DocFX) + +Documentation is generated with DocFX and published by CI (pipeline `5-static.yml`). To work with docs locally: + +- Install DocFX (one-time): +~~~ +dotnet tool install -g docfx +~~~ + +- Generate docs locally: +~~~powershell +.\run_docs.ps1 +~~~ + +This script typically cleans, builds the code with XML docs, runs DocFX and serves the site locally. + +### Update `index.md` from `README.md` + +To sync the landing page: +~~~powershell +.\run_update_index.ps1 +~~~ + +The update script will add the required YAML front matter and append README content to `index.md`. + +--- + +## 4. Code Coverage + +Quick steps to run coverage locally (Windows PowerShell): + +~~~powershell +.\run_codecoverage.ps1 +# or +cd .\CryptoNet.UnitTests\ +dotnet test --collect:"XPlat Code Coverage" +reportgenerator -reports:"**/coverage.cobertura.xml" -targetdir:coverage-report +~~~ + +If the test project is missing coverage collectors, add them once: + +~~~bash +cd .\CryptoNet.UnitTests\ +dotnet add package coverlet.collector +dotnet add package coverlet.msbuild +dotnet tool install --global dotnet-reportgenerator-globaltool +~~~ + +--- + +## 5. Scripts & Paths + +Common scripts referenced in this repo: + +- __run_docs.ps1__ — generate documentation with DocFX. +- __run_release.ps1__ — create a release tag and invoke CI release workflows. +- __run_codecoverage.ps1__ — run tests and generate coverage reports. +- __run_update_index.ps1__ — synchronize README -> index.md for documentation landing. + +Scripts are typically in the Scripts/ directory or repository root. Check the script header for exact behavior and required parameters. + +--- + +## 6. Notes & Best Practices + +- Doc generation depends on XML comments. Ensure public APIs include XML `` tags before generating docs. +- The project uses Central Package Management (Directory.Packages.props). Do not hardcode package versions in individual .csproj files. +- Core libraries target .NET Standard 2.0 — avoid .NET 8+ APIs in those projects. +- Build and test locally before opening a PR. CI is configured to treat warnings as errors. + +--- + +## 7. Quick Local Setup Checklist + +~~~text +[ ] Install .NET SDK(s) required by the repository +[ ] Install DocFX tool: dotnet tool install -g docfx +[ ] Install report generator tool: dotnet tool install --global dotnet-reportgenerator-globaltool +[ ] Run dotnet build and dotnet test locally +[ ] Run .\run_docs.ps1 to validate documentation generation +[ ] Run .\run_codecoverage.ps1 to validate coverage generation (optional) +~~~ + +--- + +If you want, I can: +- apply the same formatting and script-name convention to other docs (for example `docs/examples.md`), or +- create a small PR with these changes. \ No newline at end of file