VNX-SWIFT-006 – Swift Non-Cryptographic PRNG Used for a Security Value
Overview
This rule flags calls to non-cryptographic generators in Swift: the libc rand(), random(), drand48(), lrand48(), mrand48() and erand48/nrand48/jrand48, and GameplayKit’s random sources and distributions (GKRandomSource, GKARC4RandomSource, GKLinearCongruentialRandomSource, GKMersenneTwisterRandomSource, GKRandomDistribution, GKGaussianDistribution, GKShuffledDistribution), which Apple documents as unsuitable for cryptography. A call is reported only when its result is a security value: the line (or a let/var opened up to three lines above it) assigns a name ending with token, nonce, salt, secret, password, OTP, CSRF, session id, challenge, API key, a *Key, IV or PIN, or the enclosing function or type is named for one (generateToken, makeNonce, enum OTP).
Not reported: arc4random(), arc4random_uniform(), arc4random_buf(), SystemRandomNumberGenerator and Int.random(in:), which are backed by the system CSPRNG on Apple platforms; SecRandomCopyBytes and CryptoKit; generators used for shuffling, jitter, delays or cache keys; seeding calls on their own; string literals, comments and test files.
This maps to CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG).
Severity: High | CWE: CWE-338 – Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)
Why This Matters
Security properties of tokens, keys, nonces, and salts depend entirely on their unpredictability. A PRNG that is predictable — or one whose internal state can be reconstructed from observed outputs — allows an attacker to enumerate possible values, dramatically reducing the search space for brute-force attacks.
On Apple platforms arc4random and its variants use a ChaCha20-based CSPRNG seeded by the kernel, and Swift’s SystemRandomNumberGenerator (used by Int.random(in:), shuffled() and friends) is documented as cryptographically secure there. rand(), random() and the drand48 family are linear generators whose state can be recovered from a handful of outputs, and the GameplayKit sources are deterministic, seedable generators meant for games.
The attack is most impactful when a predictable PRNG is used to generate password reset tokens, OAuth state parameters, CSRF tokens, one-time passwords, or symmetric encryption IVs. If the attacker can observe a few generated values (e.g., by triggering multiple password resets), they may be able to reconstruct the generator’s state and predict all future values, compromising every account that requests a reset during the window.
Apple’s SecRandomCopyBytes and the CryptoKit framework’s SymmetricKey and Nonce types use the OS CSPRNG (/dev/random backed by the Secure Enclave) and are the correct choice for all security-sensitive random data generation on Apple platforms.
What Gets Flagged
// FLAGGED: libc rand() for a reset token
let resetToken = String(rand(), radix: 16)
// FLAGGED: drand48() for a salt
let salt = drand48()
// FLAGGED: GameplayKit for a one-time code
let otp = GKRandomSource.sharedRandom().nextInt(upperBound: 1_000_000)
// FLAGGED: rand() inside a key generator
func generateSessionKey() -> [UInt8] {
var bytes = [UInt8](repeating: 0, count: 32)
for i in 0..<bytes.count {
bytes[i] = UInt8(truncatingIfNeeded: rand())
}
return bytes
}
// FLAGGED: a GameplayKit source held in a property, then drawn from
private let source = GKMersenneTwisterRandomSource(seed: 1337)
let otp = source.nextInt(upperBound: 1_000_000)
// NOT FLAGGED: jitter inside a token-refresh function
let jitter = Double(random() % 30)
// NOT FLAGGED: CSPRNG-backed on Apple platforms
let nonce = arc4random_uniform(UInt32.max)
let pin = Int.random(in: 0..<10_000)
Remediation
Use
SecRandomCopyBytesfor raw random bytes. This function fills a buffer with cryptographically secure random data using the hardware RNG.// SAFE: cryptographically secure token using SecRandomCopyBytes import Security func generateSecureToken(length: Int = 32) throws -> String { var bytes = [UInt8](repeating: 0, count: length) let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) guard status == errSecSuccess else { throw CryptoError.randomGenerationFailed(status) } return bytes.map { String(format: "%02x", $0) }.joined() }Use CryptoKit for symmetric keys, nonces, and IVs. CryptoKit’s types generate secure random values internally and enforce correct sizes for the chosen algorithm.
// SAFE: AES-GCM encryption with CryptoKit-generated key and nonce import CryptoKit func encryptData(_ plaintext: Data, using key: SymmetricKey) throws -> (ciphertext: Data, tag: Data, nonce: AES.GCM.Nonce) { let nonce = AES.GCM.Nonce() // Cryptographically secure, correct size let sealed = try AES.GCM.seal(plaintext, using: key, nonce: nonce) return (sealed.ciphertext, sealed.tag, nonce) } // Generate a 256-bit key let key = SymmetricKey(size: .bits256) // Cryptographically secureUse
UUID()only for identifiers. It is random but has a fixed format and only 122 random bits; for a secret value, generate bytes withSecRandomCopyBytesor CryptoKit.Audit all token and key generation code. Search for
rand(,random(,drand48(andGK*RandomSourceand replace any that generate security-relevant values.