Insufficient password hashing iterations in Swift

Insufficient password hashing iterations in Swift

Description

PBKDF1 is a legacy KDF based only on MD5 or SHA-1 and must not be used for new password storage, regardless of its iteration count. For PBKDF2, fewer than 600,000 iterations with HMAC-SHA256 or 220,000 with HMAC-SHA512 makes offline guessing against leaked hashes easier. If existing compatibility requires HMAC-SHA1, use at least 1,400,000 iterations, but do not choose it for a new system.

Potential impact

  • Lower cost of password cracking.
  • Greater likelihood of recovering passwords from leaked hashes.
  • Increased risk of attacks that reuse compromised credentials.

Remediation

  1. Use PBKDF1 only for legacy compatibility and migrate existing password hashes to a safer KDF when users log in.
  2. Configure PBKDF2-HMAC-SHA256 with at least the OWASP recommendation of 600,000 iterations and review the policy regularly.
  3. For PBKDF2-HMAC-SHA512, use at least 220,000 iterations.
  4. For legacy PBKDF2-HMAC-SHA1, use at least 1,400,000 iterations and migrate to a more modern KDF at login.
  5. Prefer a dedicated password hashing algorithm such as Argon2id, bcrypt, or scrypt where possible.
  6. Specify the iteration count explicitly instead of relying on CryptoSwift's low default.
  7. Manage the iteration count through configuration and validate that it cannot fall below a safe minimum.

Examples

Before

swift
import CryptoSwift

func deriveWithTooFewIterations(
    password: [UInt8], salt: [UInt8]
) throws -> [UInt8] {
    return try PKCS5.PBKDF2(
        password: password,
        salt: salt,
        iterations: 90000,
        keyLength: 32,
        variant: .sha2(.sha256)
    ).calculate()
}

func deriveWithObsoletePBKDF1(password: [UInt8]) throws -> [UInt8] {
    let salt = (0..<8).map { _ in UInt8.random(in: 0...UInt8.max) }
    return try PKCS5.PBKDF1(
        password: password,
        salt: salt,
        iterations: 1000,
        keyLength: 16
    ).calculate()
}

After

swift
import CryptoSwift

func derivePasswordKey(password: [UInt8], salt: [UInt8]) throws -> [UInt8] {
    return try PKCS5.PBKDF2(
        password: password,
        salt: salt,
        iterations: 600000,
        keyLength: 32,
        variant: .sha2(.sha256)
    ).calculate()
}

Explanation:

  • Before: A low iteration count for PBKDF2-HMAC-SHA256 speeds up an attacker's offline guesses. Even with a random salt, PBKDF1 remains a legacy MD5/SHA-1-based KDF and is unsuitable for new password hashes.
  • After: PBKDF2-HMAC-SHA256 uses the currently recommended minimum iteration count. Reassess the work factor as server performance and policy requirements change.

References