Application Security — Stronger Hashes and Safer Passwords
Every application that stores passwords makes an implicit bet: that the hashing algorithm it chose will remain strong enough to resist attacks for as long as those hashes exist. It’s worth revisiting that bet regularly. This post walks through the reasoning behind some recent changes we’ve made to password hashing across DevExpress components, and covers broader principles that apply whether you use our tools or not.
A Quick Primer on Password Hashing
A hash function takes an input - say, a password - and produces a fixed-length string that looks nothing like the original. The critical property is that this transformation is one-way: given the hash, it should be computationally impossible to recover the original password.
Here’s a concise example using the .NET class Rfc2898DeriveBytes, which
implements the PBKDF2 algorithm:
using System.Security.Cryptography;
byte[] salt = RandomNumberGenerator.GetBytes(16);
int iterations = 600_000;
byte[] hash = Rfc2898DeriveBytes.Pbkdf2(
password: "correct-horse-battery-staple",
salt: salt,
iterations: iterations,
hashAlgorithm: HashAlgorithmName.SHA512,
outputLength: 64);
The salt ensures that identical passwords produce different hashes. The iteration count controls how much computational work is required to produce each hash, making brute-force attacks proportionally more expensive. And the choice of hash algorithm matters more than you might think, as I’ll discuss below.

Document Protection: SHA-512 for Office Documents
If you’ve used document protection in Word-compatible file formats, you know that it’s an “advisory” protection mechanism. It signals that a document shouldn’t be edited, but it’s ultimately up to the consuming application to check and enforce that flag. Document protection is not an encryption mechanism designed to provide strong security.
Even so, the protection password hash is stored inside the document. While these
passwords are often shared with collaborators and hopefully aren’t reused for
online banking, there’s still no good reason to make it easy for someone to
brute-force the original password. In v26.1, our
Document.Protect
method uses the strongest hash function supported by the Office format, as
documented by
Microsoft. You
can find the full details in our breaking change notice.
Digital Signatures: OcspClient Defaults to SHA-512
Password hashes aren’t the only place where hash strength matters. Digital
signatures rely on hash functions too, and a weak hash can undermine the
integrity guarantees that a signature is supposed to provide. With v26.1, the
OcspClient class used for OCSP (Online Certificate Status Protocol) responses
in our digital signature workflow now uses SHA-512 by
default. This
is a straightforward upgrade that brings the default in line with current best
practices.
XAF Security System: SHA-512 with 600000 Iterations
XAF’s built-in Security System handles full user account management, including password storage. This is the scenario where hashing strength matters most, since these are real user passwords protecting real application access.
With v26.1, we’re configuring the default hash mechanism to use SHA-512 with 600000 iterations of PBKDF2. This is a significant step up from previous defaults. Because this change affects how stored passwords are verified, it can be considered a breaking change (full documentation and migration guidance is available here). We’ll provide detailed steps to help you update your application and migrate existing password hashes to the new configuration.
Why These Specific Choices?
You might wonder: why SHA-512? Why 600000 iterations? Why not something else entirely?
The short answer is that we’re following the OWASP Password Storage Cheat Sheet, specifically its guidance on PBKDF2 for environments that require FIPS-140 compliance. OWASP is widely regarded as the authoritative source for application security best practices, and their recommendations are well-researched, regularly updated, and practical.
If you handle passwords in your own applications - whether for storage, verification, or any other processing - reading the OWASP guidance in detail is highly recommended. It’s clearly written and covers important related concepts like the salts mentioned above (random values mixed into each hash to prevent precomputed attacks) and peppers (application-level secrets added as an extra layer of defense).
That said, there are two important points worth expanding on in the context of the choices we made.
Point 1: Iteration Count Is a Trade-Off, and Rate Limiting Is Non-Negotiable
The OWASP guidance for PBKDF2 with SHA-512 suggests an iteration count that is actually somewhat lower than the 600000 we adopted for XAF. That’s because iteration count is a trade-off between security and performance: more iterations mean more work for an attacker, but also more work for your server on every legitimate login.
To make an informed choice, think about where and when your application computes password hashes. How frequently does it happen? What does the load look like during peak usage? Measure how long a single hash computation takes on your hardware. These data points, combined with the OWASP guidance, will help you find the right balance for your specific situation.
This line of thinking leads to a critical complementary measure: rate limiting. No matter how strong your hash function is, if an attacker can make unlimited login attempts, they have unlimited opportunities to guess passwords. Rate limiting caps the number of attempts in a given time window, making brute-force attacks impractical even against weaker hashes.
You want this at two levels. At the application level, lock out individual
accounts after repeated failures. ASP.NET Core Identity supports this through
the
LockoutOptions.MaxFailedAccessAttempts
property in the IdentityOptions.Lockout configuration, and the XAF Security
System offers equivalent functionality with the
ISecurityUserLockout
interface. At the edge, protect your infrastructure from being overwhelmed, by
using a WAF (Web Application Firewall), Cloudflare, or a reverse proxy like
Nginx or Caddy with built-in rate limiting. Use both techniques: account lockout
and infrastructure protection solve different problems.
Point 2: PBKDF2 vs. Memory-Hard Algorithms - the State of the Art
If you read the OWASP cheat sheet closely, you’ll notice that PBKDF2 is positioned as the last choice in their ranked list of recommended algorithms. OWASP explicitly notes that PBKDF2 is the best option only when FIPS-140 compliance is required.
The reason is architectural. PBKDF2 is a CPU-bound algorithm, its security relies on requiring many sequential computations. The problem is that modern GPUs and purpose-built ASIC chips can perform these computations orders of magnitude faster than general-purpose CPUs. An attacker with access to specialized hardware can brute-force PBKDF2 hashes far more efficiently than a defender’s server can compute them. Some hardware of this nature can be rented conveniently in the cloud, making it accessible to a wide range of attackers.
OWASP’s preferred alternatives are memory-hard algorithms like Argon2id and scrypt. These algorithms are designed to require large amounts of memory during computation, which makes them resistant to GPU and ASIC attacks. Specialized hardware is fast at computation but has limited memory bandwidth, which levels the playing field.
However, memory-hard algorithms come with trade-offs of their own. Consider the OWASP-recommended minimum for Argon2id: 19 MiB of memory and 2 iterations per hash computation. If you need to support just 100 concurrent login attempts, that’s already 1.9 GiB of memory dedicated solely to password hashing. The calculation for your maximum concurrent user count becomes a fundamentally different exercise than with CPU-bound algorithms.
There’s also a practical consideration in the .NET ecosystem: Argon2id and scrypt are not natively supported by the .NET runtime. Using them requires third-party libraries, which introduces dependencies on external maintainers for security-critical code. Many developers and organizations reasonably conclude that a well-configured, natively supported algorithm - PBKDF2 with SHA-512 and a high iteration count - is preferable to taking on that dependency risk. This is the reasoning behind our choice for DevExpress products.
The Bottom Line
Password hashing is one of those areas where “good enough” has a shelf life. Algorithms that were considered strong a few years ago may no longer provide adequate protection against modern hardware. The updates we’re shipping in v26.1 reflect current best practices, and we encourage you to review your own applications with the same critical eye, if you handle passwords in any capacity.