【问题标题】:Loop through a table and encode a field with SHA256 base64 hash循环遍历表并使用 SHA256 base64 哈希对字段进行编码
【发布时间】:2020-07-01 01:08:34
【问题描述】:

我需要使用自己的 SHA256 base64 密码更新当前包含纯文本密码的列。为此,我使用游标循环遍历每条记录并对密码进行编码,但执行后所有记录都具有相同的编码密码。

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;

【问题讨论】:

  • 不使用盐就不要散列密码!
  • UPDATE @Table SET [Password] = @h64 应该是UPDATE @Table SET [Password] = @h64 where ID = @Id
  • @Dai 感谢您的提示。
  • @DaleK 天哪!我没有意识到。愚蠢的我!

标签: sql-server base64 sha256 database-cursor


【解决方案1】:
  • Don't hash passwords without using a salt!
    • 理想情况下,使用专为密码散列设计的散列函数,如 bcrypt 而不是 SHA 系列(因为 bcrypt 具有可配置的强度值,并将所有参数和字段包含在单个字符串值中,而手动散列意味着需要存储散列和盐分开)。
      • 但是 SQL Server 本身不支持 bcrypt。
      • 同时避免使用PWDENCRYPT,因为它已被弃用并且不允许您指定使用的哈希算法,请改用HASHBYTES
    • 您仍然可以使用CRYPT_GEN_RANDOM 安全地生成盐,这是一个加密安全的 RNG(它使用操作系统提供的值,可能是 PRNG 或基于硬件的 RNG)。
      • 请注意,在没有WHERE 子句的UPDATE 语句中使用CRYPT_GEN_RANDOM 是安全的,因为它会为每一行生成一个新数字。
  • 您不需要游标 - 您可以在单个 UPDATE 语句中执行此操作。
    • UPDATE 语句无论是否与游标一起使用,其行为都相同 - 因此,如果您想更新单行,则必须指定 WHERE [primaryKey] = pkValue 子句。
  • 始终避免将二进制数据存储为 Base64 编码的字符串 - 将二进制数据存储为 binary(n)varbinary(n)
    • 这是因为 SQL 默认使用不区分大小写的排序规则,但 Base64 区分大小写(Base16 不区分大小写),因此对 Base64 列进行查询可能会返回不正确的结果。
    • Base64 值比 binary 值多占用 33% 的空间 - 编码/解码使每个操作的成本高得多。
    • Base64 值在已经使用 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

RANDCRYPT_GEN_RANDOM 的注释

如上所述,CRYPT_GEN_RANDOM 是加密安全的 RNG,而 RAND 不是,因此RAND 不得用于生成加密盐值。

但我想展示另一个有趣的区别: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 循环内单独设置的。

rngrng2 列都以 0x00...003F... 开头,因为 RAND() 返回具有已定义二进制表示的 float (IEEE-754) 值。

【讨论】:

  • 太棒了!非常感谢您的宝贵时间和建议。
猜你喜欢
  • 1970-01-01
  • 2016-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-21
相关资源
最近更新 更多