Питання Як я можу генерувати криптографічно захищений номер у SQL Server?


Я наразі користуюсь посібником NEWID() але я знаю, що це не криптографічно захищений.

Чи є кращий спосіб генерувати криптографічно захищений номер у SQL Server?


20
2017-12-11 09:42


походження


@ Ендрі Чи можете ви прояснити, чого ви не знайшли? CRYPT_GEN_RANDOM виглядає як відмінна відповідь. - CodesInChaos
@ CodesInChaos CRYPT_GEN_RANDOM не генерує точно номери .. Звичайно, ви можете перетворити їх в Int, але чи буде довжина потрібна? Наприклад, якщо мені потрібно створити 8-значні криптографічно захищені номери, як я можу використовувати CRYPT_GEN_RANDOM? Також вони унікальні?
Крім того, я зацікавлений в будь-якому іншому способі створення безпечних номерів у SQL.
Я сумніваюсь, що ви знайдете кращу відповідь, ніж та, яку дала Мартін Сміт, тому що все, що є надійним, є важким, і CRYPT_GEN_RANDOM виглядає як цілеспрямований для роботи. Якщо ви дійсно зацікавлені, ви, можливо, можете подивитися на RFC2898 і використовувати HASHBYTES для побудови функції виведення ключів. - chrisb


Відповіді:


Цікаве питання :)

Я думаю, що це буде працювати: CRYPT_GEN_RANDOM


11
2017-12-11 09:52



О, це навіть підходить як джерело випадкових чисел! Службові числа уточні в SQL Server. - usr
Я також потребую бути унікальним. Чи можу я використовувати Convert(int, CRYPT_GEN_RANDOM(9, Convert(varbinary, NEWID()))) ?
@ user1761123 - може, якщо б ви могли поставити все ваших потреб у ваш запит, люди можуть мати шанс відповісти на це. Поки що це, схоже, повинно бути унікальним і про int розмір Тепер, яке визначення "криптографічно захищений" ви хочете, щоб ми використовували? Це буде, наприклад, невідомий? - Damien_The_Unbeliever
Я створюю форми, і кожна форма повинна мати власний код підтвердження, щоб користувач міг підтвердити свою автентичність, вставивши код підтвердження в текстовому вікні (саме тому я вважав, що підхід до інтерфейсу). У мене взагалі немає інших вимог
@ user1761123: чому це має бути унікальним тоді? - leppie


CRYPT_GEN_RANDOM задокументовано, щоб повернути "криптографічне випадкове число".

Вона займає параметр довжини між 1 і 8000 який являє собою довжину числа, що повертається в байтах.

Для довжин <= 8 байт. Це може бути відкинуто на одну з Цілі типи SQL Server прямо.

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

Три з них підписані цілими числами та непідписаними. Наступна кожен буде використовувати весь спектр їх відповідних типів даних.

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)

Можна також надати меншу вартість, ніж зберігання типу даних.

SELECT CAST(CRYPT_GEN_RANDOM(3)  AS INT)

У цьому випадку можна повернути лише позитивні числа. Знаковий біт завжди буде 0, оскільки останній байт розглядається як 0x00. Діапазон можливих чисел, які можна повернути вище, знаходиться між 0 і POWER(2, 24) - 1 включно.

Припустимо, що вимога полягає в тому, щоб сформувати деяку випадкову кількість між 1 and 250.

Один із можливих способів зробити це було б

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

Однак цей метод має проблему.

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

Перші десять рядків результатів

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

Нижчі числа (у цьому випадку 1 -6) генеруються два рази частіше, ніж інші, оскільки існує два можливі входи до функції модуля, які можуть генерувати кожен з цих результатів.

Одним з можливих рішень буде відкинути всі числа> = 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)

Схоже, що це працює на моїй машині, але це, мабуть, не гарантується, що SQL Server буде оцінювати цю функцію лише в обох посиланнях на Random в CASE вираз Крім того, він все ще залишає проблему необхідності в другій та наступній пропуски, щоб виправити NULL рядки, де випадкове значення було відхилено.

Заявка скалярного UDF може вирішити обидві ці проблеми.

/*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

І потім

UPDATE #T
SET    X  = dbo.GET_CRYPT_GEN_RANDOM1()

Альтернативно і більш прямо вперед можна просто використовувати

CAST(CRYPT_GEN_RANDOM(8)  AS BIGINT) % 250

На тій підставі, що в асортименті bigint настільки величезний, що будь-яке упередження може бути незначним. Є 73,786,976,294,838,208 способів 1 може бути згенерована і 73,786,976,294,838,206, що 249 може бути з запиту вище.

Якщо навіть це невелике можливе упередження не дозволено, ви можете відкинути будь-які значення NOT BETWEEN -9223372036854775750 AND 9223372036854775749 як показано раніше.


25
2018-02-06 18:36