CryptoHelper.cs 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. using System;
  2. using System.Security.Cryptography;
  3. using System.Text;
  4. namespace ivf_tl_Operate.Helpers
  5. {
  6. /// <summary>
  7. /// M5-02-1:凭据加密工具(DPAPI / ProtectedData,DataProtectionScope.LocalMachine)。
  8. /// 设计要点(以 M5 子计划 M5-02-1 为准):
  9. /// - 密文以前缀 <see cref="EncPrefix"/>("enc:") 标记,Base64 存于 App.config,便于区分明文(旧值)/密文。
  10. /// - 读取边界:IsEncrypted=true 则解密;否则视为旧明文,由调用方触发一次"读后回写为密文"迁移(幂等)。
  11. /// - 零外部密钥管理,与本机绑定,契合"本机凭据"语义。
  12. /// [D8] 若需跨机可迁移密钥(DPAPI LocalMachine 不可跨机),改用对称密钥+固定盐,待确认。
  13. /// [M7] 加解密往返一致性需运行环境验证(本地不可构建/运行)。
  14. /// </summary>
  15. public static class CryptoHelper
  16. {
  17. /// <summary>密文前缀标记,用于区分明文旧值与已加密值。</summary>
  18. public const string EncPrefix = "enc:";
  19. // 附加熵(与本机+应用绑定,非密钥;DPAPI 主密钥由 Windows 管理)。
  20. private static readonly byte[] Entropy = Encoding.UTF8.GetBytes("ivf_tl_operate_M5_02");
  21. /// <summary>
  22. /// 判断给定值是否已为本工具加密的密文(带 enc: 前缀)。空值视为未加密。
  23. /// </summary>
  24. public static bool IsEncrypted(string value)
  25. {
  26. return !string.IsNullOrEmpty(value) && value.StartsWith(EncPrefix, StringComparison.Ordinal);
  27. }
  28. /// <summary>
  29. /// 加密明文 → "enc:" + Base64(DPAPI密文)。空/已加密值原样返回(幂等)。
  30. /// </summary>
  31. public static string Encrypt(string plain)
  32. {
  33. if (string.IsNullOrEmpty(plain)) return plain;
  34. if (IsEncrypted(plain)) return plain; // 幂等:已是密文不再二次加密
  35. try
  36. {
  37. byte[] data = Encoding.UTF8.GetBytes(plain);
  38. byte[] cipher = ProtectedData.Protect(data, Entropy, DataProtectionScope.LocalMachine);
  39. return EncPrefix + Convert.ToBase64String(cipher);
  40. }
  41. catch
  42. {
  43. // 加密失败时不写入半成品,回退原值(由 [M7] 运行环境核查)。
  44. return plain;
  45. }
  46. }
  47. /// <summary>
  48. /// 解密 "enc:"+Base64 密文 → 明文。若入参为明文(旧值)则原样返回,便于平滑迁移。
  49. /// </summary>
  50. public static string Decrypt(string cipher)
  51. {
  52. if (string.IsNullOrEmpty(cipher)) return cipher;
  53. if (!IsEncrypted(cipher)) return cipher; // 旧明文:直接返回,调用方负责回写迁移
  54. try
  55. {
  56. string b64 = cipher.Substring(EncPrefix.Length);
  57. byte[] data = Convert.FromBase64String(b64);
  58. byte[] plain = ProtectedData.Unprotect(data, Entropy, DataProtectionScope.LocalMachine);
  59. return Encoding.UTF8.GetString(plain);
  60. }
  61. catch
  62. {
  63. // 解密失败(如跨机迁移 [D8])回退原始字符串,避免登录流程抛异常。
  64. return cipher;
  65. }
  66. }
  67. }
  68. }