//-----------------------------------------------------------------------
//
// 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);
}
}