How to Encrypt and Decrypt Passwords in ASP.NET Core
Before anything else: if the goal is storing user login passwords, encryption (which is reversible, by design) is the wrong tool entirely. Passwords should be hashed, not encrypted — hashing is deliberately one-way, so even if your database is ever breached, the actual passwords can't be recovered from the stored hashes at all. If your application currently encrypts and decrypts login passwords, that's a genuine security issue worth fixing, not just a style preference.
ASP.NET Core Identity, the framework's built-in authentication system, already handles password hashing correctly out of the box using PBKDF2 with a per-user salt, through its IPasswordHasher<TUser> implementation — if you're using ASP.NET Core Identity for user accounts, you likely don't need to write any password hashing code yourself at all, and shouldn't reimplement something the framework already does correctly.
There are, however, entirely legitimate reasons to need real, reversible encryption in an ASP.NET Core application — third-party API credentials your application needs to use again later, database connection strings for dynamically configured tenants, or other secrets that genuinely must be recovered in their original form to be useful. This is a fundamentally different problem from password storage, and calls for a different tool.
ASP.NET Core's Data Protection API is the framework's built-in, recommended solution for this kind of reversible encryption need. It handles key management, key rotation, and the underlying cryptographic algorithm choices for you — you call Protect() to encrypt a value and Unprotect() to decrypt it, without needing to choose an algorithm or manage encryption keys manually, which is where a lot of hand-rolled encryption code goes wrong.
A critical detail for any multi-server deployment is that Data Protection's keys need to be shared and persisted across all instances of your application, not left on each server's local disk independently — otherwise, data encrypted by one server instance can't be decrypted by another, and worse, an application restart or redeployment can lose the keys entirely if they're only stored locally, permanently locking you out of previously encrypted data. Configure a shared key storage location — Azure Key Vault, Redis, or a shared network location, depending on your hosting environment.
For encrypting data before storing it in a database column specifically, the general pattern is calling Protect() on the value before saving it, and Unprotect() on the way back out when reading it — but consider carefully whether the encrypted column can still be queried or indexed the way you need, since encrypted values obviously can't be searched or filtered the same way plaintext columns can, which sometimes affects your data model design more than the encryption code itself.
If you're auditing existing code and find login passwords being encrypted and decrypted rather than hashed, migrating away from that pattern requires care — you generally can't directly convert stored encrypted passwords into properly hashed ones without the original plaintext, so the practical migration path is usually decrypting existing passwords once during a controlled migration, immediately re-hashing them with ASP.NET Core Identity's hasher, and discarding the encryption keys used for the old scheme entirely once the migration is verified complete.
Found this helpful?
SyncTonight's tools and guides are free and always will be. If this post saved you some debugging time, a coffee goes a long way — no pressure, just appreciated.
☕ Buy me a coffee