Search code examples
javasecuritycryptographyuuid

Do you really need secure random UUIDs?


I'm encountering this issue not the first time, projects that use UUID a lot have performance impact generating them sometimes having seconds to generate a UUID.

I know why this happens. This question is not about how to fix it there is plenty of information there. I'm more curious if it's actually so common that it deserves to be a default.

Java defensively made it default to use SecureRandom when you generate UUID.randomUUID(), however most people use UUIDs as IDs for different things they put into a database, like a user, trade, order id, or any other entity id... but those are not sensitive in terms of security (or am I wrong?), if you are a hacker and you figured out how to guess these ids it does not give you any edge. It matters if someone generates a uuid and does MD5 over it and stores it as default password for new accounts... very rare scenario, but is obvious that it is sensitive data and true randomness is important here.

My argument here is that 99% of uses of UUID.randomUUID() do not actually care about true randomness and using secure random is not desirable behaviour for them. Have I missed something here? I'm not a hacker and I had situations where something seemed harmless but ended up being huge deal in terms of security.

So my question is : assuming that people don't use generated UUIDs on sensitive data such as private key/password generation, is it really important to have secure randomness for entity ids? What are the cases where this really matters in UUID generation?


Solution

  • A random (v4) UUID has to provide approximately 128 bits of randomness. In order to do that correctly, it's required to use a PRNG with at least 128 bits of entropy so that the outputs don't end up being correlated or having patterns that prevent them from being independent. That PRNG must also not expose its internal state for the same reason, which means it almost certainly needs to be a CSPRNG.

    In addition, UUIDs need to be universally unique: that's why they're called universally unique identifiers. If they were based on a weak PRNG, like one with a 32-bit seed, especially where that seed is based on something simple like the time, different systems could generate identical UUIDs. This could especially be the case if you have a large fleet of systems being deployed at once which all started their time-based weak PRNGs around the same time.

    Furthermore, there are CSPRNGs that are extremely fast. ChaCha20, which is the PRNG used in the Linux kernel, can output over 3 GB/s per core. Rust uses ChaCha12 as its default PRNG, which is also cryptographically secure and even faster, and it can be configured to use a per-thread option. I don't know what the default option is for Java, but there's no reason it intriniscally needs to be slow, and since there are many fast, secure options, there's little reason to use weak options when you don't have to.

    Finally, it makes sense to use secure defaults. If we gave people an option between fast but non-unique and slower but unique, many people would choose wrong and end up with breakage or security problems because it's a little bit faster. We don't offer those options because, as I mentioned, it's not prudent to offer options which are likely to lead to breakage or security vulnerabilities when there is little need for them and a lot of downsides.