PrivateBox/PrivateBox/Encryption/AesEncryptionService.cs
2025-11-03 15:08:44 +08:00

173 lines
7.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Security.Cryptography;
using System.Text;
namespace PrivateBox.Encryption
{
/// <summary>
/// AES加密解密服务实现动态盐值+密钥派生)
/// </summary>
public class AesEncryptionService:IEncryptionService
{
// 配置参数(可通过依赖注入从配置文件读取)
private const int SaltSize = 16; // 盐值长度16字节推荐值
private const int KeySize = 16; // 派生密钥长度16字节 = AES-128
private const int Iterations = 10000; // 密钥派生迭代次数(平衡安全性和性能)
private static readonly HashAlgorithmName HashAlgorithm = HashAlgorithmName.SHA256;
public virtual string Encrypt(string plainText,string key) {
// 验证输入
if(string.IsNullOrEmpty(plainText))
throw new ArgumentException("原始字符串不能为空",nameof(plainText));
if(string.IsNullOrEmpty(key))
throw new ArgumentException("密钥不能为空",nameof(key));
// 1. 随机生成盐值(.NET 7+ 安全随机数生成)
byte[] salt = RandomNumberGenerator.GetBytes(SaltSize);
// 2. 从原始密钥和盐值派生AES密钥
byte[] derivedKey = DeriveKey(key,salt);
// 3. AES加密逻辑
byte[] encryptedData;
using(Aes aes = Aes.Create()) {
aes.Key = derivedKey;
aes.GenerateIV(); // 生成随机IV初始化向量
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
// 创建加密器
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key,aes.IV);
using(MemoryStream msEncrypt = new MemoryStream()) {
// 先写入IV解密时需要
msEncrypt.Write(aes.IV,0,aes.IV.Length);
// 写入加密数据
using(CryptoStream csEncrypt = new CryptoStream(msEncrypt,encryptor,CryptoStreamMode.Write))
using(StreamWriter swEncrypt = new StreamWriter(csEncrypt)) {
swEncrypt.Write(plainText);
}
encryptedData = msEncrypt.ToArray();
}
}
// 4. 拼接加密数据和盐值(格式:[加密数据][盐值]
byte[] result = new byte[encryptedData.Length + SaltSize];
Array.Copy(encryptedData,0,result,0,encryptedData.Length);
Array.Copy(salt,0,result,encryptedData.Length,SaltSize);
var (base64, hash) = GetHash(result);
return $"{base64}${hash}";
}
public virtual string Decrypt(string cipherText,string key) {
// 验证输入
if(string.IsNullOrEmpty(cipherText))
throw new ArgumentException("加密字符串不能为空",nameof(cipherText));
if(string.IsNullOrEmpty(key))
throw new ArgumentException("密钥不能为空",nameof(key));
var result = new StringBuilder();
if(!cipherText.Contains("$")) {
result.AppendLine("加密值不包含验证");
}
if(cipherText.Contains("$")) {
var sp = cipherText.Split("$");
cipherText = sp[0];
if(sp.Length < 2 || string.IsNullOrEmpty(sp[1])) {
result.AppendLine("存在验证,但是验证码错误!");
}
if(!ValidateBase64(sp[0],sp[1])) {
result.AppendLine("加密验证失败!信息可能被篡改!");
}
}
// 1. 转换Base64为字节数组
byte[] fullData;
try {
fullData = Convert.FromBase64String(cipherText);
}
catch(FormatException) {
throw new ArgumentException("加密字符串不是有效的Base64格式",nameof(cipherText));
}
// 2. 验证数据长度至少包含IV+盐值)
if(fullData.Length < 16 + SaltSize) // IV固定16字节 + 盐值长度
throw new ArgumentException("加密字符串无效(长度不足)",nameof(cipherText));
// 3. 提取盐值(从尾部提取)
byte[] salt = new byte[SaltSize];
Array.Copy(fullData,fullData.Length - SaltSize,salt,0,SaltSize);
// 4. 提取加密数据(排除尾部盐值)
byte[] encryptedData = new byte[fullData.Length - SaltSize];
Array.Copy(fullData,0,encryptedData,0,encryptedData.Length);
// 5. 从原始密钥和盐值派生AES密钥与加密时一致
byte[] derivedKey = DeriveKey(key,salt);
// 6. AES解密逻辑
using(Aes aes = Aes.Create()) {
aes.Key = derivedKey;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
// 提取IV加密数据的前16字节
byte[] iv = new byte[16];
Array.Copy(encryptedData,0,iv,0,iv.Length);
aes.IV = iv;
// 创建解密器
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key,aes.IV);
// 解密数据排除IV部分
using(MemoryStream msDecrypt = new MemoryStream(encryptedData,16,encryptedData.Length - 16))
using(CryptoStream csDecrypt = new CryptoStream(msDecrypt,decryptor,CryptoStreamMode.Read))
using(StreamReader srDecrypt = new StreamReader(csDecrypt)) {
result.AppendLine(srDecrypt.ReadToEnd());
}
}
return result.ToString();
}
/// <summary>
/// 基于PBKDF2算法派生密钥
/// </summary>
private byte[] DeriveKey(string rawKey,byte[] salt) {
using(Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(
rawKey,
salt,
Iterations,
HashAlgorithm)) {
return deriveBytes.GetBytes(KeySize);
}
}
/// <summary>
/// 用于hash验证的key不可以修改修改会导致验证失败
/// </summary>
private readonly string hashKey = "694699FF-7157-4DF2-ACD9-E60CCFCC6007";
private (string base64, string hash) GetHash(byte[] bytes) {
var hk = Encoding.UTF8.GetBytes(hashKey);
using var sha256 = new HMACSHA256(hk);
byte[] hmacBytes = sha256.ComputeHash(bytes);
string hmacStr = BitConverter.ToString(hmacBytes).Replace("-","").ToLower();
string base64 = Convert.ToBase64String(bytes);
return (base64, hmacStr);
}
private bool ValidateBase64(string base64String,string hash) {
try {
byte[] decodedData = Convert.FromBase64String(base64String);
var hk = Encoding.UTF8.GetBytes(hashKey);
using var sha256 = new HMACSHA256(hk);
byte[] actualHashBytes = sha256.ComputeHash(decodedData);
string actualHash = BitConverter.ToString(actualHashBytes).Replace("-","").ToLower();
return actualHash == hash;
}
catch(FormatException) {
return false;
}
}
}
}