- Не используйте 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), которое имеет определенное двоичное представление).