48 lines
1.7 KiB
C#
48 lines
1.7 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Microsoft.AspNetCore.DataProtection;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
public class PhiEncryptionService : IPhiEncryptionService
|
|
{
|
|
private readonly IDataProtector _protector;
|
|
private readonly byte[] _searchKey;
|
|
|
|
public PhiEncryptionService(
|
|
IDataProtectionProvider provider,
|
|
IOptions<PhiEncryptionOptions> options)
|
|
{
|
|
var opts = options.Value;
|
|
_protector = provider.CreateProtector(opts.ProtectorPurpose);
|
|
_searchKey = Convert.FromBase64String(
|
|
Convert.ToBase64String(Encoding.UTF8.GetBytes(opts.SearchTokenKey))[..44]); // normalize dev key
|
|
}
|
|
|
|
public string Encrypt(string plaintext)
|
|
{
|
|
if (string.IsNullOrEmpty(plaintext))
|
|
return plaintext;
|
|
return _protector.Protect(plaintext);
|
|
}
|
|
|
|
public string Decrypt(string ciphertext)
|
|
{
|
|
if (string.IsNullOrEmpty(ciphertext))
|
|
return ciphertext;
|
|
if (!IsEncrypted(ciphertext))
|
|
return ciphertext; // migration transition: plaintext rows still readable
|
|
return _protector.Unprotect(ciphertext);
|
|
}
|
|
|
|
public bool IsEncrypted(string value) =>
|
|
value.StartsWith("CfDJ8", StringComparison.Ordinal) || // DataProtection prefix
|
|
value.Length > 50; // heuristic for protected payloads
|
|
|
|
public string ComputeNameSearchToken(string firstName, string lastName)
|
|
{
|
|
var normalized = $"{firstName.Trim().ToLowerInvariant()}|{lastName.Trim().ToLowerInvariant()}";
|
|
using var hmac = new HMACSHA256(_searchKey);
|
|
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(normalized));
|
|
return Convert.ToHexString(hash).ToLowerInvariant();
|
|
}
|
|
} |