哈希不是加密,不可逆,不能用于需还原的字段;存储过程仅支持落地前单向摘要处理,如密码校验或脱敏,正确做法是每用户独立生成盐值、分列存储哈希与盐、避免明文直哈希或算法弱化。

哈希不是加密,不能还原原始数据;存储过程里只能做落地前的单向摘要处理,比如密码校验或脱敏。
为什么不能用 HASHBYTES 做“加密传输”或“可逆存储”
常见误解是把 HASHBYTES 当成加密函数——它输出固定长度、不可逆,根本没法“解密”。你在存储过程中调用 HASHBYTES('SHA2_512', @pwd),得到的只是摘要值,原始密码丢了就真丢了。这适合存密码,不适合存身份证号、银行卡号这类需要还原的字段。
典型错误包括:
- 用
MD5或SHA1算法——已被证明易碰撞,合规审计通不过 - 对明文直接哈希,没加盐——相同密码生成相同哈希,彩虹表一扫一个准
- 盐值硬编码或全局复用——失去抗批量破解能力
- 把盐和哈希拼成一个字符串存进单列——后续换算法、轮换盐策略时无法拆分
SQL Server 中安全存密码的正确写法
必须分开存盐和哈希值,且盐要随机、每用户独立。关键点是:CRYPT_GEN_RANDOM 生成盐,HASHBYTES 计算带盐哈希,类型显式转换避免隐式截断。
- 建表时预留两列:
PasswordHash VARBINARY(64)(SHA2_512 输出为 64 字节)、PasswordSalt VARBINARY(16) - 插入时用
CONVERT(NVARCHAR(MAX), @password) + CAST(@salt AS NVARCHAR(32))拼接,再传给HASHBYTES,避免 Unicode 截断 - 别用
CONVERT(VARCHAR, HASHBYTES(...), 2)转十六进制字符串——占空间大、比较慢,直接存VARBINARY更高效
示例片段:
DECLARE @Salt VARBINARY(16) = CRYPT_GEN_RANDOM(16);
DECLARE @Password NVARCHAR(100) = N'User@2026';
INSERT INTO Users (Username, PasswordHash, PasswordSalt)
VALUES (
'alice',
HASHBYTES('SHA2_512', @Password + CAST(@Salt AS NVARCHAR(32))),
@Salt
);
WHERE 条件里怎么安全比对哈希值
验证登录时,不能在 WHERE 里现场计算哈希——那会强制全表扫描,索引完全失效。必须先查出对应用户的盐值,再在应用层或存储过程中用该盐重新哈希比对。
- 第一步查盐:
SELECT PasswordSalt FROM Users WHERE Username = @username - 第二步用查到的盐重算哈希,再跟
PasswordHash列比对(用=,不是 LIKE) - 千万别写
WHERE PasswordHash = HASHBYTES('SHA2_512', @input + PasswordSalt)——SQL Server 不支持列参与哈希计算的索引优化,性能灾难 - 如果坚持在存储过程中完成全部逻辑,确保
@input和PasswordSalt类型一致,否则CAST隐式转换可能让哈希结果不一致
真正容易被忽略的是字符集和排序规则:NVARCHAR 字符串拼接盐值时,若数据库默认排序规则不区分大小写或含填充行为,可能导致相同输入产生不同哈希。上线前务必在目标环境实测 ASCII/Unicode 输入下的哈希一致性。











