mediawiki.user: Increase entropy of random token generator
authorNuria Ruiz <nuria@wikimedia.org>
Wed, 22 Aug 2018 03:11:59 +0000 (20:11 -0700)
committerJdlrobson <jrobson@wikimedia.org>
Thu, 23 Aug 2018 20:46:22 +0000 (20:46 +0000)
generateRandomSessionId now generates a token with 80 bits and it is
unique in a 150 billion space.

The token is used to identify sessions in MediaWiki but also
as a generic random generator across the MediaWiki codebase.

Bug: T201124
Change-Id: I63a241a70b99d9f9e691eac25bb5cfe099a748fc

resources/src/mediawiki.user.js
tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js

index b2985d1..bf23da1 100644 (file)
@@ -2,7 +2,7 @@
  * @class mw.user
  * @singleton
  */
-/* global Uint32Array */
+/* global Uint16Array */
 ( function ( mw, $ ) {
        var userInfoPromise, pageviewRandomId;
 
                 * mobile usages of this code is probably higher.
                 *
                 * Rationale:
-                * We need about 64 bits to make sure that probability of collision
-                * on 500 million (5*10^8) is <= 1%
-                * See https://en.wikipedia.org/wiki/Birthday_problem#Probability_table
+                * We need about 80 bits to make sure that probability of collision
+                * on 155 billion  is <= 1%
                 *
-                * @return {string} 64 bit integer in hex format, padded
+                * See https://en.wikipedia.org/wiki/Birthday_attack#Mathematics
+                * n(p;H) = n(0.01,2^80)= sqrt (2 * 2^80 * ln(1/(1-0.01)))
+
+                * @return {string} 80 bit integer in hex format, padded
                 */
                generateRandomSessionId: function () {
                        var rnds, i,
-                               hexRnds = new Array( 2 ),
+                               hexRnds = new Array( 5 ),
                                // Support: IE 11
                                crypto = window.crypto || window.msCrypto;
 
-                       if ( crypto && crypto.getRandomValues && typeof Uint32Array === 'function' ) {
-                               // Fill an array with 2 random values, each of which is 32 bits.
-                               // Note that Uint32Array is array-like but does not implement Array.
-                               rnds = new Uint32Array( 2 );
+                       if ( crypto && crypto.getRandomValues && typeof Uint16Array === 'function' ) {
+
+                               // Fill an array with 5 random values, each of which is 16 bits.
+                               // Note that Uint16Array is array-like but does not implement Array.
+                               rnds = new Uint16Array( 5 );
                                crypto.getRandomValues( rnds );
+
                        } else {
-                               rnds = [
-                                       Math.floor( Math.random() * 0x100000000 ),
-                                       Math.floor( Math.random() * 0x100000000 )
-                               ];
+
+                               // 0x10000 is 2^16 so the operation below will return a number
+                               // between 2^16 and zero
+                               for ( i = 0; i < 5; i++ ) {
+                                       rnds[ i ] = Math.floor( Math.random() * 0x10000 );
+                               }
                        }
-                       // Convert number to a string with 16 hex characters
-                       for ( i = 0; i < 2; i++ ) {
-                               // Add 0x100000000 before converting to hex and strip the extra character
+                       // Convert the 5 16bit-numbers into 20 characters (4 hex chars per 16 bits)
+                       for ( i = 0; i < 5; i++ ) {
+                               // Add 0x1000 before converting to hex and strip the extra character
                                // after converting to keep the leading zeros.
-                               hexRnds[ i ] = ( rnds[ i ] + 0x100000000 ).toString( 16 ).slice( 1 );
+                               hexRnds[ i ] = ( rnds[ i ] + 0x10000 ).toString( 16 ).slice( 1 );
                        }
 
                        // Concatenation of two random integers with entropy n and m
index 7044069..f223ef7 100644 (file)
@@ -70,7 +70,7 @@
                result = mw.user.generateRandomSessionId();
                assert.strictEqual( typeof result, 'string', 'type' );
                assert.strictEqual( result.trim(), result, 'no whitespace at beginning or end' );
-               assert.strictEqual( result.length, 16, 'size' );
+               assert.strictEqual( result.length, 20, 'size' );
 
                result2 = mw.user.generateRandomSessionId();
                assert.notEqual( result, result2, 'different when called multiple times' );
@@ -91,7 +91,7 @@
                result = mw.user.generateRandomSessionId();
                assert.strictEqual( typeof result, 'string', 'type' );
                assert.strictEqual( result.trim(), result, 'no whitespace at beginning or end' );
-               assert.strictEqual( result.length, 16, 'size' );
+               assert.strictEqual( result.length, 20, 'size' );
 
                result2 = mw.user.generateRandomSessionId();
                assert.notEqual( result, result2, 'different when called multiple times' );
                var result = mw.user.getPageviewToken(),
                        result2 = mw.user.getPageviewToken();
                assert.strictEqual( typeof result, 'string', 'type' );
-               assert.strictEqual( /^[a-f0-9]{16}$/.test( result ), true, '16 HEX symbols string' );
+               assert.strictEqual( /^[a-f0-9]{20}$/.test( result ), true, '20 HEX symbols string' );
                assert.strictEqual( result2, result, 'sticky' );
        } );