Random numbers are the quietest failure in mobile security. Nothing crashes, nothing looks wrong, tests pass, and the tokens your app generates are predictable to anyone who works out which generator produced them. It survives code review because the line looks unremarkable.
Short answer
A general-purpose random number generator is not a cryptographic one, and the difference matters wherever the value must be unguessable. Android documents SecureRandom as the cryptographically strong generator, and Apple provides SecRandomCopyBytes for the same purpose. CWE-338 describes the weakness precisely: using a cryptographically weak pseudo-random number generator in a security context. If a value protects something, it comes from the cryptographic generator.
Where predictable values do damage
Session and reset tokens. A password reset token generated from a general-purpose generator seeded with the current time is guessable by anyone who knows roughly when it was created, and that is enough for account takeover without touching the device.
Initialization vectors and nonces. Cryptographic modes have requirements about uniqueness and unpredictability, and a repeated or predictable value can undermine the encryption entirely, sometimes revealing plaintext relationships across messages.
Salts. A predictable salt defeats the purpose of salting, which is to make precomputation useless.
Identifiers used for authorization. Any identifier that is treated as a capability, such as a share link or an invitation code, has to be unguessable, and sequential or time-derived identifiers are enumerable.
Anything used as a one-time code. A verification code from a weak generator is a weak verification code regardless of how many digits it has.
The pattern worth internalising is that the question is never whether the numbers look random. It is whether an attacker who knows the algorithm and roughly when the value was produced can narrow the search space to something they can try.
The mistakes that produce weak values
Using the language's default generator. Every platform ships one, it is faster, it is what appears in tutorials, and it is not designed to resist prediction. This is the overwhelming majority of real cases.
Seeding a cryptographic generator manually. Supplying your own seed to a secure generator can replace a properly gathered entropy source with something worse, and it is almost never necessary because the platform seeds it correctly.
Deriving values from time. Timestamps, or a hash of a timestamp, feel unique and are not unpredictable. Uniqueness and unpredictability are different properties and only one of them is a security property.
Deriving values from device identifiers. Anything stable per device is stable to an attacker who has the device or can observe one value derived from it.
Truncating properly generated output too far. A cryptographically generated value cut to a handful of characters is small enough to brute force regardless of how it was produced.
Rolling your own. Combining several weak sources does not produce a strong one, and the composition arguments people use to justify this are generally wrong.
Choosing sizes that hold up
Length is the second half of the question, and it is easier to reason about than the generator choice.
For a value that must resist offline guessing, such as a session token or a reset token, a length on the order of 128 bits of entropy is the usual baseline. Expressed as an encoded string, that is roughly twenty-two characters of base64 or thirty-two hex characters.
For a value that is rate limited and short-lived, such as a six-digit verification code, the length is deliberately small and the security comes from the rate limit and the expiry rather than from the entropy. That trade is fine as long as both controls actually exist, and they frequently do not.
For identifiers that are not secrets, such as a database key, unpredictability may not be required at all, and using a cryptographic generator anyway costs nothing worth measuring.
The reasoning to write down is which of those three categories a given value belongs to, because most disagreements about token length are actually disagreements about that classification.
Verifying it rather than assuming it
This is a weakness you find by looking, since it produces no symptoms.
Search the codebase for the general-purpose generator by name and review every use. The review question is simple: does anything about security depend on this value being unguessable. Most uses are legitimate, such as jitter in a retry backoff or a shuffle in a UI, and the ones that are not stand out quickly.
Check third-party code with the same question. A library generating identifiers for you has made this decision on your behalf, and the answer is not always the one you would have chosen.
Look at your server as well as the client. Tokens generated server-side are the ones that matter most, and mobile teams sometimes assume the backend got it right without checking.
The MASVS cryptography category covers the requirement, and the MASTG documents how to verify it in a built application. An artifact-level scan such as PTKD.com helps with the mechanical part, flagging weak generator usage and related cryptographic configuration in the shipped build, which is where the reasoning about intent then has to start.
Fixing it without breaking existing users
Switching generators is a small code change with a migration question attached.
Values already issued do not become strong retroactively. If reset tokens were weak, the ones outstanding are still weak, and expiring them is part of the fix rather than an optional extra.
Long-lived credentials generated weakly need rotation. A refresh token issued from a predictable generator two years ago is a live problem, and the remediation is to invalidate and reissue rather than to change the generator going forward and hope.
Where rotation is disruptive, stage it. Invalidate on next use, force reissue at the next authentication, and set an end date after which old values stop being accepted at all.
And record which categories of value were affected, because the answer to "did this need fixing" is different for a UI shuffle than for a session token, and someone will ask.
The generator is only half of it
A correctly generated token can still be weak, because generation is one of four properties that have to hold together.
Transmission. A strong token sent over a connection that permits interception is a strong token an attacker has. Randomness does nothing for transport.
Storage. A token generated properly and then written to a log file, a backup, or an unprotected preferences file is retrievable without any guessing at all. This is a far more common route to a compromised token than prediction is.
Lifetime. A value that never expires converts a single exposure into permanent access. Short lifetimes are what limit the damage when one of the other three properties fails.
Validation. A token the server checks by comparison but not for expiry, revocation or scope is being validated only for shape. This is the property most likely to be assumed rather than verified.
Randomness matters, and it is the property teams focus on because it feels like the cryptographic one. The other three are where more real incidents start.
Timing comparisons, a related detail
Once you have a strong token, how you compare it matters. A comparison that returns as soon as two values differ takes measurably longer for a value matching more leading characters, which leaks information about the correct value across many attempts.
The fix is a constant-time comparison, which both platforms provide and which most frameworks use internally for exactly this reason. The situations where teams write their own comparison are usually custom token schemes and signature verification, and those are the places to check.
This matters less on mobile than on a server, since the interesting comparisons happen server-side, and it is worth knowing about because custom verification code in a mobile client tends to be written by whoever also wrote the server side.
A short audit you can run today
Four searches, each a few minutes, covering most of the ground.
Search for the general-purpose random generator by name across the codebase and classify every hit as security-relevant or not.
Search for the secure generator being constructed with an explicit seed, which is a strong signal of a well-intentioned mistake.
Search for token or code generation built from timestamps, formatted dates or device identifiers, which tends to appear in helper functions with innocuous names.
List the third-party libraries that generate identifiers on your behalf and check what each one uses, since that decision was made by someone who did not know your threat model.
Write down what you found and what you decided for each, because this is precisely the kind of finding that gets rediscovered a year later by someone who assumes it was never reviewed.
Where randomness shows up in unexpected places
Two mobile-specific cases are worth knowing because they are easy to miss in a review.
Device or install identifiers generated at first launch. These often become de facto authentication material later, because some endpoint starts accepting them as a way to identify a user without a session. A predictable install identifier is then a predictable credential, and the value was generated years earlier by code nobody now owns.
Cache and file names derived from user content. Naming a downloaded file after a hash of its identifier is fine, and naming it after a counter can make one user's cached content addressable by another process on a device where the directory is readable.
Neither of these looks like a cryptographic decision at the moment it is written, which is precisely why they survive review.
A note on the values you do not control
Some of the randomness your app depends on is generated elsewhere, and it is worth knowing which.
Tokens issued by your identity provider, nonces produced by a payment SDK, and identifiers assigned by your backend are all outside the client's control. The client's job with those is to store them properly and to avoid weakening them, not to second-guess their construction.
Where it does become your problem is when the client generates something the server then trusts. A device-generated request identifier used for idempotency, a client-side nonce included in a signature, or a locally created invitation code all cross that line, and each deserves the same treatment as a session token.
The rule that catches these is to ask, for any value the client produces, whether the server makes a decision based on it. If it does, the client is generating security material whether or not anyone called it that.
Why this survives review
The reason weak randomness reaches production so reliably is that it reads as ordinary code.
A line constructing a general-purpose generator looks like every other line around it. There is no dangerous-looking API call, no obviously sensitive string, and no compiler warning. A reviewer scanning for security problems is looking for hardcoded secrets and missing authorization, and a generator choice does not trip either instinct.
It also produces output that passes every casual check. The values look varied, they do not repeat in testing, and a distribution test would pass, because a general-purpose generator is genuinely good at being statistically random. The property it lacks, resistance to prediction by someone who knows the algorithm, is invisible from the output.
So the only reliable defence is a deliberate search rather than a hope that review catches it, which is why the audit above is worth scheduling rather than intending.
What to take away
Use the platform cryptographic generator for anything that must be unguessable, and the general-purpose one only for values where prediction has no consequence.
Do not seed a secure generator yourself, do not derive security values from time or device identifiers, and do not truncate output to a length that can be searched.
Classify each value as needing unpredictability, needing only uniqueness, or needing neither, and let that classification drive the length.
Verify by searching for the general-purpose generator and reviewing every use, including in dependencies and on the server.
And when you fix it, expire the values that were issued weakly, because changing the generator does nothing for credentials already in circulation.




