diff --git a/NEW/src/JdeScoping.Infrastructure/Security/RsaKeyService.cs b/NEW/src/JdeScoping.Infrastructure/Security/RsaKeyService.cs
new file mode 100644
index 0000000..609550e
--- /dev/null
+++ b/NEW/src/JdeScoping.Infrastructure/Security/RsaKeyService.cs
@@ -0,0 +1,50 @@
+// NEW/src/JdeScoping.Infrastructure/Security/RsaKeyService.cs
+using System.Security.Cryptography;
+using JdeScoping.Core.Interfaces;
+
+namespace JdeScoping.Infrastructure.Security;
+
+///
+/// RSA key service that auto-generates and persists keys.
+///
+public class RsaKeyService : IRsaKeyService, IDisposable
+{
+ private readonly RSA _rsa;
+
+ ///
+ /// Creates a new RSA key service.
+ ///
+ /// Path to persist the private key
+ public RsaKeyService(string keyFilePath)
+ {
+ _rsa = RSA.Create(2048);
+
+ if (File.Exists(keyFilePath))
+ {
+ var keyBytes = File.ReadAllBytes(keyFilePath);
+ _rsa.ImportRSAPrivateKey(keyBytes, out _);
+ }
+ else
+ {
+ var privateKey = _rsa.ExportRSAPrivateKey();
+ var directory = Path.GetDirectoryName(keyFilePath);
+ if (!string.IsNullOrEmpty(directory))
+ Directory.CreateDirectory(directory);
+ File.WriteAllBytes(keyFilePath, privateKey);
+ }
+ }
+
+ ///
+ public string GetPublicKeyPem()
+ => _rsa.ExportSubjectPublicKeyInfoPem();
+
+ ///
+ public byte[] Decrypt(byte[] ciphertext)
+ => _rsa.Decrypt(ciphertext, RSAEncryptionPadding.OaepSHA256);
+
+ public void Dispose()
+ {
+ _rsa.Dispose();
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/NEW/tests/JdeScoping.Infrastructure.Tests/Security/RsaKeyServiceTests.cs b/NEW/tests/JdeScoping.Infrastructure.Tests/Security/RsaKeyServiceTests.cs
new file mode 100644
index 0000000..9512a34
--- /dev/null
+++ b/NEW/tests/JdeScoping.Infrastructure.Tests/Security/RsaKeyServiceTests.cs
@@ -0,0 +1,96 @@
+// NEW/tests/JdeScoping.Infrastructure.Tests/Security/RsaKeyServiceTests.cs
+using JdeScoping.Core.Interfaces;
+using JdeScoping.Infrastructure.Security;
+using Shouldly;
+
+namespace JdeScoping.Infrastructure.Tests.Security;
+
+public class RsaKeyServiceTests : IDisposable
+{
+ private readonly string _testKeyPath;
+
+ public RsaKeyServiceTests()
+ {
+ _testKeyPath = Path.Combine(Path.GetTempPath(), $"test-rsa-key-{Guid.NewGuid()}.bin");
+ }
+
+ public void Dispose()
+ {
+ if (File.Exists(_testKeyPath))
+ File.Delete(_testKeyPath);
+ }
+
+ [Fact]
+ public void GetPublicKeyPem_ReturnsValidPemFormat()
+ {
+ // Arrange
+ var service = new RsaKeyService(_testKeyPath);
+
+ // Act
+ var pem = service.GetPublicKeyPem();
+
+ // Assert
+ pem.ShouldStartWith("-----BEGIN PUBLIC KEY-----");
+ pem.ShouldEndWith("-----END PUBLIC KEY-----");
+ }
+
+ [Fact]
+ public void Constructor_WhenKeyFileMissing_GeneratesAndPersistsKey()
+ {
+ // Arrange - ensure file doesn't exist
+ File.Exists(_testKeyPath).ShouldBeFalse();
+
+ // Act
+ _ = new RsaKeyService(_testKeyPath);
+
+ // Assert
+ File.Exists(_testKeyPath).ShouldBeTrue();
+ new FileInfo(_testKeyPath).Length.ShouldBeGreaterThan(0);
+ }
+
+ [Fact]
+ public void Constructor_WhenKeyFileExists_LoadsSameKey()
+ {
+ // Arrange - create service to generate key
+ var service1 = new RsaKeyService(_testKeyPath);
+ var publicKey1 = service1.GetPublicKeyPem();
+
+ // Act - create new service instance
+ var service2 = new RsaKeyService(_testKeyPath);
+ var publicKey2 = service2.GetPublicKeyPem();
+
+ // Assert - same key loaded
+ publicKey2.ShouldBe(publicKey1);
+ }
+
+ [Fact]
+ public void Decrypt_WithValidCiphertext_ReturnsPlaintext()
+ {
+ // Arrange
+ var service = new RsaKeyService(_testKeyPath);
+ var plaintext = "Hello, World!"u8.ToArray();
+
+ // Encrypt using public key (simulating what client does)
+ using var rsa = System.Security.Cryptography.RSA.Create();
+ rsa.ImportFromPem(service.GetPublicKeyPem());
+ var ciphertext = rsa.Encrypt(plaintext, System.Security.Cryptography.RSAEncryptionPadding.OaepSHA256);
+
+ // Act
+ var decrypted = service.Decrypt(ciphertext);
+
+ // Assert
+ decrypted.ShouldBe(plaintext);
+ }
+
+ [Fact]
+ public void Decrypt_WithInvalidCiphertext_ThrowsCryptographicException()
+ {
+ // Arrange
+ var service = new RsaKeyService(_testKeyPath);
+ var invalidCiphertext = new byte[] { 1, 2, 3, 4, 5 };
+
+ // Act & Assert
+ Should.Throw(
+ () => service.Decrypt(invalidCiphertext));
+ }
+}