fwknop.erl:78:25: Warning: crypto:rand_uniform/2 is deprecated; use rand:uniform/1 instead
As far as I can tell, rand_uniform is not cryptographically secure, whereas crypto:rand_uniform was intended to be, and probably (?) was, at least from about OTP 20 onwards.
When running crypto 3.0 it uses BN_rand_range which is cryptographigally secure. So it depends on which version of libcrypto you are using. The deprecation of crypto:rand_uniform was done long before crypto 3.0 was created.
So using rand instead is a good option. If you want something that is cryptographically secure no matter which version of crypto you have, you should use strong rand bytes.
This should probably be made clear in the crypto docs. A PR would be most welcome.
I decided to abandon the security code I was trying to update. It’s from a project that’s looking semi-abandoned and I decided leaving it semi-broken was better than risking introducing security problems because I’m not confident I know everything about the code.
The project is ‘fwknop’. It’s a good little tool (it protects SSH servers from being scanned from all over the internet), but its popularity has waned, in part because ‘wireguard’ has overlapping properties and is more popular. The github repo doesn’t have many recent commits, the mailing list has no posts for several years and the code has rotted in several ways.
The builtin random number generator algorithms are not cryptographically strong. If a cryptographically strong random number generator is needed, use something like crypto:rand_seed/0.
So it would seem that the following is safe:
crypto:rand_seed().
rand:uniform(10).
However, it seems that the only way to be sure that rand is using the secure seed provided by crypto is to wrap every call to rand with a check that rand:export_seed/0 returns {crypto, _}:
1> rand:uniform(10) % insecure!
.
9
2> {crypto, _} = rand:export_seed(), rand:uniform(10).
** exception error: no match of right hand side value {exsss,[...]}
3> crypto:rand_seed().
{#{type => crypto, ...}, no_seed}
4> {crypto, _} = rand:export_seed(), rand:uniform(10).
3
This is not ideal, as it could be possible to forget to call rand:export_seed, or accidentally remove it, ending up with insufficient randomness. It would be nice if rand had a mode that allowed us to be sure we are getting cryptographically secure randomness. With crypto:rand_uniform, this is unambiguous.
I don’t know why you would need to wrap all calls torand…
randhas a mode that guarantees cryptographically secure random numbers, and that mode is invoked by calling crypto:rand_seed/0 or friends. After that all calls to e.g rand:uniform/1, in the same process returns strong random numbers from crypto.
Or you can keep the return value from crypto:rand_seed/0 and use that explicit state with the rand:*_s/* functions, from any process.
The rand module has a framework for creating numbers of any range, and floats. Having a new set of API functions for every new algorithm is not feasible. Doubling all API functions just for the sake of cryptographically strong numbers would be awkward, so they live within the same API as all other pseudo random number algorithms, but with initialization from crypto…
I think there are two issues for people who want secure randomness:
The default is not secure
The usage does not guarantee that the result is secure
Unless the call to rand comes immediately after either the crypto:rand_seed call or an assertion that it’s set, it’s possible that the randomness does not come from crypto. Another example in addition to the ones I mentioned in the post above is if, for example, rand’s algoritm gets changed somehow by other code before code that expects secure randomness is called.
crypto:rand_seed()
more_code() % this changes rand's seed
rand:uniform() % not cryptographically secure
Compared to:
crypto:rand_seed()
more_code() % this changes rand's seed
crypto:rand_uniform() % still secure
What I’m getting at is that the intention of the programmer who wrote crypto:rand_uniform() is quite clear, but with rand:uniform() it is necessary to check the surrounding context - which may not be clear, or may be incorrect due to coding errors/bugs. That is an area of potential defects that could be avoided with an explicit option.
To store state in the process dictionary is sometimes convenient, but frowned upon because it deviates from functional programming, and requires the discipline to ensure that the state in the process dictionary is not accidentally altered.
The explicit state functions in rand offers a functional interface which some would say must be the only right way, while others would prefer the convenience of not having to store the state.
The API has both alternatives.
The rand module is written for Pseudo Random Number Generation, and cryptographically strong generators is a special case in that algorithm class. That is stated in the section “Quality of the Generated Numberts”. The default is chosen for speed and distribution, for example simulations, not for cryptography.
Misunderstand me correctely ;-), I am sure you are.
But sometimes the code that is called between PRNG init and use is limited and all under your control, so you can trust your own discipline. And sometimes the code called is managed by a number of other teams in other parts of a big organization and then you don’t want to trust the collective to be disciplined enough.
So when security is at stake I agree that using the functional API with explicit state in general is the better choice since you can then verify a limited amount of code and prove (at least to yourself) that it is correct.
I notice a few things after thinking about this more:
crypto has provided strong_rand_bytes/1 since 2011, which is redundant due to rand:bytes/1 if I’ve understood correctly.
The crypto module also includes undocumented functions (rand_plugin_uniform/1 etc.) to support rand’s plugin API so has basically implemented this logic even if not indented to be called directly.
The jump part of rand’s plugin API does not apply to cryptographically secure random number generation, because the numbers are never predictable.
It feels to me that it might be cleaner for rand to state that it should not be used for cryptographic operations at all. Then crypto would not need to implement the Plugin API. It already has functions for bytes and integers. So would need maybe 1 or 2 more (normal and uniform_real)? This is also seems to be the intention in the commit that deprecated crypto:rand_uniform:
rand module should be used if not cryptographically strong is required.
If cryptographically strong is required, new cryptographically strong
functions should be added to crypto.
crypto:strong_rand_bytes/1 and rand:bytes/1 overlap but have different use cases. If it is cryptographically strong bytes that is needed, use crypto. rand also supports generating bytes, from any of the PRNG backends. So if you need to be able to switch backend, use rand, or if you need more lightweight not cryptographically strong bytes, and/or don’t want to waste entropy. To use rand solely for strong random bytes is, as we say in Sweden, to go over the river to fetch water. It only adds overhead since the plugin framework forces crypto’s bytes into 56 bit integers and converts them into bytes. Much ado about nothing…
The plugin functions are for the rand module and not very useful without it.
Correct. Jumping contradicts non-predictability so it doesn’t apply
In rand we have uniform for any integer range and for float, uniform_real for another variant of float, and normal for normal distribution float. For any random (pun intended) new features like shuffle that we add, we would get further into API multiplication since we have an API for these features in rand. Doubling them all into crypto would be awkward. For example the implementation of uniform range, normal distribution, shuffle, is tricky to get right, so one would want just one implementation, and some plug-in structure…
The commit commenter that deprecated crypto:rand_uniform was probably not aware of the plug-in work in rand for uniform integers.
Yes, this is the argument I am making too, except for all cryptographically strong operations. I think that the risks associated with having to go via rand, when you only want cryptographically strong operations are higher than the benefits achieved by being able to use rand’s API. I’m suggesting that a restricted subset in crypto - just like crypto:strong_rand_bytes/1 - only for cryptographic operations would be more desirable for the times when cryptographically strong guarantees are desired.
For what it’s worth; I am rewriting the documentation to clarify this situation and to recommend the explicit state rand functions for cryptographic applications.
The latest suggestion, in the PR I linked to above, is to export crypto:strong_rand_range/1, and point the deprecated function to that. But I don’t think any more of rand’s functionality belongs in crypto. The functions that OpenSSL’s libcrypto implements do, since they are building blocks for rand, but not all of the code in e.g rand:normal/* or the new rand:shuffle/1 etc.