Add domestic vendor push infrastructure
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace MiaoJiZhang.Api.Services;
|
||||
|
||||
public sealed class PushTokenProtector(IConfiguration configuration)
|
||||
{
|
||||
private readonly byte[]? key = ReadKey(configuration["Push:TokenEncryptionKey"]);
|
||||
|
||||
public bool IsConfigured => key is { Length: 32 };
|
||||
|
||||
public string Protect(string value)
|
||||
{
|
||||
if (key is null)
|
||||
throw new InvalidOperationException("Push__TokenEncryptionKey must be a base64-encoded 32-byte key");
|
||||
|
||||
var plaintext = Encoding.UTF8.GetBytes(value);
|
||||
var nonce = RandomNumberGenerator.GetBytes(12);
|
||||
var tag = new byte[16];
|
||||
var ciphertext = new byte[plaintext.Length];
|
||||
using var aes = new AesGcm(key, tag.Length);
|
||||
aes.Encrypt(nonce, plaintext, ciphertext, tag);
|
||||
|
||||
var envelope = new byte[nonce.Length + tag.Length + ciphertext.Length];
|
||||
Buffer.BlockCopy(nonce, 0, envelope, 0, nonce.Length);
|
||||
Buffer.BlockCopy(tag, 0, envelope, nonce.Length, tag.Length);
|
||||
Buffer.BlockCopy(ciphertext, 0, envelope, nonce.Length + tag.Length, ciphertext.Length);
|
||||
return Convert.ToBase64String(envelope);
|
||||
}
|
||||
|
||||
public string Unprotect(string value)
|
||||
{
|
||||
if (key is null)
|
||||
throw new InvalidOperationException("Push__TokenEncryptionKey must be a base64-encoded 32-byte key");
|
||||
|
||||
var envelope = Convert.FromBase64String(value);
|
||||
if (envelope.Length < 29) throw new CryptographicException("Invalid push token envelope");
|
||||
var nonce = envelope.AsSpan(0, 12);
|
||||
var tag = envelope.AsSpan(12, 16);
|
||||
var ciphertext = envelope.AsSpan(28);
|
||||
var plaintext = new byte[ciphertext.Length];
|
||||
using var aes = new AesGcm(key, tag.Length);
|
||||
aes.Decrypt(nonce, ciphertext, tag, plaintext);
|
||||
return Encoding.UTF8.GetString(plaintext);
|
||||
}
|
||||
|
||||
public static string Hash(string value) =>
|
||||
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(value)));
|
||||
|
||||
public static string CreateUnbindToken() =>
|
||||
Convert.ToBase64String(RandomNumberGenerator.GetBytes(32))
|
||||
.TrimEnd('=').Replace('+', '-').Replace('/', '_');
|
||||
|
||||
private static byte[]? ReadKey(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return null;
|
||||
try
|
||||
{
|
||||
var parsed = Convert.FromBase64String(value);
|
||||
return parsed.Length == 32 ? parsed : null;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user