Files

76 lines
2.7 KiB
C#

using System.Security.Cryptography;
using System.Text;
namespace MiaoJiZhang.Api.Services;
public sealed class LlmSecretProtector(IConfiguration configuration)
{
public const string ConfigKey = "llm.api_key_encrypted";
private const string Prefix = "v1";
public string Protect(string value)
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("API Key 不能为空", nameof(value));
var key = ReadEncryptionKey();
var nonce = RandomNumberGenerator.GetBytes(12);
var plaintext = Encoding.UTF8.GetBytes(value.Trim());
var ciphertext = new byte[plaintext.Length];
var tag = new byte[16];
using var aes = new AesGcm(key, tag.Length);
aes.Encrypt(nonce, plaintext, ciphertext, tag);
return string.Join(':', Prefix,
Convert.ToBase64String(nonce),
Convert.ToBase64String(ciphertext),
Convert.ToBase64String(tag));
}
public string Unprotect(string protectedValue)
{
var parts = protectedValue.Split(':');
if (parts.Length != 4 || parts[0] != Prefix)
throw new CryptographicException("不支持的密钥密文格式");
var nonce = Convert.FromBase64String(parts[1]);
var ciphertext = Convert.FromBase64String(parts[2]);
var tag = Convert.FromBase64String(parts[3]);
var plaintext = new byte[ciphertext.Length];
using var aes = new AesGcm(ReadEncryptionKey(), tag.Length);
aes.Decrypt(nonce, ciphertext, tag, plaintext);
return Encoding.UTF8.GetString(plaintext);
}
public bool TryUnprotect(string? protectedValue, out string value)
{
value = "";
if (string.IsNullOrWhiteSpace(protectedValue)) return false;
try
{
value = Unprotect(protectedValue);
return !string.IsNullOrWhiteSpace(value);
}
catch (Exception exception) when (
exception is ArgumentException or FormatException or
CryptographicException or InvalidOperationException)
{
return false;
}
}
public static string Mask(string value)
{
if (string.IsNullOrEmpty(value)) return "";
var suffixLength = Math.Min(4, value.Length);
return $"••••{value[^suffixLength..]}";
}
private byte[] ReadEncryptionKey()
{
var jwtSecret = configuration["Jwt:Secret"];
if (string.IsNullOrWhiteSpace(jwtSecret) || jwtSecret.Length < 32)
throw new InvalidOperationException("服务端 JWT 密钥配置无效,无法保护 API Key");
return HMACSHA256.HashData(
Encoding.UTF8.GetBytes(jwtSecret),
Encoding.UTF8.GetBytes("jizhi:llm-api-key-encryption:v1"));
}
}