Spring Security - insufficient cryptographic randomness
The `SecureRandomFactoryBean` class in Spring Security by Pivotal has a vulnerability in certain versions that could lead to the generation of predictable random values when a custom seed is supplied. This vulnerability could lead to predictable keys or tokens in applications that depend on cryptographically-secure randomness. Applications that use this class may need to evaluate if any predictable tokens were generated that should be revoked.
The SecureRandomFactoryBean
class in Spring Security by Pivotal has a
vulnerability in certain versions that could lead to the generation of
predictable random values when a custom seed is supplied. This contradicted the
documentation that states that adding a custom seed does not decrease the
entropy. The cause of this bug is the use of the Java SecureRandom API in an
incorrect way. This vulnerability could lead to predictable keys or tokens in
applications that depend on cryptographically-secure randomness. This
vulnerability was fixed by Pivotal by ensuring that the proper seeding always
takes place.
Applications that use this class may need to evaluate if any predictable tokens were generated that should be revoked.
Technical Background
The SecureRandom
class in Java offers a cryptographically secure pseudo-random
number generator. It is often the best method in Java for generating keys,
tokens or nonces for which unpredictability is critical. When using this class
multiple algorithms may be available. An explicit algorithm can be selected by
calling (for example) SecureRandom.getInstance("SHA1PRNG")
. The seeding of an
instance generated this way happens as soon as the first bytes are requested,
not during creation.
Normally, when calling setSeed()
on a SecureRandom
instance the seed is
incorporated into the state, to supplement its randomness. However, when
calling setSeed()
on a instance newly created with an explicit algorithm there
is no state yet, therefore the seed will set the entire state and no other
entropy is used.
This is mentioned in the documentation for SecureRandom.getInstance()
:
https://docs.oracle.com/javase/7/docs/api/java/security/SecureRandom.html
The returned SecureRandom object has not been seeded. To seed the returned
object, call the setSeed method. If setSeed is not called, the first call to
nextBytes will force the SecureRandom object to seed itself. This self-
seeding will not occur if setSeed was previously called.
This text is misleading, as the first two sentences may give the impression that the instance could be unsafe to use without seeding, while the self-seeding will in fact be much safer than supplying the seed for almost all applications.
This is a well known flaw in the design that can lead to incorrect use that has been discussed before:
https://stackoverflow.com/a/27301156
You should never call setSeed before retrieving data from the "SHA1PRNG" in
the SUN provider as that will make your RNG (Random Number Generator) into a
Deterministic RNG - it will only use the given seed instead of adding the
seed to the state. In other words, it will always generate the same stream
of pseudo random bits or values.
Google noticed that on Android some apps depend on this unexpected usage, which made it difficult to change the behaviour.
https://android-developers.googleblog.com/2016/06/security-crypto-provider-deprecated-in.html
A common but incorrect usage of this provider was to derive keys for
encryption by using a password as a seed. The implementation of SHA1PRNG had
a bug that made it deterministic if setSeed() was called before obtaining
output.
Vulnerability
The SecureRandomFactoryBean
class in spring-security returns a SecureRandom
object with SHA1PRNG as explicit provider. It is optionally possible to set a
Resource as a seed:
security/core/token/SecureRandomFactoryBean.java#L37-L52
public SecureRandom getObject() throws Exception {
SecureRandom rnd = SecureRandom.getInstance(algorithm);
if (seed != null) {
// Seed specified, so use it
byte[] seedBytes = FileCopyUtils.copyToByteArray(seed.getInputStream());
rnd.setSeed(seedBytes);
}
else {
// Request the next bytes, thus eagerly incurring the expense of default
// seeding
rnd.nextBytes(new byte[1]);
}
return rnd;
}
The documentation of SecureRandomFactoryBean.setSeed()
states (contradictory
to the documentation of SecureRandom
itself):
Allows the user to specify a resource which will act as a seed for the
SecureRandom instance. Specifically, the resource will be read into an
InputStream and those bytes presented to the SecureRandom.setSeed(byte[])
method. Note that this will simply supplement, rather than replace, the
existing seed. As such, it is always safe to set a seed using this method
(it never reduces randomness).
When used with a seed this means that a SecureRandom
instance is generated in
the vulnerable way as described above. In other words, the Resource entirely
determines all output of this PRNG. If two different objects are created with
the same seed then they will return identical output. The note in the
documentation stating that it supplements the seed and can not reduce
randomness was therefore false.
Recommendation
The easiest way to prevent this vulnerability would be to request the first
byte even if a seed is set, before calling setSeed()
:
SecureRandom rnd = SecureRandom.getInstance(algorithm);
// Request the first byte, thus eagerly incurring the expense of default
// seeding and to prevent the seed from replacing the entire state.
rnd.nextBytes(new byte[1]);
if (seed != null) {
// Seed specified, so use it
byte[] seedBytes = FileCopyUtils.copyToByteArray(seed.getInputStream());
rnd.setSeed(seedBytes);
}
This, however, requires that no application depends on the current possibility
of using SecureRandom
fully deterministically.
Mitigation
Applications that use SecureRandomFactoryBean
with a vulnerable version of
Spring Security can mitigate this issue by not providing a seed with setSeed()
or ensuring that the seed itself has sufficient entropy.
Conclusion
Pivotal responded quickly and fixed the issue in the recommended way in Spring
Security. However, depending on the applications that use this library, keys
or tokens which were generated using insufficient randomness may still exist
and be in use. Applications that use SecureRandomFactoryBean
should
investigate if this may be the case and if any keys or tokens need to be
revoked.
Applications that rely on using SecureRandomFactoryBean
to generate
deterministic sequences will no longer work and should switch to a proper
key-derivation function.
Timeline
2019-03-08 Report sent to security@pivotal.io.
2019-03-09 Reply from Pivotal that they confirmed the issue and are working on a fix.
2019-03-18 Fixed by Pivotal in revision 9c1eac79e2abb50f7b01e77c2418566f2a30532f.
2019-04-02 Vulnerability report published by Pivotal.
2019-04-03 Spring Security 5.1.5, 5.0.12, 4.2.12 released with the fix.
2019-07-04 Advisory published by Computest.