2009-08-21 14 views
15

Ho bisogno di una sostituzione per la funzione PHP rand() che utilizza un generatore di numeri casuali crittograficamente forte.replace rand() con openssl_random_pseudo_bytes()

La funzione openssl_random_pseudo_bytes() consente di accedere al potente generatore di numeri casuali, ma invia i dati come stringa di byte. Invece, ho bisogno di un numero intero compreso tra 0 e X.

Immagino che la chiave sia quella di ottenere l'output di openssl_random_pseudo_bytes() in un numero intero, quindi si può fare qualsiasi calcolo matematico su di esso che è necessario. Posso pensare ad alcuni modi di "forza bruta" di convertire da una stringa di byte a un intero, ma speravo in qualcosa ... elegante.

+0

mt_rand() darà numeri casuali molto migliore qualità di rand(), ma non è crittograficamente forte sia. – David

+0

attento, questa risposta è sbagliata. per essere più specifici, la parte "% $ range" non è corretta. Prendi il seguente scenario: vuoi generare numeri compresi tra 1 (incluso) e 4 (escluso) (dato che fai max-min). $ range = 3. Ora 3 non si divide uniformemente in 2^8, il che significa che 1 sarà più comune di 3. Stai molto attento, volevi creare un generatore di numeri casuali sicuro ma inavvertitamente reso insicuro, ecco perché si consiglia di utilizzare le implementazioni esistenti. – chacham15

+0

@ chacham15: Se capisci correttamente, in un intervallo di 8 bit ottieni 85 insiemi di [1,2,3] più un singolo [1,] rimasto, facendo sì che un "1" aumenti dell'1,17% rispetto a " 2 "o" 3 ", giusto? Come risolveresti questo problema? Ovviamente l'uso di un'implementazione * * non è un'opzione, o non avrei fatto la domanda. – tylerl

risposta

11

Utilizzando forniti suggerimenti, ho creato una goccia -in sostituzione di rand() usando OpenSSL. Lo includerò qui per i posteri.

L'opzione $ pedantic dà risultati privi di bias ricominciando da capo quando i risultati non saranno distribuiti uniformemente nell'intervallo possibile.

function crypto_rand($min,$max,$pedantic=True) { 
    $diff = $max - $min; 
    if ($diff <= 0) return $min; // not so random... 
    $range = $diff + 1; // because $max is inclusive 
    $bits = ceil(log(($range),2)); 
    $bytes = ceil($bits/8.0); 
    $bits_max = 1 << $bits; 
    // e.g. if $range = 3000 (bin: 101110111000) 
    // +--------+--------+ 
    // |....1011|10111000| 
    // +--------+--------+ 
    // bits=12, bytes=2, bits_max=2^12=4096 
    $num = 0; 
    do { 
     $num = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes))) % $bits_max; 
     if ($num >= $range) { 
      if ($pedantic) continue; // start over instead of accepting bias 
      // else 
      $num = $num % $range; // to hell with security 
     } 
     break; 
    } while (True); // because goto attracts velociraptors 
    return $num + $min; 
} 
+0

Ho aggiunto una risposta con una guida su come utilizzare CryptoLib per fare questo come una sostituzione. Questo è molto più sicuro (e ha un codice più efficiente) e fornisce anche un controllo di ripetibilità. – mjsa

+0

@mjsa Mentre sono sicuro che la tua libreria crittografica è ottima, questa domanda riguardava la meccanica del codice stesso, piuttosto che una richiesta per qualcuno di costruire un'altra libreria per farlo. – tylerl

+0

Questa non è una cattiva soluzione. Sto aiutando il backport di PHP 7 'random_int()' nei progetti PHP 5, e il nostro approccio funziona anche quando '$ max - $ min> PHP_INT_MAX'. Puoi trovare i nostri sforzi su Github sotto [random_compat] (https://github.com/paragonie/random_compat) se vuoi valutare con la tua prospettiva. –

8

La pagina di manuale per openssl_random_pseudo_bytes() ha un esempio che penso si desideri. Puoi semplicemente chiamare bin2hex() sull'uscita di openssl_random_pseudo_bytes() per convertire in un numero esadecimale, quindi hexdec() su quel valore da convertire in decimale.

$rand_num = hexdec(bin2hex(openssl_random_pseudo_bytes($length, $strong))); 

A quel punto è possibile eseguire qualsiasi calcolo si desideri ottenere un valore nell'intervallo desiderato. L'altra opzione (cheater) che potresti avere è di eseguire un comando di sistema per generare un numero casuale - ci sono alcune buone opzioni per i generatori di numeri casuali per vari sistemi operativi disponibili online.

+0

Questo non risponde completamente alla domanda, vedere le altre risposte di seguito. – Andrew

1

bene, basta usare hexdec sul risultato di openssl_random_pseudo_bytes e otterrai il numero intero. E 'elegante come si arriva :)

print hexdec('45261b8f'); 

> 1160125327 
+0

Non risponde alla domanda delle operazioni poiché rand prende un minimo e un massimo e questo restituisce solo un numero casuale. – nate

+0

Non direttamente, ma è piuttosto banale aggiungere questo, come si può vedere dalla soluzione fornita dall'operatore stesso. –

2

Heres una versione delle soluzioni di cui sopra, che non usa funzione ricorsiva chiama:

function secure_rand($min,$max) { 
    $range = $max - $min + 1; 
    if ($range == 0) return $min; 
    $length = (int) (log($range,2)/8) + 1; 
    $max = pow(2, 8 * $length); 
    $num = $max + 1; // Hackish, I know.. 
    while ($num > $max) { 
     $num = hexdec(bin2hex(openssl_random_pseudo_bytes($length,$s))); 
    } 
    return ($num % $range) + $min; 
} 
-1
function ($min,$max){ 
    $range = $max-$min+1; 
    do{ 
     $result = floor($range*(hexdec(bin2hex(openssl_random_pseudo_bytes(4)))/0xffffffff)); 
    } while($result == $range);  
    return $result + $min; 
} 
0

Il modo più semplice per fare questo (e la più sicura di tutte le opzioni qui) è quello di utilizzare CryptoLib che ha una funzione randomInt che fornisce un rimpiazzo per il rand.

Prima scaricare CryptoLib da e incollarla nel progetto: https://github.com/IcyApril/CryptoLib

Due, cadono in questo codice. sostituire il path/to/con la directory di cryptolib.php ed il minimo massimo con il numero minimo e massimo:

<?php 
    require_once('path/to/cryptoLib.php'); 

    $min = 1; 
    $max = 5; 

    $randomNum = CryptoLib::randomInt($min, $max); 
?> 

La documentazione completa CryptoLib e ': https://cryptolib.ju.je/

2

Dal PHP 7 è fuori ora, il Il modo più semplice per risolvere questo problema è sostituire tutte le istanze di mt_rand con random_int.

(Supponendo che hai effettuato l'aggiornamento, che è.)