VNX-SWIFT-006: Swift non-cryptographic PRNG (libc rand/random/drand48, GameplayKit) used for a security value
Detects the libc rand/random/drand48 family and GameplayKit random sources (directly or through a source variable) used to produce a token, nonce, salt, key, IV, PIN, OTP or password in Swift; arc4random, SystemRandomNumberGenerator and .random(in:) are CSPRNG-backed on Apple platforms and are not reported.
| Field | Value |
|---|---|
| Rule ID | VNX-SWIFT-006 |
| Severity | High |
| SARIF level | error |
| Kind | sast |
| Languages | Swift |
| CWE | CWE-338 |
| CAPEC | CAPEC-112 |
| ATT&CK | T1600 |
| CWSS vector | CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H |
| Tags | weak-random cryptography swift ios |
| Source | internal/sast/rules/vnx-swift-006.rego |
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).
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 secure -
Use
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.
References
- CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)
- OWASP Mobile Security Testing Guide – MSTG-CRYPTO-6 (Randomness)
- Apple – SecRandomCopyBytes
- Apple – CryptoKit
- Apple – Performing Common Cryptographic Operations
- CAPEC-112: Brute Force
- MITRE ATT&CK T1600 – Weaken Encryption
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-swift-006
// nosec vnx-swift-006
Or record an ignore rule: vulnetix ignore add --rule vnx-swift-006 --reason "...". See Suppressing findings.