Security, Cryptography, and Token Utilities
$ dotnet add package Plinth.SecuritySecurity, Cryptography, and Token Utilities
Provides production-ready cryptographic utilities for common security scenarios including data encryption, password hashing, and predictable hashing.
ISecureData provides symmetric encryption for protecting sensitive data at rest and in transit using AES-CBC with HMAC authentication.
using Plinth.Security.Crypto;
// Generate a 32-byte (256-bit) hex key (64 hex characters)
var encryptionKey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
var secureData = new SecureData(encryptionKey);
services.AddSingleton<ISecureData>(secureData);
Support multiple keys for seamless key rotation:
var secureData = new SecureData(
defaultKeyId: 1,
keyRing: [
(0, "old_key_64_hex_chars..."),
(1, "current_key_64_hex_chars..."), // default for new encryptions
(2, "future_key_64_hex_chars...")
]
);
services.AddSingleton<ISecureData>(secureData);
public class UserService
{
private readonly ISecureData _secureData;
public UserService(ISecureData secureData)
{
_secureData = secureData;
}
public async Task SaveSensitiveDataAsync(string ssn)
{
// Encrypt to Base64 string (suitable for database storage)
var encrypted = _secureData.EncryptToBase64(ssn);
// Or encrypt to hex string
var encryptedHex = _secureData.EncryptToHex(ssn);
// Or encrypt to byte array
var encryptedBytes = _secureData.Encrypt(ssn);
// Save encrypted value...
}
public async Task<string> GetSensitiveDataAsync()
{
// Retrieve encrypted value...
var encrypted = "...";
// Decrypt from Base64 string
var decrypted = _secureData.DecryptBase64ToString(encrypted);
// Or decrypt from hex string
var decryptedFromHex = _secureData.DecryptHexToString(encrypted);
// Or decrypt to byte array
var decryptedBytes = _secureData.DecryptBase64(encrypted);
return decrypted;
}
}
// Encrypt with specific key ID
var encrypted = _secureData.EncryptToBase64(data, keyId: 2);
// Decryption automatically uses the correct key based on the encrypted data
var decrypted = _secureData.DecryptBase64ToString(encrypted);IPasswordHasher provides secure password hashing using PBKDF2 with SHA-256, following OWASP recommendations.
using Plinth.Security.Crypto;
var passwordHasher = new PBKDF2PasswordHasher();
services.AddSingleton<IPasswordHasher>(passwordHasher);public class AuthService
{
private readonly IPasswordHasher _passwordHasher;
public AuthService(IPasswordHasher passwordHasher)
{
_passwordHasher = passwordHasher;
}
public async Task RegisterUserAsync(string email, string password)
{
// Hash the password (includes salt automatically)
var hashedPassword = _passwordHasher.HashPassword(password);
// Store hashedPassword in database...
// Example: "04a1b2c3d4e5f6..."
}
public async Task<bool> LoginAsync(string email, string password)
{
// Retrieve hashed password from database...
var storedHash = "...";
// Verify password
var isValid = _passwordHasher.VerifyPasswordHash(password, storedHash);
return isValid;
}
}The implementation supports multiple versions for backward compatibility:
The version is automatically detected during verification, allowing seamless migration.
IPredictableHasher creates deterministic hashes for sensitive data that needs to be searchable or checked for uniqueness (e.g., SSNs, credit card numbers).
using Plinth.Security.Crypto;
// Generate a 32-byte (256-bit) hex key (64 hex characters)
var hashKey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
var predictableHasher = new PBKDF2PredictableHasher(
hashKey: hashKey,
hashLength: 32, // 32 bytes recommended
iterations: 310000 // 310k iterations recommended
);
services.AddSingleton<IPredictableHasher>(predictableHasher);public class UserService
{
private readonly IPredictableHasher _hasher;
private readonly ISecureData _secureData;
public UserService(IPredictableHasher hasher, ISecureData secureData)
{
_hasher = hasher;
_secureData = secureData;
}
public async Task<bool> AddSsnAsync(string ssn)
{
// Create predictable hash for uniqueness check
var ssnHash = _hasher.PredictableHash(ssn);
// Check if SSN already exists (indexed column)
if (await _db.Users.AnyAsync(u => u.SsnHash == ssnHash))
return false; // SSN already exists
// Encrypt the actual SSN for storage
var encryptedSsn = _secureData.EncryptToBase64(ssn);
// Store both encrypted SSN and hash
var user = new User
{
SsnEncrypted = encryptedSsn,
SsnHash = ssnHash // For uniqueness checks
};
await _db.Users.AddAsync(user);
await _db.SaveChangesAsync();
return true;
}
public async Task<User?> FindBySsnAsync(string ssn)
{
// Hash the SSN to search
var ssnHash = _hasher.PredictableHash(ssn);
// Search by hash (indexed)
return await _db.Users.FirstOrDefaultAsync(u => u.SsnHash == ssnHash);
}
}ISecureData and IPredictableHasherKey Management
Password Hashing
Data Encryption
Predictable Hashing
ISecureData