using System; using System.IO; using System.Security.Cryptography; using System.Text; namespace DCIT.Core.Utilities { public static class HashUtility { private static readonly byte[] EncryptionKey = ComputeSha256Bytes("DCIT::DiffEncryptionKey::20260424"); private static readonly byte[] TamperKey = ComputeSha256Bytes("DCIT::TamperProtectionKey::20260424"); public static string ComputeSha256Base64(byte[] bytes) { using (var sha = SHA256.Create()) { return Convert.ToBase64String(sha.ComputeHash(bytes ?? new byte[0])); } } public static string ComputeSha256Base64(string value) { return ComputeSha256Base64(Encoding.UTF8.GetBytes(value ?? string.Empty)); } public static string ComputeFileSha256Base64(string path) { using (var stream = File.OpenRead(path)) using (var sha = SHA256.Create()) { return Convert.ToBase64String(sha.ComputeHash(stream)); } } public static string ComputeHmacBase64(byte[] bytes) { using (var hmac = new HMACSHA256(TamperKey)) { return Convert.ToBase64String(hmac.ComputeHash(bytes ?? new byte[0])); } } public static string ComputeFileHmacBase64(string path) { using (var stream = File.OpenRead(path)) using (var hmac = new HMACSHA256(TamperKey)) { return Convert.ToBase64String(hmac.ComputeHash(stream)); } } public static EncryptedPayload Encrypt(byte[] plaintextBytes) { var iv = new byte[16]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(iv); } byte[] cipherBytes; using (var aes = Aes.Create()) { aes.Key = EncryptionKey; aes.IV = iv; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; using (var encryptor = aes.CreateEncryptor()) { cipherBytes = encryptor.TransformFinalBlock(plaintextBytes ?? new byte[0], 0, (plaintextBytes ?? new byte[0]).Length); } } var tagInput = Combine(iv, cipherBytes); using (var hmac = new HMACSHA256(TamperKey)) { return new EncryptedPayload { CiphertextBase64 = Convert.ToBase64String(cipherBytes), NonceBase64 = Convert.ToBase64String(iv), TagBase64 = Convert.ToBase64String(hmac.ComputeHash(tagInput)), }; } } public static EncryptedPayloadHeader EncryptStream(Stream plaintextStream, Stream ciphertextStream) { if (plaintextStream == null) { throw new ArgumentNullException("plaintextStream"); } if (ciphertextStream == null) { throw new ArgumentNullException("ciphertextStream"); } if (!ciphertextStream.CanSeek) { throw new ArgumentException("密文流必须支持 Seek 以便计算防篡改标签。", "ciphertextStream"); } var iv = new byte[16]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(iv); } using (var aes = Aes.Create()) { aes.Key = EncryptionKey; aes.IV = iv; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; using (var encryptor = aes.CreateEncryptor()) using (var cryptoStream = new CryptoStream(ciphertextStream, encryptor, CryptoStreamMode.Write, leaveOpen: true)) { plaintextStream.CopyTo(cryptoStream); cryptoStream.FlushFinalBlock(); } } ciphertextStream.Position = 0; using (var hmac = new HMACSHA256(TamperKey)) { hmac.TransformBlock(iv, 0, iv.Length, iv, 0); var buffer = new byte[65536]; int read; while ((read = ciphertextStream.Read(buffer, 0, buffer.Length)) > 0) { hmac.TransformBlock(buffer, 0, read, buffer, 0); } hmac.TransformFinalBlock(new byte[0], 0, 0); return new EncryptedPayloadHeader { NonceBase64 = Convert.ToBase64String(iv), TagBase64 = Convert.ToBase64String(hmac.Hash), }; } } public static byte[] Decrypt(string ciphertextBase64, string nonceBase64, string tagBase64) { var cipherBytes = Convert.FromBase64String(ciphertextBase64 ?? string.Empty); var iv = Convert.FromBase64String(nonceBase64 ?? string.Empty); var providedTag = Convert.FromBase64String(tagBase64 ?? string.Empty); var expectedTag = Convert.FromBase64String(ComputeHmacBase64(Combine(iv, cipherBytes))); if (!FixedTimeEquals(providedTag, expectedTag)) { throw new CryptographicException("差异载荷验证失败。"); } using (var aes = Aes.Create()) { aes.Key = EncryptionKey; aes.IV = iv; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; using (var decryptor = aes.CreateDecryptor()) { return decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length); } } } public static void DecryptStream(Stream cipherStream, Stream plaintextStream, byte[] iv, byte[] expectedTag) { if (cipherStream == null) { throw new ArgumentNullException("cipherStream"); } if (plaintextStream == null) { throw new ArgumentNullException("plaintextStream"); } if (!cipherStream.CanSeek) { throw new ArgumentException("密文流必须支持 Seek 以便校验防篡改标签。", "cipherStream"); } using (var hmac = new HMACSHA256(TamperKey)) { hmac.TransformBlock(iv ?? new byte[0], 0, (iv ?? new byte[0]).Length, iv ?? new byte[0], 0); cipherStream.Position = 0; var buffer = new byte[65536]; int read; while ((read = cipherStream.Read(buffer, 0, buffer.Length)) > 0) { hmac.TransformBlock(buffer, 0, read, buffer, 0); } hmac.TransformFinalBlock(new byte[0], 0, 0); if (!FixedTimeEquals(hmac.Hash, expectedTag ?? new byte[0])) { throw new CryptographicException("差异载荷防篡改校验失败。"); } } cipherStream.Position = 0; using (var aes = Aes.Create()) { aes.Key = EncryptionKey; aes.IV = iv; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; using (var decryptor = aes.CreateDecryptor()) using (var cryptoStream = new CryptoStream(cipherStream, decryptor, CryptoStreamMode.Read, leaveOpen: true)) { cryptoStream.CopyTo(plaintextStream); } } } private static byte[] ComputeSha256Bytes(string value) { using (var sha = SHA256.Create()) { return sha.ComputeHash(Encoding.UTF8.GetBytes(value ?? string.Empty)); } } private static bool FixedTimeEquals(byte[] left, byte[] right) { if (left == null || right == null || left.Length != right.Length) { return false; } var diff = 0; for (var index = 0; index < left.Length; index++) { diff |= left[index] ^ right[index]; } return diff == 0; } private static byte[] Combine(byte[] left, byte[] right) { left = left ?? new byte[0]; right = right ?? new byte[0]; var bytes = new byte[left.Length + right.Length]; Buffer.BlockCopy(left, 0, bytes, 0, left.Length); Buffer.BlockCopy(right, 0, bytes, left.Length, right.Length); return bytes; } } public class EncryptedPayload { public string CiphertextBase64 { get; set; } public string NonceBase64 { get; set; } public string TagBase64 { get; set; } } public class EncryptedPayloadHeader { public string NonceBase64 { get; set; } public string TagBase64 { get; set; } } }