必须重新计算哈希比对而非解密——因HASHBYTES单向,需查盐、拼接(顺序/编码/空格/NULL一致)、转大写十六进制后比对BINARY字段。

不能直接用明文密码和数据库里存的哈希值做等值比对——SQL Server 里没有“解密哈希”的操作,HASHBYTES 是单向的,必须重新计算哈希再比对。
为什么 HASHBYTES('SHA2_512', @password) 比对总是失败
常见错误是把用户输入的密码原样传进 HASHBYTES,却忽略了数据库里存的哈希值其实是加盐后算出来的。如果盐值没参与本次计算,两个哈希值天然不等。
- 盐值必须从数据库中查出来(比如
SELECT @salt = email_salt FROM users WHERE username = @user),不能硬编码或复用 - 拼接顺序必须严格一致:
CONCAT(@password, @salt)和存库时用的顺序要完全一样 - 字符编码要统一:如果字段是
NVARCHAR,@password也得是NVARCHAR,否则HASHBYTES按字节算,UTF-16 和 UTF-8 的字节流不同 - 别用
+拼接 NULL:任一参数为 NULL,CONCAT返回 NULL,HASHBYTES也返回 NULL,导致恒假比对
存储过程里怎么写安全的登录校验逻辑
核心是三步:查盐 → 算新哈希 → 转字符串比对。直接在存储过程中完成,避免明文密码出库。
- 盐字段建议定义为
VARBINARY(16),用CRYPT_GEN_RANDOM(16)生成,每用户独立 - 哈希字段用
BINARY(64)存 SHA2_512 结果,别转成VARCHAR再存(易引入大小写/空格差异) - 比对前统一转大写十六进制:
CONVERT(VARCHAR(128), HASHBYTES('SHA2_512', CONCAT(@pwd, @salt)), 2),注意第三个参数2表示无0x前缀 - WHERE 条件里不要写
WHERE hash_col = HASHBYTES(...),因为类型不匹配(BINARYvsVARCHAR),隐式转换可能失效
容易被忽略的 NULL 和空格陷阱
看似简单的比对,实际在线上常因 NULL 或不可见字符崩掉。
-
HASHBYTES('SHA2_512', NULL)返回 NULL,如果密码字段允许 NULL,且没加IS NOT NULL判断,整个校验逻辑会跳过 - 用户输入密码前后带空格?用
TRIM(@password)预处理,否则'abc '和'abc'哈希值完全不同 - MySQL 和 SQL Server 之间同步盐值或哈希值时,
CONVERT(VARCHAR, @salt, 2)在 SQL Server 得到的是纯十六进制字符串,而 MySQL 的HEX(@salt)才对应;混用会导致跨库校验失败 - 如果业务允许空密码,必须显式判断:
IF LEN(TRIM(@password)) = 0 RAISERROR('密码不能为空', 16, 1),不能靠哈希层兜底
真正难的不是写出哈希语句,而是确保每次拼接的输入字节流完全一致——盐、密码、编码、空格、NULL,缺一不可。线上出问题,八成卡在其中某一个环节没对齐。

















