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:
Regis Crelier 2015-10-15 10:16:03 -07:00
parent 72328eaf93
commit 09954546fb
7 changed files with 170 additions and 24 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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) \

View file

@ -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 {

View file

@ -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);

View file

@ -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

View 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));
}