-
Notifications
You must be signed in to change notification settings - Fork 238
Description
-
PasswordHasherOptions.IterationCount < 100K (Core) -
PasswordHasherOptions.CompatibilityMode == IdentityV2 (Core) -
KeyDerivation.Pbkdf2.iterationCount < 100K (Core) -
Rfc2898DeriveBytes.Pbkdf2.iterations < 100K (Core) -
PasswordHasherinstantiated (FRM) -
Rfc2898DeriveBytes.iterations < 100K (cross-platform) -
Rfc2898DeriveBytes.hashAlgorithm does not exist (cross-platform) - (
OpenBsdCrypt/BCrypt).Generate.cost < 12 (BouncyCastle) -
PbeParametersGenerator.Init.iterationCount < 100K (BouncyCastle) -
SCrypt.Generate N < 2^12, r < 8, or dklen < 32 (BouncyCastle)
Why
As part of MMF-3716, we want to close the gap between C# and other languages regarding cryptography-related rules support. S5344 is one of the rules that is not currently supported by this analyzer.
What
S5344 aims to detect when passwords are stored in clear text or with a fast hashing algorithm. Here, we will focus on detecting cost tweakable password hashing function configured with insufficiently strong parameters. The detection logic will be split between three components: .NET core, .NET framework and BouncyCastle.
Detection logic
.Net Core
For Microsoft.AspNetCore.Identity:
Raise on PasswordHasherOptions attributes matching :
IterationCountis < 100 000CompatibilityModeis set toPasswordHasherCompatibilityMode.IdentityV2
Example code
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
[ApiController]
[Route("passwordHasher")]
public class PasswordHasherController : ControllerBase
{
[Route("Compliant")]
public string Compliant()
{
string password = Request.Query["password"];
PasswordHasher<User> hasher = new PasswordHasher<User>();
string hash = hasher.HashPassword(new User("test", password), password);
return hash;
}
[Route("Noncompliant/LowIterations")]
public string NoncompliantLowIt()
{
string password = Request.Query["password"];
IOptions<PasswordHasherOptions> options = Options.Create(new PasswordHasherOptions() {
IterationCount = 1
});
PasswordHasher<User> hasher = new PasswordHasher<User>(options);
string hash = hasher.HashPassword(new User("test", password), password);
return hash;
}
[Route("Noncompliant/CompatMode")]
public string NoncompliantCompat()
{
string password = Request.Query["password"];
IOptions<PasswordHasherOptions> options = Options.Create(new PasswordHasherOptions() {
CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2 // Noncompliant
});
PasswordHasher<User> hasher = new PasswordHasher<User>(options);
string hash = hasher.HashPassword(new User("test", password), password);
return hash;
}
}For Microsoft.AspNetCore.Cryptography.KeyDerivation:
Raise when KeyDerivation.Pbkdf2 is called with iterationCount < 100 000.
Example code
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using System.Security.Cryptography;
namespace demo.Controllers;
[ApiController]
[Route("crypto")]
public class CryptoController : ControllerBase
{
[Route("aspnetcore/Compliant")]
public string AspNetCoreCompliant()
{
string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password!,
salt: salt,
prf: KeyDerivationPrf.HMACSHA256,
iterationCount: 100000,
numBytesRequested: 256 / 8));
return hashed;
}
[Route("aspnetcore/Noncompliant/LowIterations")]
public string AspNetCoreNoncompliantLowIt()
{
string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8); // divide by 8 to convert bits to bytes
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password!,
salt: salt,
prf: KeyDerivationPrf.HMACSHA256,
iterationCount: 1, // Noncompliant
numBytesRequested: 256 / 8));
return hashed;
}
}For System.Security.Cryptography:
Raise when Rfc2898DeriveBytes is instantiated with an iterations parameter < 100 000.
Raise when Rfc2898DeriveBytes is instantiated without a hashAlgorithm parameter.
Raise when Rfc2898DeriveBytes.Pbkdf2 is called with an iterations parameter < 100 000.
Example code
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using System.Security.Cryptography;
namespace demo.Controllers;
[ApiController]
[Route("crypto")]
public class CryptoController : ControllerBase
{
[Route("security/Compliant/Constructor")]
public string SecurityConstructorCompliant()
{
string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, salt, 100_000, HashAlgorithmName.SHA256);
string hashed = Convert.ToBase64String(kdf.GetBytes(256 / 8));
return hashed;
}
[Route("security/Compliant/Method")]
public string SecurityMethodCompliant()
{
string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
string hashed = Convert.ToBase64String(Rfc2898DeriveBytes.Pbkdf2(password, salt, 100_000, HashAlgorithmName.SHA256, 256 /8));
return hashed;
}
[Route("security/NonCompliant/Constructor/LowIter")]
public string SecurityConstructorNonCompliantLowIter()
{
string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, salt, 100, HashAlgorithmName.SHA256); // Noncompliant
string hashed = Convert.ToBase64String(kdf.GetBytes(256 / 8));
return hashed;
}
[Route("security/NonCompliant/Constructor/obsolete")]
public string SecurityConstructorNonCompliantObsolete()
{
string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, 128/8); // Noncompliant
string hashed = Convert.ToBase64String(kdf.GetBytes(256 / 8));
return hashed;
}
}.Net Framework
For Microsoft.AspNet.Identity
Raise any time a PasswordHasher is instantiated.
Example code
using System;
using Microsoft.AspNet.Identity;
string password = "NotSoSecure";
PasswordHasher hasher = new PasswordHasher();
string hash = hasher.HashPassword(password); // Noncompliant, defaults to 1000 iterationsFor System.Security.Cryptography:
Raise when Rfc2898DeriveBytes is instantiated with an iterations parameter < 10 000.
Raise when Rfc2898DeriveBytes is instantiated without a hashAlgorithm parameter.
Example code
using System;
using System.Security.Cryptography;
public static string SecurityConstructorCompliant(string password)
{
byte[] salt = new byte[32];
rngCsp.GetBytes(salt);
Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, salt, 100_000, HashAlgorithmName.SHA256);
string hashed = Convert.ToBase64String(kdf.GetBytes(256 / 8));
return hashed;
}
public static string SecurityConstructorNonCompliantLowIter(string password)
{
byte[] salt = new byte[32];
rngCsp.GetBytes(salt);
Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, salt, 100, HashAlgorithmName.SHA256); // Noncompliant
string hashed = Convert.ToBase64String(kdf.GetBytes(256 / 8));
return hashed;
}
public static string SecurityConstructorNonCompliantObsolete(string password)
{
byte[] salt = new byte[32];
rngCsp.GetBytes(salt);
Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, 128 / 8); // Noncompliant
string hashed = Convert.ToBase64String(kdf.GetBytes(256 / 8));
return hashed;
}BouncyCastle
For Org.BouncyCastle.Crypto.Generators.OpenBsdBCrypt, or Org.BouncyCastle.Crypto.Generators.BCrypt:
Raise when Generate is called with cost < 12.
For Org.BouncyCastle.Crypto.PbeParametersGenerator:
Raise when Init is called with iterationCount < 100 000. Note that this method is usually indirectly called by a subclass such as Org.BouncyCastle.Crypto.Generators.Pkcs5S2ParametersGenerator.
For Org.BouncyCastle.Crypto.Generators.SCrypt:
Raise when Generate is called with N < 2^12, r < 8, or dklen < 32.
Example code
using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
using System.Text;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
namespace demo.Controllers;
[ApiController]
[Route("bc")]
public class BCController : ControllerBase
{
[Route("Compliant/BCrypt")]
public string CompliantBcrypt()
{
string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
string hashed = OpenBsdBCrypt.Generate(password.ToCharArray(), salt, 14);
return hashed;
}
[Route("Noncompliant/Bcrypt")]
public string NoncompliantBCrypt()
{
string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
string hashed = OpenBsdBCrypt.Generate(password.ToCharArray(), salt, 4); // Noncompliant
return hashed;
}
[Route("Compliant/SCrypt")]
public string CompliantScrypt()
{
string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
string hashed = Convert.ToBase64String(SCrypt.Generate(Encoding.Unicode.GetBytes(password), salt, 1<<12, 8, 1, 32));
return hashed;
}
[Route("Noncompliant/Scrypt")]
public string NoncompliantSCrypt()
{
string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
string hashed = Convert.ToBase64String(SCrypt.Generate(Encoding.Unicode.GetBytes(password), salt, 4, 8, 1, 32)); // Noncompliant
return hashed;
}
[Route("Compliant/PBKDF2")]
public string CompliantPBKDF2()
{
string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator();
gen.Init(Encoding.Unicode.GetBytes(password), salt, 100_000);
KeyParameter keyParam = (KeyParameter) gen.GenerateDerivedMacParameters(256);
string hashed = Convert.ToBase64String(keyParam.GetKey());
return hashed;
}
[Route("Noncompliant/PBKDF2")]
public string NoncompliantPBKDF2()
{
string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator();
gen.Init(Encoding.Unicode.GetBytes(password), salt, 100); // Noncompliant
KeyParameter keyParam = (KeyParameter) gen.GenerateDerivedMacParameters(256);
string hashed = Convert.ToBase64String(keyParam.GetKey());
return hashed;
}
}RSPEC
This rule's RSPEC contains information regarding messages and highlighting: