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