//----------------------------------------------------------------------- // // Copyright (c) lanedirt. All rights reserved. // Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. // //----------------------------------------------------------------------- namespace AliasVault.Cryptography.Server; using System.Security.Cryptography; using System.Text; using System.Text.Json; /// /// RSA/AES and Argon2id encryption methods. /// public static class Encryption { /// /// Generates a random symmetric key for use with AES-256. /// /// A 256-bit (32-byte) random key as a byte array. public static byte[] GenerateRandomSymmetricKey() { return RandomNumberGenerator.GetBytes(32); // 256 bits } /// /// Encrypts a symmetric key using an RSA public key. /// /// The symmetric key to encrypt. /// The RSA public key in JWK format. /// The encrypted symmetric key as a base64-encoded string. public static string EncryptSymmetricKeyWithRsa(byte[] symmetricKey, string publicKey) { using (var rsa = RSA.Create()) { ImportPublicKey(rsa, publicKey); rsa.KeySize = 2048; var rsaParams = RSAEncryptionPadding.OaepSHA256; byte[] encryptedKey = rsa.Encrypt(symmetricKey, rsaParams); return Convert.ToBase64String(encryptedKey); } } /// /// Decrypts an encrypted symmetric key using an RSA private key. /// /// The encrypted symmetric key as ciphertext. /// The RSA private key in JWK format. /// The encrypted symmetric key as a base64-encoded string. public static byte[] DecryptSymmetricKeyWithRsa(string ciphertext, string privateKey) { using var rsa = RSA.Create(); ImportPrivateKey(rsa, privateKey); rsa.KeySize = 2048; var rsaParams = RSAEncryptionPadding.OaepSHA256; byte[] cipherBytes = Convert.FromBase64String(ciphertext); return rsa.Decrypt(cipherBytes, rsaParams); } /// /// SymmetricEncrypt a plaintext string using AES-256 GCM. /// /// The plaintext string. /// Key to use for encryption (must be 32 bytes for AES-256). /// The encrypted string (ciphertext). public static string SymmetricEncrypt(string plaintext, byte[] key) { var encryptedBytes = SymmetricEncrypt(Encoding.UTF8.GetBytes(plaintext), key); return Convert.ToBase64String(encryptedBytes); } /// /// SymmetricEncrypt a byte array using AES-256 GCM. /// /// The plain byte array. /// Key to use for encryption (must be 32 bytes for AES-256). /// The encrypted string (ciphertext). public static byte[] SymmetricEncrypt(byte[] plainBytes, byte[] key) { byte[] nonce = new byte[AesGcm.NonceByteSizes.MaxSize]; // 12 bytes RandomNumberGenerator.Fill(nonce); byte[] ciphertext = new byte[plainBytes.Length]; byte[] tag = new byte[AesGcm.TagByteSizes.MaxSize]; // 16 bytes using var aesGcm = new AesGcm(key, AesGcm.TagByteSizes.MaxSize); aesGcm.Encrypt(nonce, plainBytes, ciphertext, tag); // Combine nonce + ciphertext + tag byte[] result = new byte[nonce.Length + ciphertext.Length + tag.Length]; Buffer.BlockCopy(nonce, 0, result, 0, nonce.Length); Buffer.BlockCopy(ciphertext, 0, result, nonce.Length, ciphertext.Length); Buffer.BlockCopy(tag, 0, result, nonce.Length + ciphertext.Length, tag.Length); return result; } /// /// SymmetricDecrypt a ciphertext string using AES-256 GCM. /// /// The encrypted string (ciphertext). /// The key used to originally encrypt the string. /// The original plaintext string. public static string SymmetricDecrypt(string ciphertext, byte[] key) { var plainBytes = SymmetricDecrypt(Convert.FromBase64String(ciphertext), key); return Encoding.UTF8.GetString(plainBytes).TrimEnd('\0'); } /// /// SymmetricDecrypt a cipher byte array using AES-256 GCM. /// /// The encrypted byte array (cipherBytes). /// The key used to originally encrypt the string. /// The original plaintext string. public static byte[] SymmetricDecrypt(byte[] encryptedBytes, byte[] key) { int nonceSize = AesGcm.NonceByteSizes.MaxSize; int tagSize = AesGcm.TagByteSizes.MaxSize; // Extract nonce, ciphertext, and tag byte[] nonce = new byte[nonceSize]; byte[] tag = new byte[tagSize]; byte[] ciphertext = new byte[encryptedBytes.Length - nonceSize - tagSize]; Buffer.BlockCopy(encryptedBytes, 0, nonce, 0, nonceSize); Buffer.BlockCopy(encryptedBytes, nonceSize, ciphertext, 0, ciphertext.Length); Buffer.BlockCopy(encryptedBytes, nonceSize + ciphertext.Length, tag, 0, tagSize); byte[] plaintext = new byte[ciphertext.Length]; using var aesGcm = new AesGcm(key, tagSize); aesGcm.Decrypt(nonce, ciphertext, tag, plaintext); return plaintext; } /// /// Imports a public key from JWK format into an RSA provider. /// /// The RSA provider to import the key into. /// The public key in JWK format. private static void ImportPublicKey(RSA rsa, string jwk) { var jwkObj = JsonSerializer.Deserialize(jwk); var n = Base64UrlDecode(jwkObj.GetProperty("n").GetString()!); var e = Base64UrlDecode(jwkObj.GetProperty("e").GetString()!); var rsaParameters = new RSAParameters { Modulus = n, Exponent = e, }; rsa.ImportParameters(rsaParameters); } /// /// Imports a private key from JWK format into an RSA provider. /// /// The RSA provider to import the key into. /// The private key in JWK format. private static void ImportPrivateKey(RSA rsa, string jwk) { var jwkObj = JsonSerializer.Deserialize(jwk); var n = Base64UrlDecode(jwkObj.GetProperty("n").GetString()!); var e = Base64UrlDecode(jwkObj.GetProperty("e").GetString()!); var d = Base64UrlDecode(jwkObj.GetProperty("d").GetString()!); var p = Base64UrlDecode(jwkObj.GetProperty("p").GetString()!); var q = Base64UrlDecode(jwkObj.GetProperty("q").GetString()!); var dp = Base64UrlDecode(jwkObj.GetProperty("dp").GetString()!); var dq = Base64UrlDecode(jwkObj.GetProperty("dq").GetString()!); var qi = Base64UrlDecode(jwkObj.GetProperty("qi").GetString()!); var rsaParameters = new RSAParameters { Modulus = n, Exponent = e, D = d, P = p, Q = q, DP = dp, DQ = dq, InverseQ = qi, }; rsa.ImportParameters(rsaParameters); } /// /// Decodes a Base64Url-encoded string to a byte array. /// /// The Base64Url-encoded string. /// The decoded byte array. private static byte[] Base64UrlDecode(string base64Url) { string padded = base64Url; switch (base64Url.Length % 4) { case 2: padded += "=="; break; case 3: padded += "="; break; } string base64 = padded.Replace("-", "+").Replace("_", "/"); return Convert.FromBase64String(base64); } }