用戶掃描發票生成 PDF 上傳,服務端需驗證文件未被篡改。傳統數字簽名/水印方案較重,本文實現輕量級「尾部追加 MD5」方案。
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public static class PdfIntegrity
{
// 客戶端:簽名並追加
public static string Sign(string filePath, string secretKey)
{
byte[] pdfBytes = File.ReadAllBytes(filePath);
string md5 = ComputeMd5(pdfBytes, 0, pdfBytes.Length);
md5 = ComputeMd5(Encoding.ASCII.GetBytes(md5 + secretKey)); // 加鹽
byte[] md5Bytes = Encoding.ASCII.GetBytes(md5);
File.WriteAllBytes(filePath, pdfBytes.Concat(md5Bytes).ToArray());
return md5;
}
// 服務端:驗證
public static bool Verify(string filePath, string secretKey)
{
try
{
byte[] allBytes = File.ReadAllBytes(filePath);
if (allBytes.Length < 32) return false;
byte[] pdfBytes = new byte[allBytes.Length - 32];
Array.Copy(allBytes, pdfBytes, pdfBytes.Length);
byte[] storedMd5Bytes = new byte[32];
Array.Copy(allBytes, pdfBytes.Length, storedMd5Bytes, 0, 32);
string calcMd5 = ComputeMd5(pdfBytes, 0, pdfBytes.Length);
calcMd5 = ComputeMd5(Encoding.ASCII.GetBytes(calcMd5 + secretKey));
string storedMd5 = Encoding.ASCII.GetString(storedMd5Bytes);
return calcMd5 == storedMd5;
}
catch { return false; }
}
private static string ComputeMd5(byte[] buffer, int offset, int count)
{
using (MD5 md5 = MD5.Create())
{
byte[] hash = md5.ComputeHash(buffer, offset, count);
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}
private static string ComputeMd5(byte[] buffer) => ComputeMd5(buffer, 0, buffer.Length);
}
secretKey 防止第三方偽造 MD5,即使知道算法也無法生成正確簽名。FileStream 分塊計算 MD5,避免一次性載入內存。| 威脅 | 防護程度 | 說明 |
|---|---|---|
| 內容篡改 | 高 | 任何字節變動導致 MD5 不匹配 |
| 截斷/追加攻擊 | 中 | 攻擊者若不知 secretKey 無法重新計算尾部 MD5 |
| 碰撞攻擊 | 低 | MD5 已知碰撞漏洞,高安全場景建議升級 SHA-256 |
| 重放攻擊 | 無 | 未包含時間戳/隨機數,同一文件可重複上傳 |