2012-12-11 10 views
21

Attualmente sto usando guid NEWID() ma so che non è crittograficamente sicuro.Come posso generare un numero crittograficamente sicuro in SQL Server?

Esiste un modo migliore per generare un numero crittograficamente sicuro in SQL Server?

+0

@Endri Puoi chiarire cosa hai trovato mancante? 'CRYPT_GEN_RANDOM' sembra una risposta perfetta. – CodesInChaos

+0

@CodesInChaos 'CRYPT_GEN_RANDOM' non genera esattamente i numeri .. Naturalmente è possibile convertirli in Int, ma la lunghezza sarà quella richiesta? Ad esempio, se ho bisogno di generare numeri crittograficamente sicuri a 8 cifre, come posso usare 'CRYPT_GEN_RANDOM'? Inoltre, sono unici? –

+0

Inoltre sono curioso di qualsiasi altro modo di generare numeri sicuri in 'SQL'. –

risposta

11

Domanda interessante :)

penso che questo funzionerà: CRYPT_GEN_RANDOM

+0

Oh, questo è anche utilizzabile come fonte di numeri casuali! I numeri casuali sono noiosi in SQL Server. – usr

+0

Anche io ne ho bisogno per essere unico. Posso usare 'Convert (int, CRYPT_GEN_RANDOM (9, Converti (varbinary, NEWID())))'? –

+0

@ user1761123 - forse se potessi inserire * tutti * i tuoi requisiti nella tua domanda, le persone potrebbero avere la possibilità di rispondervi. Finora, sembra che debba essere unico e di dimensione 'int'. Ora, quale definizione di "crittograficamente sicuro" vuoi che usiamo? È questo ad esempio un nonce? –

26

CRYPT_GEN_RANDOM è documentato per restituire un "numero casuale di crittografia".

Richiede un parametro di lunghezza compreso tra 1 e 8000 che corrisponde alla lunghezza del numero da restituire in byte.

Per lunghezze < = 8 byte. Questo può essere lanciato su uno dei SQL Server integer types direttamente.

+-----------+------------------+---------+ 
| Data type |  Range  | Storage | 
+-----------+------------------+---------+ 
| bigint | -2^63 to 2^63-1 | 8 Bytes | 
| int  | -2^31 to 2^31-1 | 4 Bytes | 
| smallint | -2^15 to 2^15-1 | 2 Bytes | 
| tinyint | 0 to 255   | 1 Byte | 
+-----------+------------------+---------+ 

Tre di questi sono interi con segno e uno senza segno. Quanto segue utilizzerà ciascuna l'intera gamma dei rispettivi tipi di dati.

SELECT 
     CAST(CRYPT_GEN_RANDOM(1) AS TINYINT), 
     CAST(CRYPT_GEN_RANDOM(2) AS SMALLINT), 
     CAST(CRYPT_GEN_RANDOM(4) AS INT), 
     CAST(CRYPT_GEN_RANDOM(8) AS BIGINT) 

È inoltre possibile fornire un valore inferiore rispetto all'archiviazione del tipo di dati.

SELECT CAST(CRYPT_GEN_RANDOM(3) AS INT) 

In questo caso è possibile restituire solo numeri positivi. Il bit di segno sarà sempre 0 poiché l'ultimo byte viene considerato come 0x00. L'intervallo di numeri possibili che possono essere restituiti da quanto sopra è compreso tra 0 e POWER(2, 24) - 1 incluso.

Supponiamo che sia necessario generare un numero casuale tra 1 and 250.

Un possibile modo di farlo sarebbe

SELECT (1 + CAST(CRYPT_GEN_RANDOM(1) AS TINYINT) % 250) AS X 
INTO #T 
FROM master..spt_values V1, master..spt_values 

Tuttavia questo metodo ha un problema.

SELECT COUNT(*),X 
FROM #T 
GROUP BY X 
ORDER BY X 

Le prime dieci righe di risultati sono

+-------+----+ 
| Count | X | 
+-------+----+ 
| 49437 | 1 | 
| 49488 | 2 | 
| 49659 | 3 | 
| 49381 | 4 | 
| 49430 | 5 | 
| 49356 | 6 | 
| 24914 | 7 | 
| 24765 | 8 | 
| 24513 | 9 | 
| 24732 | 10 | 
+-------+----+ 

numeri più bassi (in questo caso 1 -6) vengono generati due volte regolarmente come gli altri perché ci sono due possibili ingressi alla funzione modulo che può generare ciascuno di questi risultati.

Una possibile soluzione potrebbe essere quella di eliminare tutti i numeri> = 250

UPDATE #T 
SET X = CASE 
      WHEN Random >= 250 THEN NULL 
      ELSE (1 + Random % 250) 
      END 
FROM #T 
CROSS APPLY (SELECT CAST(CRYPT_GEN_RANDOM(1) AS TINYINT)) CA (Random) 

Questo sembra funzionare sulla mia macchina, ma probabilmente non è garantito che SQL Server valuterà solo la funzione di volta in entrambi i riferimenti a Random nell'espressione CASE. Inoltre lascia ancora il problema di aver bisogno di passate seconde e successive per sistemare le righe NULL in cui il valore casuale è stato scartato.

Dichiarare una UDF scalare può risolvere entrambi questi problemi.

/*Work around as can't call CRYPT_GEN_RANDOM from a UDF directly*/ 
CREATE VIEW dbo.CRYPT_GEN_RANDOM1 
AS 
SELECT CAST(CRYPT_GEN_RANDOM(1) AS TINYINT) AS Random 

go 


CREATE FUNCTION GET_CRYPT_GEN_RANDOM1() 
RETURNS TINYINT 
AS 
BEGIN 
    DECLARE @Result TINYINT 

    WHILE (@Result IS NULL OR @Result >= 250) 
      /*Not initialised or result to be discarded*/ 
     SELECT @Result = Random FROM dbo.CRYPT_GEN_RANDOM1 

    RETURN @Result 

END 

E poi

UPDATE #T 
SET X = dbo.GET_CRYPT_GEN_RANDOM1() 

alternativa e più dritto in avanti si potrebbe semplicemente usare

CAST(CRYPT_GEN_RANDOM(8) AS BIGINT) % 250 

Sulla base del fatto che la gamma di bigint è così enorme che ogni pregiudizio sarà probabilmente insignificante . Ci sono 73.786.976.294.838.208 modi che possono essere generati da 1 e 73.786.976.294.838.206 che 249 possono essere dalla query sopra.

Se anche questo piccolo bias non è consentito, è possibile scartare qualsiasi valore NOT BETWEEN -9223372036854775750 AND 9223372036854775749 come illustrato in precedenza.

Problemi correlati