Plinth.Security
1.8.0
Prefix Reserved
dotnet add package Plinth.Security --version 1.8.0
NuGet\Install-Package Plinth.Security -Version 1.8.0
<PackageReference Include="Plinth.Security" Version="1.8.0" />
<PackageVersion Include="Plinth.Security" Version="1.8.0" />
<PackageReference Include="Plinth.Security" />
paket add Plinth.Security --version 1.8.0
#r "nuget: Plinth.Security, 1.8.0"
#:package Plinth.Security@1.8.0
#addin nuget:?package=Plinth.Security&version=1.8.0
#tool nuget:?package=Plinth.Security&version=1.8.0
README
Plinth.Security
Security, Cryptography, and Token Utilities
Provides production-ready cryptographic utilities for common security scenarios including data encryption, password hashing, and predictable hashing.
- Crypto
- ISecureData: Symmetric encryption utility for encrypting data in transit and at rest
- IPasswordHasher: Secure storage and validation of passwords using PBKDF2
- IPredictableHasher: Mechanism for producing a secure one-way hash of sensitive data that is repeatable for uniqueness checks
- Tokens
- A library for generating secure, Plinth-specific authentication tokens, loosely based on JWE
ISecureData - Data Encryption
ISecureData provides symmetric encryption for protecting sensitive data at rest and in transit using AES-CBC with HMAC authentication.
Setup with Single Key
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);
Setup with Key Rotation
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);
Encrypting and Decrypting Data
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;
}
}
Using Different Keys
// 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);
Key Requirements
- Keys must be 16, 24, or 32 bytes (32, 48, or 64 hex characters)
- 32 bytes (AES-256) is recommended for production
- Store keys securely (Azure Key Vault, AWS Secrets Manager, etc.)
IPasswordHasher - Password Hashing
IPasswordHasher provides secure password hashing using PBKDF2 with SHA-256, following OWASP recommendations.
Setup
using Plinth.Security.Crypto;
var passwordHasher = new PBKDF2PasswordHasher();
services.AddSingleton<IPasswordHasher>(passwordHasher);
Hashing Passwords
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;
}
}
Version History
The implementation supports multiple versions for backward compatibility:
- Version 1: 50k iterations, SHA-1 (legacy, don't use)
- Version 2: 100k iterations, SHA-1 (legacy)
- Version 3: 100k iterations, SHA-256 (2016-2021)
- Version 4: 310k iterations, SHA-256 (2021-2022)
- Version 5: 600k iterations, SHA-256 (2023-current, recommended)
The version is automatically detected during verification, allowing seamless migration.
IPredictableHasher - Deterministic Hashing
IPredictableHasher creates deterministic hashes for sensitive data that needs to be searchable or checked for uniqueness (e.g., SSNs, credit card numbers).
Setup
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);
Usage Example
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);
}
}
Use Cases
- Social Security Numbers
- Credit Card Numbers
- Phone Numbers
- Email Addresses (for duplicate detection while maintaining privacy)
Security Considerations
- Use different keys for
ISecureDataandIPredictableHasher - Store the hash key securely
- Use at least 310k iterations for production
- Consider the trade-off: predictable hashes enable searching but reveal duplicates
Best Practices
Key Management
- Store all cryptographic keys in secure configuration (Azure Key Vault, AWS Secrets Manager, etc.)
- Never commit keys to source control
- Rotate encryption keys periodically
- Use different keys for different purposes
Password Hashing
- Never try to decrypt password hashes (they're one-way)
- Let the verifier handle version detection automatically
Data Encryption
- Use 32-byte (AES-256) keys for production
- Encrypt sensitive data before storing in databases
- Consider using predictable hashing for searchable encrypted data
Predictable Hashing
- Only use for data that needs to be searchable
- Understand that identical inputs produce identical hashes
- Use separate keys from
ISecureData
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Plinth.Common (>= 1.8.0)
- Plinth.Serialization (>= 1.8.0)
-
net8.0
- Plinth.Common (>= 1.8.0)
- Plinth.Serialization (>= 1.8.0)
- System.Text.Json (>= 10.0.0)
-
net9.0
- Plinth.Common (>= 1.8.0)
- Plinth.Serialization (>= 1.8.0)
- System.Text.Json (>= 10.0.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Plinth.Security:
| Package | Downloads |
|---|---|
|
Plinth.AspNetCore
Plinth ASP.NET Core Services Utilities |
|
|
Plinth.Storage
Plinth library for storing binary files |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.8.0 | 422 | 11/13/2025 |
| 1.8.0-b211.72089fd9 | 232 | 11/12/2025 |
| 1.7.4 | 1,473 | 8/6/2025 |
| 1.7.3 | 268 | 8/2/2025 |
| 1.7.2 | 3,741 | 3/16/2025 |
| 1.7.1 | 1,241 | 12/12/2024 |
| 1.7.0 | 2,474 | 11/12/2024 |
| 1.6.6 | 1,167 | 11/8/2024 |
| 1.6.5 | 3,348 | 8/31/2024 |
| 1.6.4 | 816 | 8/2/2024 |
| 1.6.3 | 2,161 | 5/15/2024 |
| 1.6.2 | 847 | 2/16/2024 |
| 1.6.1 | 5,310 | 1/5/2024 |
| 1.6.0 | 1,640 | 11/30/2023 |
| 1.5.10-b186.aca976b4 | 126 | 11/30/2023 |
| 1.5.9 | 504 | 11/29/2023 |
| 1.5.9-b174.64153841 | 147 | 11/23/2023 |
| 1.5.9-b172.dfc6e7bd | 122 | 11/17/2023 |
| 1.5.9-b171.4e2b92e2 | 144 | 11/4/2023 |
| 1.5.8 | 1,111 | 10/23/2023 |
| 1.5.7 | 9,332 | 7/31/2023 |
| 1.5.6 | 7,243 | 7/13/2023 |
| 1.5.5 | 720 | 6/29/2023 |
| 1.5.4 | 1,848 | 3/7/2023 |
| 1.5.3 | 1,117 | 3/3/2023 |
| 1.5.2 | 1,373 | 1/11/2023 |
| 1.5.2-b92.7c961f5f | 245 | 1/11/2023 |
| 1.5.0 | 1,770 | 11/9/2022 |
| 1.5.0-b88.7a7c20cd | 221 | 11/9/2022 |
| 1.4.7 | 5,957 | 10/20/2022 |
| 1.4.6 | 2,931 | 10/17/2022 |
| 1.4.5 | 3,127 | 10/1/2022 |
| 1.4.4 | 3,227 | 8/16/2022 |
| 1.4.3 | 3,352 | 8/2/2022 |
| 1.4.2 | 3,276 | 7/19/2022 |
| 1.4.2-b80.7fdbfd04 | 249 | 7/19/2022 |
| 1.4.2-b74.acaf86f5 | 252 | 6/15/2022 |
| 1.4.1 | 3,306 | 6/13/2022 |
| 1.4.0 | 3,041 | 6/6/2022 |
| 1.3.8 | 4,340 | 4/12/2022 |
| 1.3.7 | 3,047 | 3/21/2022 |
| 1.3.6 | 3,068 | 3/17/2022 |
| 1.3.6-b67.ca5053f3 | 278 | 3/16/2022 |
| 1.3.6-b66.4a9683e6 | 263 | 3/16/2022 |
| 1.3.5 | 3,086 | 2/23/2022 |
| 1.3.4 | 3,569 | 1/20/2022 |
| 1.3.3 | 2,369 | 12/29/2021 |
| 1.3.2 | 1,884 | 12/11/2021 |
| 1.3.1 | 1,848 | 11/12/2021 |
| 1.3.0 | 1,854 | 11/8/2021 |
| 1.2.3 | 3,126 | 9/22/2021 |
| 1.2.2 | 2,114 | 8/20/2021 |
| 1.2.1 | 2,578 | 8/5/2021 |
| 1.2.0 | 3,990 | 8/1/2021 |
| 1.2.0-b37.a54030b9 | 305 | 6/24/2021 |
| 1.1.6 | 5,881 | 3/22/2021 |
| 1.1.5 | 2,195 | 3/9/2021 |
| 1.1.4 | 3,318 | 2/27/2021 |
| 1.1.3 | 2,102 | 2/17/2021 |
| 1.1.2 | 2,097 | 2/12/2021 |
| 1.1.1 | 2,473 | 2/1/2021 |
| 1.1.0 | 2,100 | 12/16/2020 |
| 1.1.0-b27.b66c309b | 444 | 11/15/2020 |
| 1.0.12 | 4,138 | 10/18/2020 |
| 1.0.11 | 2,220 | 10/6/2020 |
| 1.0.10 | 2,225 | 9/30/2020 |
| 1.0.9 | 2,279 | 9/29/2020 |
| 1.0.8 | 2,331 | 9/26/2020 |
| 1.0.7 | 2,263 | 9/19/2020 |
| 1.0.6 | 2,229 | 9/3/2020 |
| 1.0.5 | 2,434 | 9/2/2020 |
| 1.0.4 | 2,546 | 9/1/2020 |
| 1.0.3 | 2,212 | 9/1/2020 |
| 1.0.2 | 2,300 | 8/29/2020 |
| 1.0.1 | 2,211 | 8/29/2020 |
| 1.0.0 | 2,258 | 8/29/2020 |
| 1.0.0-b1.c22f563d | 393 | 8/28/2020 |
net10.0 support