mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 21:30:17 +00:00
Add a 'secure' constructor to Random in dart:math returning a cryptographically
secure random generator which reads from the entropy source provided by the embedder for every generated random value. Add a test. Fixes #1746. R=lrn@google.com Review URL: https://codereview.chromium.org/1398453004 .
This commit is contained in:
parent
72328eaf93
commit
09954546fb
|
@ -93,9 +93,10 @@ static RawTypedData* GetRandomStateArray(const Instance& receiver) {
|
|||
|
||||
|
||||
// Implements:
|
||||
// var state = ((_A * (_state[kSTATE_LO])) + _state[kSTATE_HI]) & _MASK_64;
|
||||
// _state[kSTATE_LO] = state & _MASK_32;
|
||||
// _state[kSTATE_HI] = state >> 32;
|
||||
// var state =
|
||||
// ((_A * (_state[_kSTATE_LO])) + _state[_kSTATE_HI]) & (1 << 64) - 1);
|
||||
// _state[_kSTATE_LO] = state & (1 << 32) - 1);
|
||||
// _state[_kSTATE_HI] = state >> 32;
|
||||
DEFINE_NATIVE_ENTRY(Random_nextState, 1) {
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Instance, receiver, arguments->NativeArgAt(0));
|
||||
const TypedData& array = TypedData::Handle(GetRandomStateArray(receiver));
|
||||
|
@ -145,8 +146,8 @@ uint64_t mix64(uint64_t n) {
|
|||
// hash = 0x5A17;
|
||||
// }
|
||||
// var result = new Uint32List(2);
|
||||
// result[kSTATE_LO] = seed & _MASK_32;
|
||||
// result[kSTATE_HI] = seed >> 32;
|
||||
// result[_kSTATE_LO] = seed & ((1 << 32) - 1);
|
||||
// result[_kSTATE_HI] = seed >> 32;
|
||||
// return result;
|
||||
DEFINE_NATIVE_ENTRY(Random_setupSeed, 1) {
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Integer, seed_int, arguments->NativeArgAt(0));
|
||||
|
@ -198,4 +199,25 @@ DEFINE_NATIVE_ENTRY(Random_initialSeed, 0) {
|
|||
return CreateRandomState(zone, seed);
|
||||
}
|
||||
|
||||
|
||||
DEFINE_NATIVE_ENTRY(SecureRandom_getBytes, 1) {
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Smi, count, arguments->NativeArgAt(0));
|
||||
const intptr_t n = count.Value();
|
||||
ASSERT((n > 0) && (n <= 8));
|
||||
uint8_t buffer[8];
|
||||
Dart_EntropySource entropy_source = isolate->entropy_source_callback();
|
||||
if ((entropy_source == NULL) || !entropy_source(buffer, n)) {
|
||||
const String& error = String::Handle(String::New(
|
||||
"No source of cryptographically secure random numbers available."));
|
||||
const Array& args = Array::Handle(Array::New(1));
|
||||
args.SetAt(0, error);
|
||||
Exceptions::ThrowByType(Exceptions::kUnsupported, args);
|
||||
}
|
||||
uint64_t result = 0;
|
||||
for (intptr_t i = 0; i < n; i++) {
|
||||
result = (result << 8) | buffer[i];
|
||||
}
|
||||
return Integer::New(result);
|
||||
}
|
||||
|
||||
} // namespace dart
|
||||
|
|
|
@ -90,14 +90,18 @@ patch class Random {
|
|||
.._nextState()
|
||||
.._nextState();
|
||||
}
|
||||
|
||||
/*patch*/ factory Random.secure() {
|
||||
return new _SecureRandom();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class _Random implements Random {
|
||||
// Internal state of the random number generator.
|
||||
final _state;
|
||||
static const kSTATE_LO = 0;
|
||||
static const kSTATE_HI = 1;
|
||||
static const _kSTATE_LO = 0;
|
||||
static const _kSTATE_HI = 1; // Unused in Dart code.
|
||||
|
||||
_Random._withState(Uint32List this._state);
|
||||
|
||||
|
@ -106,32 +110,33 @@ class _Random implements Random {
|
|||
// The constant A is selected from "Numerical Recipes 3rd Edition" p.348 B1.
|
||||
|
||||
// Implements:
|
||||
// var state = ((_A * (_state[kSTATE_LO])) + _state[kSTATE_HI]) & _MASK_64;
|
||||
// _state[kSTATE_LO] = state & _MASK_32;
|
||||
// _state[kSTATE_HI] = state >> 32;
|
||||
// var state =
|
||||
// ((_A * (_state[_kSTATE_LO])) + _state[_kSTATE_HI]) & ((1 << 64) - 1);
|
||||
// _state[_kSTATE_LO] = state & ((1 << 32) - 1);
|
||||
// _state[_kSTATE_HI] = state >> 32;
|
||||
// This is a native to prevent 64-bit operations in Dart, which
|
||||
// fail with --throw_on_javascript_int_overflow.
|
||||
void _nextState() native "Random_nextState";
|
||||
|
||||
int nextInt(int max) {
|
||||
const limit = 0x3FFFFFFF;
|
||||
if (max <= 0 || ((max > limit) && (max > _POW2_32))) {
|
||||
throw new ArgumentError("max must be positive and < 2^32:"
|
||||
" $max");
|
||||
if ((max <= 0) || ((max > limit) && (max > _POW2_32))) {
|
||||
throw new RangeError.range(max, 1, _POW2_32, "max",
|
||||
"Must be positive and <= 2^32");
|
||||
}
|
||||
if ((max & -max) == max) {
|
||||
// Fast case for powers of two.
|
||||
_nextState();
|
||||
return _state[kSTATE_LO] & (max - 1);
|
||||
return _state[_kSTATE_LO] & (max - 1);
|
||||
}
|
||||
|
||||
var rnd32;
|
||||
var result;
|
||||
do {
|
||||
_nextState();
|
||||
rnd32 = _state[kSTATE_LO];
|
||||
rnd32 = _state[_kSTATE_LO];
|
||||
result = rnd32 % max;
|
||||
} while ((rnd32 - result + max) >= _POW2_32);
|
||||
} while ((rnd32 - result + max) > _POW2_32);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -143,9 +148,7 @@ class _Random implements Random {
|
|||
return nextInt(2) == 0;
|
||||
}
|
||||
|
||||
// Constants used by the algorithm or masking.
|
||||
static const _MASK_32 = (1 << 32) - 1;
|
||||
static const _MASK_64 = (1 << 64) - 1;
|
||||
// Constants used by the algorithm.
|
||||
static const _POW2_32 = 1 << 32;
|
||||
static const _POW2_53_D = 1.0 * (1 << 53);
|
||||
static const _POW2_27_D = 1.0 * (1 << 27);
|
||||
|
@ -164,6 +167,46 @@ class _Random implements Random {
|
|||
static int _nextSeed() {
|
||||
// Trigger the PRNG once to change the internal state.
|
||||
_prng._nextState();
|
||||
return _prng._state[kSTATE_LO];
|
||||
return _prng._state[_kSTATE_LO];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class _SecureRandom implements Random {
|
||||
_SecureRandom() {
|
||||
// Throw early in constructor if entropy source is not hooked up.
|
||||
_getBytes(1);
|
||||
}
|
||||
|
||||
// Return count bytes of entropy as a positive integer; count <= 8.
|
||||
static int _getBytes(int count) native "SecureRandom_getBytes";
|
||||
|
||||
int nextInt(int max) {
|
||||
RangeError.checkValueInInterval(
|
||||
max, 1, _POW2_32, "max", "Must be positive and <= 2^32");
|
||||
final byteCount = ((max - 1).bitLength + 7) >> 3;
|
||||
if (byteCount == 0) {
|
||||
return 0; // Not random if max == 1.
|
||||
}
|
||||
var rnd;
|
||||
var result;
|
||||
do {
|
||||
rnd = _getBytes(byteCount);
|
||||
result = rnd % max;
|
||||
} while ((rnd - result + max) > (1 << (byteCount << 3)));
|
||||
return result;
|
||||
}
|
||||
|
||||
double nextDouble() {
|
||||
return (_getBytes(7) >> 3) / _POW2_53_D;
|
||||
}
|
||||
|
||||
bool nextBool() {
|
||||
return _getBytes(1).isEven;
|
||||
}
|
||||
|
||||
// Constants used by the algorithm.
|
||||
static const _POW2_32 = 1 << 32;
|
||||
static const _POW2_53_D = 1.0 * (1 << 53);
|
||||
}
|
||||
|
||||
|
|
|
@ -144,6 +144,7 @@ namespace dart {
|
|||
V(Random_nextState, 1) \
|
||||
V(Random_setupSeed, 1) \
|
||||
V(Random_initialSeed, 0) \
|
||||
V(SecureRandom_getBytes, 1) \
|
||||
V(DateNatives_currentTimeMillis, 0) \
|
||||
V(DateNatives_timeZoneName, 1) \
|
||||
V(DateNatives_timeZoneOffsetInSeconds, 1) \
|
||||
|
|
|
@ -60,6 +60,12 @@ class Random {
|
|||
@patch
|
||||
factory Random([int seed]) =>
|
||||
(seed == null) ? const _JSRandom() : new _Random(seed);
|
||||
|
||||
@patch
|
||||
factory Random.secure() {
|
||||
throw new UnsupportedError(
|
||||
"No source of cryptographically secure random numbers available.");
|
||||
}
|
||||
}
|
||||
|
||||
class _JSRandom implements Random {
|
||||
|
|
|
@ -9,23 +9,34 @@ part of dart.math;
|
|||
*
|
||||
* The default implementation supplies a stream of
|
||||
* pseudo-random bits that are not suitable for cryptographic purposes.
|
||||
*
|
||||
* Use the Random.secure() constructor for cryptographic
|
||||
* purposes.
|
||||
*/
|
||||
abstract class Random {
|
||||
/**
|
||||
* Creates a random number generator.
|
||||
*
|
||||
* The optional parameter [seed] is used
|
||||
* to initialize the internal state of the generator. The implementation of
|
||||
* the random stream can change between releases of the library.
|
||||
* The optional parameter [seed] is used to initialize the
|
||||
* internal state of the generator. The implementation of the
|
||||
* random stream can change between releases of the library.
|
||||
*/
|
||||
external factory Random([int seed]);
|
||||
|
||||
/**
|
||||
* Creates a cryptographically secure random number generator.
|
||||
*
|
||||
* If the program cannot provide a cryptographically secure
|
||||
* source of random numbers, it throws an [UnsupportedError].
|
||||
*/
|
||||
external factory Random.secure();
|
||||
|
||||
/**
|
||||
* Generates a non-negative random integer uniformly distributed in the range
|
||||
* from 0, inclusive, to [max], exclusive.
|
||||
*
|
||||
* Implementation note: The default implementation supports [max] values
|
||||
* between 1 and ((1<<32) - 1) inclusive.
|
||||
* between 1 and (1<<32) inclusive.
|
||||
*/
|
||||
int nextInt(int max);
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ async/schedule_microtask6_test: RuntimeError # global error handling is not supp
|
|||
math/double_pow_test: RuntimeError
|
||||
math/low_test: RuntimeError
|
||||
math/random_big_test: RuntimeError # Using bigint seeds for random.
|
||||
math/random_secure_test: RuntimeError # Issue 1746.
|
||||
|
||||
mirrors/abstract_class_test: RuntimeError # Issue 12826
|
||||
mirrors/class_declarations_test/none: RuntimeError # Issue 13440
|
||||
|
|
62
tests/lib/math/random_secure_test.dart
Normal file
62
tests/lib/math/random_secure_test.dart
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// Test that the secure random generator does not systematically generates
|
||||
// duplicates. Note that this test is flaky by definition, since duplicates
|
||||
// can occur. They should be extremely rare, though.
|
||||
|
||||
// Library tag to allow Dartium to run the test.
|
||||
library random_secure;
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
import 'dart:math';
|
||||
|
||||
main() {
|
||||
var results;
|
||||
var rng0;
|
||||
var rng1;
|
||||
var checkInt = (max) {
|
||||
var intVal0 = rng0.nextInt(max);
|
||||
var intVal1 = rng1.nextInt(max);
|
||||
if (max > (1 << 28)) {
|
||||
Expect.isFalse(results.contains(intVal0));
|
||||
results.add(intVal0);
|
||||
Expect.isFalse(results.contains(intVal1));
|
||||
results.add(intVal1);
|
||||
}
|
||||
};
|
||||
results = [];
|
||||
rng0 = new Random.secure();
|
||||
for(var i = 0; i <= 32; i++) {
|
||||
rng1 = new Random.secure();
|
||||
checkInt(1 << 32);
|
||||
checkInt(1 << (32 - i));
|
||||
checkInt(1000000000);
|
||||
}
|
||||
var checkDouble = () {
|
||||
var doubleVal0 = rng0.nextDouble();
|
||||
var doubleVal1 = rng1.nextDouble();
|
||||
Expect.isFalse(results.contains(doubleVal0));
|
||||
results.add(doubleVal0);
|
||||
Expect.isFalse(results.contains(doubleVal1));
|
||||
results.add(doubleVal1);
|
||||
};
|
||||
results = [];
|
||||
rng0 = new Random.secure();
|
||||
for(var i = 0; i < 32; i++) {
|
||||
rng1 = new Random.secure();
|
||||
checkDouble();
|
||||
}
|
||||
var cnt0 = 0;
|
||||
var cnt1 = 0;
|
||||
rng0 = new Random.secure();
|
||||
for(var i = 0; i < 32; i++) {
|
||||
rng1 = new Random.secure();
|
||||
cnt0 += rng0.nextBool() ? 1 : 0;
|
||||
cnt1 += rng1.nextBool() ? 1 : 0;
|
||||
}
|
||||
Expect.isTrue((cnt0 > 0) && (cnt0 < 32));
|
||||
Expect.isTrue((cnt1 > 0) && (cnt1 < 32));
|
||||
}
|
||||
|
Loading…
Reference in a new issue