Don't use weak password-based key derivation
Matches methods creating PasswordDeriveBytes (PBKDF1, obsolete SYSLIB0041) or Rfc2898DeriveBytes through a constructor without a HashAlgorithmName parameter: such constructors silently use SHA1 and default to 1000 iterations, orders of magnitude below the OWASP 2023 recommendation (600.000 iterations for PBKDF2-HMAC-SHA256).
This is CWE-916 Use of Password Hash With Insufficient Computational Effort.
Flagged Code
This is CWE-916 Use of Password Hash With Insufficient Computational Effort.
Flagged Code
static byte[] DeriveKey(string password, byte[] salt) {
using var kdf = new Rfc2898DeriveBytes(password, salt, 1000); // flagged: SHA1 default
return kdf.GetBytes(32);
}
static byte[] LegacyKey(string password, byte[] salt) =>
new PasswordDeriveBytes(password, salt).GetBytes(20); // flagged: PBKDF1
Fixed Codestatic byte[] DeriveKey(string password, byte[] salt) =>
Rfc2898DeriveBytes.Pbkdf2(password, salt, 600_000, HashAlgorithmName.SHA256, 32);
// Or the constructor overload that names the hash algorithm explicitly:
static byte[] DeriveKeyCtor(string password, byte[] salt) {
using var kdf = new Rfc2898DeriveBytes(password, salt, 600_000, HashAlgorithmName.SHA256);
return kdf.GetBytes(32);
}How to fix
Use Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, HashAlgorithmName.SHA256, length) (or the constructor overload taking a HashAlgorithmName) with an explicit high iteration count, or a memory-hard function such as Argon2id or scrypt from a maintained library. Store the iteration count with the hash so it can be raised over time.
CQLinq source
This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.
// <Name>Don't use weak password-based key derivation</Name>
// <Id>ND3123:DontUseWeakPasswordBasedKeyDerivation</Id>
warnif count > 0
// Third-party members are part of the code model only when the application uses them.
let weakKdfMembers =
// PBKDF1 (PasswordDeriveBytes) is obsolete (SYSLIB0041): it produces at most 20 bytes and is not a standard KDF
ThirdParty.Types.WithFullName("System.Security.Cryptography.PasswordDeriveBytes")
.ChildMethods().Where(m => m.IsConstructor)
// Rfc2898DeriveBytes constructors without a HashAlgorithmName parameter default to SHA1 (and 1000 iterations)
.Concat(ThirdParty.Types.WithFullName("System.Security.Cryptography.Rfc2898DeriveBytes")
.ChildMethods().Where(m => m.IsConstructor && !m.Name.Contains("HashAlgorithmName")))
.ToHashSetEx()
where weakKdfMembers.Any()
from m in Application.Methods.UsingAny(weakKdfMembers)
let weakKdfMembersUsed = m.MethodsCalled.Intersect(weakKdfMembers).ToArray()
select new {
m,
weakKdfMembersUsed,
Debt = 30.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Expl>
// The *{0}* derives a key from a password with *{1}*, which relies on
// PBKDF1 or on PBKDF2-SHA1 with a default iteration count.
//</Expl>
//<Desc>
// Matches methods creating *PasswordDeriveBytes* (PBKDF1, obsolete
// SYSLIB0041) or *Rfc2898DeriveBytes* through a constructor without a
// *HashAlgorithmName* parameter: such constructors silently use SHA1 and
// default to 1000 iterations, orders of magnitude below the OWASP 2023
// recommendation (600.000 iterations for PBKDF2-HMAC-SHA256).
//
// This is CWE-916 *Use of Password Hash With Insufficient Computational
// Effort*.
//
// **Flagged Code**
// ```csharp
// static byte[] DeriveKey(string password, byte[] salt) {
// using var kdf = new Rfc2898DeriveBytes(password, salt, 1000); // flagged: SHA1 default
// return kdf.GetBytes(32);
// }
// static byte[] LegacyKey(string password, byte[] salt) =>
// new PasswordDeriveBytes(password, salt).GetBytes(20); // flagged: PBKDF1
// ```
//
// **Fixed Code**
// ```csharp
// static byte[] DeriveKey(string password, byte[] salt) =>
// Rfc2898DeriveBytes.Pbkdf2(password, salt, 600_000, HashAlgorithmName.SHA256, 32);
// // Or the constructor overload that names the hash algorithm explicitly:
// static byte[] DeriveKeyCtor(string password, byte[] salt) {
// using var kdf = new Rfc2898DeriveBytes(password, salt, 600_000, HashAlgorithmName.SHA256);
// return kdf.GetBytes(32);
// }
// ```
//</Desc>
//<HowToFix>
// Use *Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, HashAlgorithmName.SHA256, length)*
// (or the constructor overload taking a *HashAlgorithmName*) with an
// explicit high iteration count, or a memory-hard function such as
// *Argon2id* or *scrypt* from a maintained library. Store the iteration
// count with the hash so it can be raised over time.
//</HowToFix>
Open in the interactive Rules Explorer · All default rules
Security: all 39 items
- Software Composition Analysis (SCA) ND3120
- Transitive Software Composition Analysis (SCA) ND3121
- Don't hard-code secrets in constants ND3114
- Review credentials built from hard-coded values ND3115
- Don't use insecure deserializers ND3110
- Don't use weak hash algorithms (MD5, SHA1) ND3109
- Don't use DES/3DES/RC2 weak cipher algorithms ND3102
- Don't use weak password-based key derivation ND3123
- Don't use the ECB encryption mode ND3132
- Don't disable certificate validation ND3103
- Don't hard-code TLS/SSL protocol versions ND3122
- Review commands vulnerable to SQL injection ND3107
- Review data adapters vulnerable to SQL injection ND3108
- Review raw SQL built from composed strings ND3128
- Review LDAP filters composed from strings ND3137
- Don't enable external XML entity resolution (XXE) ND3111
- Review processes started with a composed arguments string ND3124
- Review web endpoints building file paths from their parameters ND3129
- Review custom archive extraction (Zip Slip) ND3135
- Review assemblies and types loaded from runtime-provided names ND3125
- Review web endpoints without [Authorize] nor [AllowAnonymous] ND3118
- Review state-changing endpoints opened to anonymous callers ND3130
- Review state-changing MVC actions without anti-forgery validation ND3119
- Review HTML-encoding bypasses ND3133
- Review explicitly disabled request validation ND3134
- Review redirects to a URL that is not validated as local ND3126
- Review request-influenced outbound HTTP calls (SSRF) ND3136
- Review insecure ASP.NET Core CORS policies and unguarded developer exception page ND3116
- Review cookies created without HttpOnly and Secure flags ND3117
- Review regular expressions without a match timeout (ReDoS) ND3112
- Review secrets compared with non-constant-time equality ND3113
- Review secrets passed to logging methods ND3127
- Review security operations that log nothing ND3131
- Don't use System.Random for security purposes ND3101
- Don't use CoSetProxyBlanket and CoInitializeSecurity ND3100
- Review publicly visible event handlers ND3104
- Pointers should not be publicly visible ND3105
- Seal methods that satisfy non-public interfaces ND3106
- Restrict sensitive APIs to reviewed components ND3138