L oop через таблицу и кодировать поле с помощью хэша SHA256 base64 - PullRequest
0 голосов
/ 19 марта 2020

Мне нужно обновить столбец, который в настоящее время содержит простой текстовый пароль со своим собственным паролем SHA256 base64. Для этого я использую курсор для l oop через каждую запись и кодирую пароль, но после выполнения все записи имеют одинаковый закодированный пароль.

DECLARE @hash AS VARBINARY(128); 
DECLARE @h64 AS VARCHAR(128);
DECLARE @pass AS VARCHAR(500);
DECLARE @id AS INTEGER;

DECLARE cursor1 CURSOR 
    FOR SELECT [ID] FROM dbo.Table
OPEN cursor1
FETCH NEXT FROM cursor1 INTO @id
WHILE @@FETCH_STATUS = 0  
    BEGIN   
        SET @pass = (SELECT [Password] FROM dbo.Table WHERE ID = @id);
        SET @hash = HASHBYTES('SHA2_256', @pass);
        SET @h64 = CAST(N'' AS xml).value('xs:base64Binary(sql:variable("@hash"))', 'varchar(128)');
        UPDATE dbo.Table SET [Password] = @h64;         
        FETCH NEXT FROM cursor1 INTO @id;  
    END;
DEALLOCATE cursor1;

1 Ответ

2 голосов
/ 20 марта 2020
  • Не используйте sh пароли без соли!
    • Идеально используйте функцию ha sh, разработанную специально для пароля хеширование подобно bcrypt вместо семейства SHA (поскольку bcrypt имеет настраиваемое значение прочности и включает все параметры и поля в одно строковое значение, тогда как хеширование вручную означает необходимость хранить га sh и соль отдельно).
      • Однако bcrypt изначально не поддерживается сервером SQL.
      • Также избегайте PWDENCRYPT, поскольку он устарел и не позволяет указать используемый алгоритм хеширования, вместо этого используйте HASHBYTES.
    • Вы все еще можете безопасно генерировать соли, используя CRYPT_GEN_RANDOM, который является криптографически безопасным ГСЧ (он использует значения, предоставленные операционной системой, которые могут быть ГСЧ или аппаратного ГСЧ).
      • Обратите внимание, что можно безопасно использовать CRYPT_GEN_RANDOM в операторе UPDATE без предложения WHERE, поскольку он будет генерировать новый номер для каждой строки.
  • Вам не нужен курсор - вы можете сделать это с помощью одного оператора UPDATE.
    • UPDATE операторы ведут себя одинаково независимо от того, используются они с курсором или нет - поэтому, если вы хотите обновить одну строку, вы должны указать предложение WHERE [primaryKey] = pkValue.
  • Всегда избегайте хранения двоичных данных в виде строк в кодировке Base64 - сохраняйте двоичные данные как binary(n) или varbinary(n).
    • Это потому, что SQL использует сортировку без учета регистра по умолчанию, но Base64 чувствительна к регистру (Base16 нечувствительна к регистру), поэтому выполнение запроса к столбцу Base64 может вернуть неверные результаты.
    • Значения Base64 занимают на 33% больше места, чем значения binary - и кодирование / декодирование делает каждую операцию намного более дорогой.
    • Значения Base64 не допускаются в SARGable при использовании в запросе, уже использующем значения binary, если только вы Base64 кодирует все значения - что просто глупо.

Вот как я бы это сделал (при условии, что я не смог использовать bcrypt):

ALTER TABLE
    dbo.Table
ADD
    [Salt] binary(16) NULL;

GO -- `GO` is necessary when using ALTER TABLE statements in the same query-batch as UPDATE statements.

ALTER TABLE
    dbo.Table
ADD
    [PasswordHash] binary(32) NULL; -- store hashes as binary values, not strings. SHA-256 produces a 256-bit (32-byte) long hash, so use a fixed-length `binary(32)` column.

GO

-- The [Salt] and [PasswordHash] columns need to be set in separate `UPDATE` queries (or using a single `UPDATE FROM` query) because of how `CRYPT_GEN_RANDOM` works.

UPDATE
    dbo.Table
SET
    [Salt] = CRYPT_GEN_RANDOM( 16 );

UPDATE
    dbo.Table
SET
    [PasswordHash] = HASHBYTES( 'SHA_256', [Password] + [Salt] );
    -- T-SQL uses `+` to concatenate binary values. Don't use `CONCAT` because it will return a `varchar` value with undefined conversion semantics from `binary` values.

GO

-- Finally, remove the old password information and make the new columns non-NULLable:

ALTER TABLE dbo.Table DROP COLUMN [Password];
GO

ALTER TABLE dbo.Table ALTER COLUMN [Salt] binary(16) NOT NULL;
GO

ALTER TABLE dbo.Table ALTER COLUMN [PasswordHash] binary(32) NOT NULL;
GO

Примечания по RAND против CRYPT_GEN_RANDOM

Как указано, CRYPT_GEN_RANDOM является криптографически безопасным ГСЧ, тогда как RAND нет, поэтому RAND не должен использоваться для генерации криптографических c значений соли .

Но я хотел бы продемонстрировать еще одно интересное отличие: RAND() будет возвращать одно и то же значение для каждой строки в запросе, тогда как CRYPT_GEN_RANDOM всегда возвращает разные значения. Убедитесь сами, запустив этот запрос:

DECLARE @foo TABLE (
    rowId int        NOT NULL IDENTITY PRIMARY KEY,
    cgr   binary(16)     NULL,
    rng   binary(16)     NULL,
    rng2  binary(16)     NULL
);

INSERT INTO @foo ( cgr, rng, rng2 ) VALUES
    ( NULL, NULL, NULL ),
    ( NULL, NULL, NULL ),
    ( NULL, NULL, NULL ),
    ( NULL, NULL, NULL ),
    ( NULL, NULL, NULL );

SELECT * FROM @foo;

UPDATE
    @foo
SET
    cgr = CRYPT_GEN_RANDOM( /*length:*/ 10 ),
    rng = RAND();

--

SELECT * FROM @foo;

--

DECLARE @i int = 1;
WHILE @i <= 5
BEGIN

    UPDATE
        @foo
    SET
        rng2 = RAND()
    WHERE
        rowId = @i;

    SET @i = @i + 1;
END;

SELECT * FROM @foo;

Дает мне этот окончательный вывод:

id   cgr                                   rng                                   rng2
1    0x2DEB1D8A8DAB1F65373E000000000000    0x00000000000000003FC75AD042AE086F    0x00000000000000003FE2C5C607959DFF
2    0x4F7F050C335330AF43E6000000000000    0x00000000000000003FC75AD042AE086F    0x00000000000000003FEB46BAA0391C3E
3    0xB23F1C1C4C860A9652EE000000000000    0x00000000000000003FC75AD042AE086F    0x00000000000000003FDA62960990C897
4    0x44C604D79B0BB19167F9000000000000    0x00000000000000003FC75AD042AE086F    0x00000000000000003FC04FEA23759748
5    0xCF7F9A4FA4EDD605ECC2000000000000    0x00000000000000003FC75AD042AE086F    0x00000000000000003FE3A8FA18BD83A9

Обратите внимание, что значения cgr являются уникальными, в то время как значения rng являются всеми то же самое - несмотря на то, что оба столбца установлены в одном и том же операторе UPDATE. Столбец rng2 имеет разные значения, но только потому, что каждая строка была задана индивидуально внутри WHILE l oop.

(Все столбцы rng и rng2 начинаются с 0x00...003F..., поскольку RAND() возвращает значение float (IEEE-754), которое имеет определенное двоичное представление).

...