更新加密方式

This commit is contained in:
Falcon 2025-11-03 15:08:44 +08:00
parent 0963b55fe9
commit a3457332df
7 changed files with 260 additions and 194 deletions

View File

@ -0,0 +1,173 @@
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;
}
}
}
}

View File

@ -0,0 +1,39 @@
using System.Text.Json;
namespace PrivateBox.Encryption
{
/// <summary>
/// v1版本加密服务支持增加版本json格式存放
/// </summary>
public class AesEncryptionServiceV1:AesEncryptionService
{
/// <summary>
/// 加密版本
/// </summary>
protected virtual string Ver => "v1";
public override string Encrypt(string plainText,string key) {
var b = base.Encrypt(plainText,key);
var sp = b.Split("$",2);
var model = new EncryptionModel {
Ver = Ver,
Base64 = sp[0],
Hash = sp[1],
};
return JsonSerializer.Serialize(model);
}
public override string Decrypt(string cipherText,string key) {
try {
var modelStr = JsonSerializer.Deserialize<EncryptionModel>(cipherText);
if(modelStr == null) {
throw new Exception("json解码失败");
}
cipherText = $"{modelStr.Base64}${modelStr.Hash}";
}
catch(Exception) {
}
return base.Decrypt(cipherText,key);
}
}
}

View File

@ -0,0 +1,21 @@
namespace PrivateBox.Encryption
{
/// <summary>
/// 加密存储模型
/// </summary>
public class EncryptionModel
{
/// <summary>
/// 加密存储版本
/// </summary>
public string? Ver { get; set; }
/// <summary>
/// 加密后base编码
/// </summary>
public string? Base64 { get; set; }
/// <summary>
/// 哈希值
/// </summary>
public string? Hash { get; set; }
}
}

View File

@ -0,0 +1,24 @@
namespace PrivateBox.Encryption
{
/// <summary>
/// 加密解密服务接口
/// </summary>
public interface IEncryptionService
{
/// <summary>
/// 加密字符串
/// </summary>
/// <param name="plainText">原始字符串</param>
/// <param name="key">加密密钥(任意长度)</param>
/// <returns>加密后的Base64字符串包含盐值格式[加密数据][盐值]</returns>
string Encrypt(string plainText,string key);
/// <summary>
/// 解密字符串
/// </summary>
/// <param name="cipherText">加密的Base64字符串包含盐值</param>
/// <param name="key">解密密钥(与加密密钥相同)</param>
/// <returns>解密后的原始字符串</returns>
string Decrypt(string cipherText,string key);
}
}

View File

@ -1,193 +0,0 @@
using System.Security.Cryptography;
using System.Text;
/// <summary>
/// 加密解密服务接口
/// </summary>
public interface IEncryptionService
{
/// <summary>
/// 加密字符串
/// </summary>
/// <param name="plainText">原始字符串</param>
/// <param name="key">加密密钥(任意长度)</param>
/// <returns>加密后的Base64字符串包含盐值格式[加密数据][盐值]</returns>
string Encrypt(string plainText,string key);
/// <summary>
/// 解密字符串
/// </summary>
/// <param name="cipherText">加密的Base64字符串包含盐值</param>
/// <param name="key">解密密钥(与加密密钥相同)</param>
/// <returns>解密后的原始字符串</returns>
string Decrypt(string cipherText,string key);
}
/// <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 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 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;
}
}
}

View File

@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using PrivateBox.DataContext; using PrivateBox.DataContext;
using PrivateBox.Encryption;
namespace PrivateBox namespace PrivateBox
{ {

View File

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using PrivateBox.Encryption;
using System.Reflection; using System.Reflection;
namespace PrivateBox namespace PrivateBox
@ -30,7 +31,7 @@ namespace PrivateBox
services.AddSingleton(t); services.AddSingleton(t);
} }
} }
services.AddSingleton<IEncryptionService,AesEncryptionService>(); services.AddSingleton<IEncryptionService,AesEncryptionServiceV1>();
services.AddSingleton<DbContext>(); services.AddSingleton<DbContext>();
} }