From a3457332dff196cbe4cffae2a228634bb6533b82 Mon Sep 17 00:00:00 2001
From: Falcon <12919280+falconfly@user.noreply.gitee.com>
Date: Mon, 3 Nov 2025 15:08:44 +0800
Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=8A=A0=E5=AF=86=E6=96=B9?=
=?UTF-8?q?=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
PrivateBox/Encryption/AesEncryptionService.cs | 173 ++++++++++++++++
.../Encryption/AesEncryptionServiceV1.cs | 39 ++++
PrivateBox/Encryption/EncryptionModel.cs | 21 ++
PrivateBox/Encryption/IEncryptionService.cs | 24 +++
PrivateBox/EncryptionService.cs | 193 ------------------
PrivateBox/Forms/MainForm.cs | 1 +
PrivateBox/Program.cs | 3 +-
7 files changed, 260 insertions(+), 194 deletions(-)
create mode 100644 PrivateBox/Encryption/AesEncryptionService.cs
create mode 100644 PrivateBox/Encryption/AesEncryptionServiceV1.cs
create mode 100644 PrivateBox/Encryption/EncryptionModel.cs
create mode 100644 PrivateBox/Encryption/IEncryptionService.cs
delete mode 100644 PrivateBox/EncryptionService.cs
diff --git a/PrivateBox/Encryption/AesEncryptionService.cs b/PrivateBox/Encryption/AesEncryptionService.cs
new file mode 100644
index 0000000..aca4852
--- /dev/null
+++ b/PrivateBox/Encryption/AesEncryptionService.cs
@@ -0,0 +1,173 @@
+using System.Security.Cryptography;
+using System.Text;
+
+namespace PrivateBox.Encryption
+{
+ ///
+ /// AES加密解密服务实现(动态盐值+密钥派生)
+ ///
+ 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();
+ }
+
+ ///
+ /// 基于PBKDF2算法派生密钥
+ ///
+ private byte[] DeriveKey(string rawKey,byte[] salt) {
+ using(Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(
+ rawKey,
+ salt,
+ Iterations,
+ HashAlgorithm)) {
+ return deriveBytes.GetBytes(KeySize);
+ }
+ }
+
+ ///
+ /// 用于hash验证的key,不可以修改,修改会导致验证失败
+ ///
+ 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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/PrivateBox/Encryption/AesEncryptionServiceV1.cs b/PrivateBox/Encryption/AesEncryptionServiceV1.cs
new file mode 100644
index 0000000..4ae2abe
--- /dev/null
+++ b/PrivateBox/Encryption/AesEncryptionServiceV1.cs
@@ -0,0 +1,39 @@
+using System.Text.Json;
+
+namespace PrivateBox.Encryption
+{
+ ///
+ /// v1版本加密服务,支持增加版本,json格式存放
+ ///
+ public class AesEncryptionServiceV1:AesEncryptionService
+ {
+ ///
+ /// 加密版本
+ ///
+ 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(cipherText);
+ if(modelStr == null) {
+ throw new Exception("json解码失败!");
+ }
+ cipherText = $"{modelStr.Base64}${modelStr.Hash}";
+ }
+ catch(Exception) {
+ }
+ return base.Decrypt(cipherText,key);
+ }
+ }
+}
\ No newline at end of file
diff --git a/PrivateBox/Encryption/EncryptionModel.cs b/PrivateBox/Encryption/EncryptionModel.cs
new file mode 100644
index 0000000..a8b4eaa
--- /dev/null
+++ b/PrivateBox/Encryption/EncryptionModel.cs
@@ -0,0 +1,21 @@
+namespace PrivateBox.Encryption
+{
+ ///
+ /// 加密存储模型
+ ///
+ public class EncryptionModel
+ {
+ ///
+ /// 加密存储版本
+ ///
+ public string? Ver { get; set; }
+ ///
+ /// 加密后base编码
+ ///
+ public string? Base64 { get; set; }
+ ///
+ /// 哈希值
+ ///
+ public string? Hash { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/PrivateBox/Encryption/IEncryptionService.cs b/PrivateBox/Encryption/IEncryptionService.cs
new file mode 100644
index 0000000..7a036df
--- /dev/null
+++ b/PrivateBox/Encryption/IEncryptionService.cs
@@ -0,0 +1,24 @@
+namespace PrivateBox.Encryption
+{
+ ///
+ /// 加密解密服务接口
+ ///
+ public interface IEncryptionService
+ {
+ ///
+ /// 加密字符串
+ ///
+ /// 原始字符串
+ /// 加密密钥(任意长度)
+ /// 加密后的Base64字符串(包含盐值,格式:[加密数据][盐值])
+ string Encrypt(string plainText,string key);
+
+ ///
+ /// 解密字符串
+ ///
+ /// 加密的Base64字符串(包含盐值)
+ /// 解密密钥(与加密密钥相同)
+ /// 解密后的原始字符串
+ string Decrypt(string cipherText,string key);
+ }
+}
\ No newline at end of file
diff --git a/PrivateBox/EncryptionService.cs b/PrivateBox/EncryptionService.cs
deleted file mode 100644
index 42c92b2..0000000
--- a/PrivateBox/EncryptionService.cs
+++ /dev/null
@@ -1,193 +0,0 @@
-using System.Security.Cryptography;
-using System.Text;
-
-///
-/// 加密解密服务接口
-///
-public interface IEncryptionService
-{
- ///
- /// 加密字符串
- ///
- /// 原始字符串
- /// 加密密钥(任意长度)
- /// 加密后的Base64字符串(包含盐值,格式:[加密数据][盐值])
- string Encrypt(string plainText,string key);
-
- ///
- /// 解密字符串
- ///
- /// 加密的Base64字符串(包含盐值)
- /// 解密密钥(与加密密钥相同)
- /// 解密后的原始字符串
- string Decrypt(string cipherText,string key);
-}
-
-
-///
-/// AES加密解密服务实现(动态盐值+密钥派生)
-///
-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();
- }
-
- ///
- /// 基于PBKDF2算法派生密钥
- ///
- private byte[] DeriveKey(string rawKey,byte[] salt) {
- using(Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(
- rawKey,
- salt,
- Iterations,
- HashAlgorithm)) {
- return deriveBytes.GetBytes(KeySize);
- }
- }
-
- ///
- /// 用于hash验证的key,不可以修改,修改会导致验证失败
- ///
- 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;
- }
- }
-}
\ No newline at end of file
diff --git a/PrivateBox/Forms/MainForm.cs b/PrivateBox/Forms/MainForm.cs
index 073d3af..9a4a3ab 100644
--- a/PrivateBox/Forms/MainForm.cs
+++ b/PrivateBox/Forms/MainForm.cs
@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using PrivateBox.DataContext;
+using PrivateBox.Encryption;
namespace PrivateBox
{
diff --git a/PrivateBox/Program.cs b/PrivateBox/Program.cs
index 627a1d9..497cea8 100644
--- a/PrivateBox/Program.cs
+++ b/PrivateBox/Program.cs
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
+using PrivateBox.Encryption;
using System.Reflection;
namespace PrivateBox
@@ -30,7 +31,7 @@ namespace PrivateBox
services.AddSingleton(t);
}
}
- services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
}