HASHBYTES返回varbinary类型哈希值,需用LOWER(SUBSTRING(CONVERT(VARCHAR(34), HASHBYTES('MD5',str),2),3,32))转为标准32位小写MD5字符串,否则含0x前缀或大小写错误导致校验失败。

HASHBYTES 本身不直接输出标准 MD5 字符串,它返回的是 varbinary 类型的原始哈希值(如 0xE10ADC39...),必须配合类型转换和字符串处理才能得到 32 位小写十六进制结果。直接 CONVERT(VARCHAR, HASHBYTES(...), 2) 会保留 0x 前缀且长度超限,不是真正可用的 MD5 值。
为什么 HASHBYTES('MD5', str) 的结果不能直接用?
HASHBYTES 返回 varbinary(8000),本质是二进制数据。常见错误是误以为加个 CONVERT 就完事——比如写成 CONVERT(VARCHAR(32), HASHBYTES('MD5', '123'), 2),结果仍是 '0xE10ADC3949BA59ABBE56E057F20F883E'(34 字符,含 0x,且大小写不统一)。这不是标准 MD5,校验或对接外部系统时必然失败。
-
HASHBYTES输出永远带0x前缀,且样式参数1或2只控制大小写,不控制前缀是否存在 -
sys.fn_varbintohexstr在 Azure SQL 中不可用;sys.fn_sqlvarbasetostr更通用,返回小写0x前缀字符串,推荐优先使用 - 直接截取
RIGHT(..., 32)看似简洁,但依赖前缀固定为0x两字符,不如显式SUBSTRING(..., 3, 32)可控
标准 32 位小写 MD5 的正确写法
唯一可靠、跨 SQL Server 2008+ 和 Azure SQL 的写法是:
SELECT LOWER(SUBSTRING(CONVERT(VARCHAR(34), HASHBYTES('MD5', '123456'), 2), 3, 32))
拆解说明:
-
CONVERT(VARCHAR(34), ..., 2):确保足够容纳0x+ 32 字符,避免截断 -
SUBSTRING(..., 3, 32):从第 3 位开始取 32 位,精准跳过0x -
LOWER(...):强制小写,与主流工具(如 OpenSSL、Pythonhashlib.md5)输出一致
等效但更简短的写法(依赖 sys.fn_sqlvarbasetostr):
SELECT LOWER(SUBSTRING(sys.fn_sqlvarbasetostr(HASHBYTES('MD5', '123456')), 3, 32))
对整列做 MD5 加密时的坑
更新表中一列(如密码)为 MD5 值时,容易忽略数据类型隐式转换导致哈希不一致:
- 输入是
nvarchar(如字段定义为NVARCHAR(50))和varchar(如常量'123')会产生不同哈希值,因为 Unicode 字节序列不同 - 务必统一输入类型:用
CONVERT(VARCHAR, column_name)显式转成非 Unicode,或确保源列本身就是VARCHAR - 不要用
UPDATE ... SET md5_col = CONVERT(VARCHAR(32), HASHBYTES('MD5', col), 2)—— 这会存入带0x的 34 字符,超出VARCHAR(32)定义,被截断或报错 - 目标列应定义为
VARCHAR(32),更新语句用完整转换逻辑:LOWER(SUBSTRING(CONVERT(VARCHAR(34), HASHBYTES('MD5', CONVERT(VARCHAR, col)), 2), 3, 32))
MD5 已不安全,但若必须用,请注意兼容性边界
SQL Server 支持 HASHBYTES 的所有版本(2005+),但函数行为细节有差异:
-
CONVERT(..., 2)在 SQL Server 2008+ 才稳定支持;2005 用户只能用sys.fn_varbintohexstr或sys.fn_sqlvarbasetostr -
sys.fn_varbintohexstr返回大写,且在 Azure SQL 中已移除,不能用于云迁移场景 -
sys.fn_sqlvarbasetostr是目前最稳妥的选择:SQL Server 2012+ 和 Azure SQL 均可用,返回小写,前缀一致 - 真正该警惕的不是写法,而是用途:MD5 碰撞已被实证,用户密码等敏感字段请改用
HASHBYTES('SHA2_256', ...)并加盐
最后一点容易被忽略:哪怕写法完全正确,如果源数据里混着空格、不可见字符(如 CHAR(0))、或隐式 Unicode 转换,哈希结果也会和预期不符——建议先用 DATALENGTH() 和 CAST(... AS VARBINARY) 检查原始字节序列。

















