-
Notifications
You must be signed in to change notification settings - Fork 362
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a CSPRNG (ie. SecureRandom) to the Kotlin StdLib #184
Comments
Could you expand the proposal with some details?
Also I remember there was an observation by @DALDEI that some not-precisely-specified generic "secure" random may not meet the requirements of real-world use cases. |
@ilya-g: Before you read this, just a heads up, I modified the top post since you last read it. Fundamental Language QuestionI believe that this entire question around expectations of an RNG really hinges upon the primary users and intended users of the language. Accedemic UsageIf the primarily intended usage of the language is purely academic, then a deterministic PRNG is a fast and efficient way of supplying RNG. In an accedemic setting PRNG have the following advantages:
In an academic setting a PRNG has the following disadvantages:
Production UsageIf the primary intended usage of the language is pure as corporate/production workhorse, for example in Mobile Apps, Web Servers, and Web Frontends, there are different expectations of a deterministic RNG. In a production codebase deterministic PRNG have the following advantages:
In a production codebase deterministic PRNG have the following disadvantages:
In my opinion, Kotlin seems to be primarily used as a production workhorse language. Answering your QuestionsI've spent some time thinking about your question and trying to come up with some concrete answers after having discussed this issue with a few people.
I believe that the
I believe that the goal here is not to satisfy all use cases. The primary goal here should be to help protect developers who don't understand the pitfalls of insecure RNG from shooting themselves and their users in the foot. The goal shoud not be to solve all possible use cases.
If the opportunity presented itself to be able to do this over again, ideally, As a naive user with no deep understanding of software RNG, if I see an object or method called In this hypothetical world where we could do this over again, I'd name the interface Given that all the documentation on all of the types/methods in See all of the docs on methods/types under this package: My suggestion is to move the PRNG implementations so that they are accessable with method names that acurately describe what they are
Ideally, the API would pick the most secure default possible on the system. Here are the CSPRNG offered by Java: https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SecureRandom If the host system truly has no source of entropy then an exception should be thrown on construction time. I believe that these cases will be incredibly rare. |
@ilya-g Do you believe there's enough here to begin fleshing out a proposal PR? |
Our main consideration against making the default random implementation Could you estimate the performance of the proposed implementations and compare it to the performance of non-secure ones (e.g. JS |
I think an acceptable solution may be keeping the normal random number generator as a PRNG and adding the CSPRNG as another Random class, if sufficient warnings are given in the documentation about the dangers of using insecure random numbers. See, for example, the Python 3 random module documentation. While Python makes the default RNG a fast, deterministic PRNG, it makes sure to warn users and guide them to the alternative for security-conscious use cases. |
@ThePlasmaRailgun The problem is that defaults die hard and documentation generally isn't enough for nieve users. For example, I recently discovered a popular JVM web framework called RatPack was using a
When developers see "random" on a type, they believe that it is truly random and can not be predicted. The problem seems to be that there is industry-wide cognitive dissonance between what language & library designers expect from their users and what their users actually do. @ilya-g I am actually working on a response to your comment, but I'm busy with life things currently. I'll get to it as soon as is possible. |
(My knowledge is more towards software and security in general rather than the Kotlin ecosystem specifically, so my apologies in advance if my ignorance here renders most points moot.) Generally speaking developers can’t be expected to understand the entirety of CS as a discipline, and it’s for this reason specifically that we build abstractions at all. The naming of insecure random number generators as “Random” in many standard libraries is, in my opinion (though I believe it’s as well shared by those with far more authority) a mistake: “random" colloquially speaking means what “cryptographically secure random” means to those who know of the distinction. If it is at all possible to make this distinction clear directly in the API rather than indirectly (say, in the documentation), I’d strongly advice doing so. Admittedly this may be difficult at this point logistically (and it’s much for this reason that the poor naming exists in other libraries as a historical artifact), though it could look something like:
This does leave an extra noun which is a bit away from the ideal, but it avoids causing too much extra work for developers whilst keeping them secure. Regardless, at the very least lifting a As @JLLeitschuh has already indicated Kotlin itself should be able to punt on the difficult problem of secure implementations as Java provides |
Here are some benchmarks from what @karanlyons and I have been working together on: package randomBenchmark
import kotlin.random.Random
import kotlin.system.measureNanoTime
import java.security.SecureRandom
val secureRandom = SecureRandom()
fun secureBench(j: Int) {
for (i in 0..j) {
val res = ByteArray(4)
secureRandom.nextBytes(res)
}
}
fun insecureBench(j: Int) {
for (i in 0..j) {
val _res = Random.nextBits(32)
}
}
fun main() {
val totalRuns = 5
val runLength = 10000
for (i in 1..totalRuns + 1) {
val secureTime = measureNanoTime { secureBench(runLength) }
val insecureTime = measureNanoTime { insecureBench(runLength) }
println("Run $i:")
println(" ($i) secureTime: ${secureTime / runLength}ns")
println(" ($i) insecureTime: ${insecureTime / runLength}ns")
println(" ($i) Impact: ${secureTime / insecureTime.toDouble()}x\n")
}
}
As you can clearly see, once the JIT kicks in, the insecure RNG clearly outpaces the secure RNG. We would need to provide a sane warning to users that this was going to change, but I believe that changing this is an important move to protect the users of all of our software. |
A trivial consideration about My suggestion is to consider |
Proposal looks great. |
That's a really hard thing to do, especially when interfacing with other libraries.
The potential negative consequences of using insecure random in a secure random context is far worse than the performance issues. I think it's fundamentally a better problem to have that developers are finding out that their code is slow and they need to switch methods that they are using for RNG, than the alternative where developers figure out that they were using insecure RNG and need to issue a CVE number for every single previous release of their library due to insecure RNG. |
I can 100% see the perspective of keeping the default Random just the
existing simple case because its common use cases are in fact trivial: the
average developer is not doing cryptographic/security-adjacent things with
Random (though I'd love to be proven wrong with concrete data if that's not
the case!). Folks that _are_ generally know what they're looking for (I.e.
SecureRandom), so I don't think it's of any benefit to those that need it,
and just imposes overhead on the common case that doesn't need it. There
will of course be a nonzero number incorrect usages, but that's life and
there is no silver bullet for programmer error. No need to turn
SecureRandom into Maslow's hammer.
On a meta note: This thread feels a little like it's boiling down to "I
care about this, therefore everyone should" and discussing as if every
developer using random is using it for cryptography or suggesting
inherently pejorative names for the implementation they're against. I would
suggest avoiding this line of discussion. If you really want to give it a
name that makes them think about it, something like `SimpleRandom` is free
of pejoratives, still clarifies its role better, and is different enough
that users would be encouraged to read its documentation further if they
wanted to understand why it's denoted as "Simple".
…On Sat, Aug 17, 2019 at 5:34 PM Jonathan Leitschuh ***@***.***> wrote:
Better approach IMO would be to warn (or error) when someone tries to use
insecure Random in security-aware context.
That's a really hard thing to do, especially when interfacing with other
libraries.
But I'm a bit worried about making SecureRandom default implementation for
Random. But I'm a bit worried about making SecureRandom default
implementation for Random.
The potential negative consequences of using insecure random in a secure
random context is far worse than the performance issues. I think it's
fundamentally a better problem to have that developers are finding out that
their code is slow and they need to switch methods that they are using for
RNG, than the alternative where developers figure out that they were using
insecure RNG and need to issue a CVE number for every single previous
release of their library due to insecure RNG.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#184?email_source=notifications&email_token=AAKMJPV7M3CXSXODHWDARG3QFCKJDA5CNFSM4HCC2RU2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD4QV6GA#issuecomment-522280728>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAKMJPSS5MCMYWH5N6SJCSLQFCKJDANCNFSM4HCC2RUQ>
.
|
SimpleRandom is a good idea and could hint developers towards SecureRandom
for non simple use cases like ensuring security.
…On Sun, Aug 18, 2019, 05:02 Zac Sweers ***@***.***> wrote:
I can 100% see the perspective of keeping the default Random just the
existing simple case because it's common use cases are in fact trivial: the
average developer is not doing cryptographic/security-adjacent things with
Random (though I'd love to be proven wrong with concrete data if that's not
the case!). Folks that _are_ generally know what they're looking for (I.e.
SecureRandom), so I don't think it's of any benefit to those that need it,
and just imposes overhead on the common case that doesn't need it. There
will of course be a nonzero number incorrect usages, but that's life and
there is no silver bullet for programmer error. No need to turn
SecureRandom into Maslow's hammer.
On a meta note: This thread feels a little like it's boiling down to "I
care about this, therefore everyone should" and discussing as if every
developer using random is using it for cryptography or suggesting
inherently pejorative names for the implementation they're against. I would
suggest avoiding this line of discussion. If you really want to give it a
name that makes them think about it, something like `SimpleRandom` is free
of pejoratives, still clarifies its role better, and is different enough
that users would be encouraged to read its documentation further if they
wanted to understand why it's denoted as "Simple".
On Sat, Aug 17, 2019 at 5:34 PM Jonathan Leitschuh <
***@***.***>
wrote:
> Better approach IMO would be to warn (or error) when someone tries to use
> insecure Random in security-aware context.
>
> That's a really hard thing to do, especially when interfacing with other
> libraries.
>
> But I'm a bit worried about making SecureRandom default implementation
for
> Random. But I'm a bit worried about making SecureRandom default
> implementation for Random.
>
> The potential negative consequences of using insecure random in a secure
> random context is far worse than the performance issues. I think it's
> fundamentally a better problem to have that developers are finding out
that
> their code is slow and they need to switch methods that they are using
for
> RNG, than the alternative where developers figure out that they were
using
> insecure RNG and need to issue a CVE number for every single previous
> release of their library due to insecure RNG.
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <
#184?email_source=notifications&email_token=AAKMJPV7M3CXSXODHWDARG3QFCKJDA5CNFSM4HCC2RU2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD4QV6GA#issuecomment-522280728
>,
> or mute the thread
> <
https://github.com/notifications/unsubscribe-auth/AAKMJPSS5MCMYWH5N6SJCSLQFCKJDANCNFSM4HCC2RUQ
>
> .
>
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#184?email_source=notifications&email_token=ABVG6BLH7UIQP3ISNJFSJ3DQFC3TNA5CNFSM4HCC2RU2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD4QXM7Q#issuecomment-522286718>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABVG6BL6XIIWWUBM255GPGTQFC3TNANCNFSM4HCC2RUQ>
.
|
But it is definitely much better than breaking backward compatibility for everyone. (yes, huge performance impact in default implementation is definitely breaking of backwards compatibility IMO). Hard != impossible. I think kotlin should give developers abilities to make their libraries more secure, not restrict to be insecure. Disclaimer: I completely understand why you are care so much about this security stuff, but please think about millions of other developers who most likely will not develop something security-aware at all. |
Another example of insecure RNG that I found leaving potentially thousands of companies and potentially millions of users data exposed due to insecure RNG in a code generator called JHipster & JHipster Kotlin. This vulnerability ended up with a a CVSS v3 score of 9.8/10 https://nvd.nist.gov/vuln/detail/CVE-2019-16303 Here's the Java issue: jhipster/generator-jhipster#10401 Here's the Kotlin issue: |
Following the discussion, there are three major scenarios for RNG:
Now the questions are
(1) How likely is each scenario?This is difficult to answer and would require analyzing a lot of projects. (2) What are the up- and downsides for each scenario?
(3) What's the best tradeoff?Let's assume that the default changes to a secure RNG. Looking at the table above there are three constellations to consider now:
(4) How to ease migration?In case that the default changes or that developers have to explicitly choose between
Considering the ever increasing number of security / privacy breaches that are happening and that hackers and hacks are becoming more sophisticated, focusing on a stronger secure-by-default stdlib is certainly worth considering. |
In my opinion, a cryptographic RNG is appropriate for the "security is important" and "neither is important" cases. As I write in "Random Number Generator Recommendations for Applications":
The "performance is important" case occurs mostly in applications such as simulations and machine learning, where security is generally not a concern, but reproducibility of "random" numbers may be. Even here, a cryptographic RNG can be useful (at least for the purpose of setting a seed for the "faster" PRNG). For the "performance is important" case, the PRNG should be both high-quality and fast, and for stability purposes the PRNG implementation should use the specific algorithm in its name (not just a generic one like "Random"). Some applications care about reproducibility, and some don't. (And in fact, random numbers are not the only source of nondeterminism or inconsistency; another major one is floating-point numbers, and others include thread scheduling, parallelism, as well as changes in algorithms used by the application or libraries it uses.) For most applications that don't care about reproducibility, a cryptographic RNG is appropriate.
The issue with shuffling is not whether a secure or insecure RNG is used, but whether the RNG is capable of producing any given arrangement (permutation) of a particular list. For a PRNG, this generally depends on the size of the list and the PRNG's state size. See my section on shuffling. |
In my experience it is quite common, especially in web applications, that developers require values that are supposed to be unpredictable and/or secret (e.g. API keys, session tokens, generated user passwords, password reset tokens etc.). The use cases of secure RNG's are definitely not limited to cryptographic keys and winning lottery numbers. Of course developers should be aware of this, but in practice a lot of people make the assumption that "random" means "unpredictable" and just pick the default RNG the standard library provides to them. However, I think there might be some middle ground between a "RNG suitable for cryptographic purposes" and "RNG of which the seed can be trivially recovered after observing some output". In my opinion a default random number generator should have decent performance, be suitable for statistical application, optionally support reproducibility (i.e. manually setting a seed) and try to avoid introducing surprising security vulnerabilities in most use cases. I'd say standard library RNG's could be roughly subdivided in the following three types: 1. A specialized secrets generator 2. A fast but predictable RNG 3 A default RNG decent at most use cases Ideally, I would say something like RNG #3 would be a decent default. While a modern stream ciphers like Salsa20 won't beat the performance of Java's LCG generator, it is still very fast and does not require anything from the OS after initial seeding. Note that the deterministic CSRNG's used by SecureRandom implemenations are probably pretty decent as well, so that may be a better choice than using a Kotlin stream cipher implementation or a native library. |
I'm sorry to close this great discussion, but there are two fundamental problems here: First of all, this KEEP is not the right place for it (see https://github.com/Kotlin/KEEP#contributing-ideas). More importantly, though, the Kotlin Standard library is not the right place for any kind of cryptography primitives. Secure cryptographic primitives are very domain-specific. There are tons of conflicting requirements that need to be met in any cryptography design with tradeoffs in performance and security, there are country-specific and industry-specific rules, regulations, and recommendations, there is constant ongoing research and a never-ending stream of vulnerabilities and exploits that are being found and need to be tracked and addressed by anyone who maintains any kind of a secure system. From the standpoint of Kotlin, it means that any security-related cryptographic primitives must live in their own library and be maintained by a dedicated team with the corresponding expertise and credentials in cryptography. |
I came here from an internet search looking for a SecureRandom implementation for Kotlin, that I could use in a Multiplatform Mobile project. This is a great discussion, but ends without resolving anything. Closing the loop for future folks like me 👋... there is a SecureRandom implementation via krypt-csprng that does essentially what this issue description proposes; create a Kotlin subclass of Random that delegates the work to the platform's SecureRandom implementation. |
The need for secure sources of randomness is not domain specific. Secure randomness is widely applicable. The most common use case you will see, and see done wrong, is the creation of a random string for, as an example, password reset tokens.
This rationale didn't stop JetBrains from going forward and implementing an entire HTTP server stack in the form of KTor. Honestly, the risk introduced from implementing an HTTP server, and HTTP body parser incorrectly has a much higher risk to end user software than the security a "good enough" CSPRNG offers.
I agree, and that solution should be centralized into a standard library that the entire industry uses, instead of being tacked-on at the end by third party libraries. It's a refrain of the security industry that "security should be opt-out, not opt-in". Because the Kotlin standard library doesn't support a secure source of randomness, security in this area will always be opt-in. |
The fact that Kotlin does not support CSPRNG out of the box honestly drives me crazy. As far as I know, almost all popular languages have their built-in |
Problem
Kotlin 1.3 added support for an official Koltin
Random
which is awesome!Without an official Cryptographically secure pseudorandom number generator (ie.
SecureRandom
) Kotlin can't by default support creating truly random numbers without platform-specific implementations.Dan Kaminsky did an interesting talk at Defcon about why more standard libraries should offer CSPRNG by default. So many software security bugs have been caused purely because someone on the implementation side goofed and used a predictable Random number generator.
(The part covering random number generators starts at 17 min)
https://youtu.be/xneBjc8z0DE?t=1021
Java and Kotlin's
Random
is a Pseudo Random Number Generator (PRNG). (A "linear congruential PRNG").This means the next value can easily be calculated if you have two previous values.
https://crypto.stackexchange.com/questions/51686/how-to-determine-the-next-number-from-javas-random-method
According to @ilya-g:
This is the original discussion thread on slack from October 29th, 2018:
https://kotlinlang.slack.com/archives/C0922A726/p1540830858324300
Solution
Add official support for a CSPRNG to the Kotlin standard library.
JVM
This could just be implemented as a decorator over Java's
SecureRandom
Native
For Linux/Unix based machines, this could pull entropy or actual values from
/dev/urandom
.For windows, a similar API also exists.
Javascript
Most modern browsers support
crypto.getRandomValues
as does NodeJS.Use Cases for Secure Random
Here are some places you really don't want a predictable RNG.
Standards
Here's a quote from the documentation on Java's
SecureRandom
:Any implementation of Secure Random should also follow these similar standards.
Additional Resources & Justification
The text was updated successfully, but these errors were encountered: