first commit
This commit is contained in:
270
DCIT.Core/Utilities/HashUtility.cs
Normal file
270
DCIT.Core/Utilities/HashUtility.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user