This post explains how to implement cryptographically secure password storage and transmission to satisfy NIST SP 800-171 Rev.2 / CMMC 2.0 Level 2 Control IA.L2-3.5.10, with practical steps, recommended algorithms (PBKDF2, bcrypt, Argon2), concrete parameters, migration strategies, and small-business examples you can apply today.
Implementation overview for Compliance Framework
IA.L2-3.5.10 requires you to employ cryptographic mechanisms that protect authentication information (passwords and credentials) both at rest and in transit. For a Compliance Framework implementation this means: identify all systems that store or transmit passwords (databases, identity services, APIs, backup files), define the cryptographic controls (key derivation functions (KDFs), TLS controls), document acceptable algorithms and parameters in a configuration standard, and demonstrate enforcement via logs, configurations, and test evidence for audits.
Choosing the right KDF: PBKDF2, bcrypt, or Argon2
Argon2 (specifically Argon2id) is the modern recommended choice because it is memory-hard and resists GPU/ASIC cracking. bcrypt remains a solid option for legacy systems and libraries. PBKDF2 is acceptable if you use a modern HMAC (SHA-256 or SHA-512) and sufficiently large iteration counts. Practical parameter guidance: target a hashing latency of 100–500 ms on your production authentication hardware—this provides good brute-force resistance without unacceptable user impact. Example starting points: Argon2id: timeCost=2, memoryCost=64MB–256MB, parallelism=1–4; bcrypt: cost (log rounds) = 12–14; PBKDF2-HMAC-SHA256: iterations = 100,000–600,000 (calibrate to your environment).
Code and configuration examples
Use well-vetted libraries and never write your own crypto. Example snippets (calibrate values and wrap in your error handling and logging):
# Python PBKDF2 example (standard library)
import os, hashlib
salt = os.urandom(16)
iterations = 200_000
dk = hashlib.pbkdf2_hmac('sha256', b'correct horse battery staple', salt, iterations)
# store salt, iterations, algorithm metadata with the derived key
// Node.js bcrypt example
const bcrypt = require('bcrypt');
const saltRounds = 12; // calibrate to ~100-300ms
const hash = await bcrypt.hash('user-password', saltRounds);
// Node.js argon2 example
const argon2 = require('argon2');
const hash = await argon2.hash('user-password', {
type: argon2.argon2id,
timeCost: 2,
memoryCost: 65536, // 64 MB
parallelism: 1
});
Protecting passwords in transit
Passwords must always travel over authenticated, encrypted channels. Enforce TLS 1.2+ and prefer TLS 1.3, disable weak ciphers, enable HSTS, and use Secure + HttpOnly + SameSite cookie flags for session cookies. Do not send passwords in URLs, logs, or error messages; POST to endpoints over TLS and reject plaintext connections. Avoid client-side “security through obscurity” such as relying on JavaScript hashing alone; client-side hashing can supplement but not replace server-side KDFs. Where possible, adopt federated auth (OIDC/OAuth2) or MFA to reduce the frequency and blast radius of password use.
Storage design and operational controls
Store one salted, individually-derived hash per account plus metadata: algorithm name, parameters (iterations, memory, cost), and salt. Use a securely generated random salt of at least 16 bytes (128 bits). Consider a pepper (global secret) stored in an HSM or privileged config store—this increases attack cost if the database is breached, but plan for pepper rotation and failover. Implement versioning: when upgrading from PBKDF2/bcrypt to Argon2, store the algorithm version in the user record and re-hash on next successful authentication (or perform a phased bulk re-hash during low-traffic windows). Enforce rate limits, progressive delays, and lockout thresholds to stop credential stuffing and brute-force attacks; log failed attempts and alert on unusual patterns.
Risk of non-implementation (real-world small business scenario)
Failing to implement these controls leaves your organization exposed to credential theft, unauthorized access to Controlled Unclassified Information (CUI), lateral movement across networks, and loss of contracts. For example, a small defense subcontractor stored password hashes using a low PBKDF2 iteration count and no salt; an attacker who breached a dev VM copied the DB and cracked multiple accounts offline within hours, exfiltrating CUI and causing contract termination and regulatory fines. The reputational and financial impact often exceeds the cost of implementing proper KDFs and TLS.
Compliance tips, testing, and best practices
Create a concise compliance checklist for IA.L2-3.5.10: (1) inventory systems that hold/transmit credentials, (2) adopt approved KDFs and document parameters, (3) enforce TLS 1.2+/1.3 and secure cookie policies, (4) implement salts/pepper and metadata/versioning, (5) add rate limits and MFA, (6) perform regular parameter calibration tests and penetration tests, and (7) maintain evidence (config files, test results, change control) for auditors. Test your hashing configuration by measuring real-world hash times, run offline cracking simulations in a controlled environment to validate parameter strength, and include the migration plan in your System Security Plan (SSP). For small businesses, managed identity providers (Azure AD, AWS Cognito, Okta) can offload most implementation and evidence collection while still meeting CMMC/NIST requirements—document the delegation and controls they provide.
Summary: To meet NIST SP 800-171 Rev.2 / CMMC 2.0 IA.L2-3.5.10, use a modern memory-hard KDF (Argon2id preferred, bcrypt acceptable for legacy, PBKDF2 only with high iterations), enforce TLS for all transports, store per-user salts and algorithm metadata, consider a pepper in an HSM, implement rate-limiting and MFA, and document/testing the configuration as part of your compliance evidence. These practical steps reduce the risk of credential compromise and help demonstrate compliance during assessments.