feat: reorganize admin llm settings
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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 raw = configuration["Secrets:EncryptionKey"];
|
||||
byte[]? key = null;
|
||||
try { key = string.IsNullOrWhiteSpace(raw) ? null : Convert.FromBase64String(raw); }
|
||||
catch (FormatException) { }
|
||||
if (key?.Length != 32)
|
||||
throw new InvalidOperationException(
|
||||
"请通过 Secrets__EncryptionKey 配置 base64 编码的 32 字节密钥后再保存 API Key");
|
||||
return key;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user