← PHP crc32()函数:循环冗余校验与数据完整性验证 PHP echo()函数:字符串输出机制与工程实践 →

PHP crypt()函数:单向哈希算法与盐值机制详解

著
原创 2026-10-10 PHP 已有人查阅

一、函数定位与语法

crypt()是PHP字符串函数库中用于单向哈希的函数。它接受一个明文字符串和一个可选的盐值(salt),返回哈希后的字符串。所谓单向,是指无法从哈希结果反推出原始输入——这是密码存储的基本前提。

语法结构:

string crypt ( string $str [, string $salt ] )
参数 说明 是否必需
$str 待哈希的字符串 必需
$salt 盐值字符串,决定使用哪种算法 可选

盐值在crypt()中不只是"加点随机数据"那么简单。盐值的格式决定了crypt()使用哪种哈希算法。这是理解该函数的核心,也是它与其他哈希函数(如md5、sha1)较大的区别。

crypt()的行为依赖底层操作系统的C库实现。不同Unix系统支持的算法种类可能不同,这也是为什么PHP提供了CRYPT_*系列常量用于检测当前环境支持哪些算法。

二、支持的哈希算法与常量

PHP定义了一组常量,值为1表示当前系统支持对应算法:

常量 算法 盐值格式特征
CRYPT_STD_DES 标准DES 2个字符
CRYPT_EXT_DES 扩展DES 9个字符,以_开头
CRYPT_MD5 MD5 以$1$开头
CRYPT_BLOWFISH Blowfish 以$2a$、$2y$等开头
CRYPT_SHA_256 SHA-256 以$5$开头
CRYPT_SHA_512 SHA-512 以$6$开头

每种算法对盐值长度和格式有不同要求。如果盐值格式不符合任何已知算法,crypt()会退回到标准DES,只取盐值前2个字符。这个"降级"行为在排查问题时经常被忽略。

三、代码号学习编程:各算法调用示例

标准DES(2字符盐值):

<?php
if (CRYPT_STD_DES == 1) {
    echo "Standard DES: " . crypt('codehao', 'jt') . "\n";
} else {
    echo "Standard DES not supported.\n";
}

标准DES的盐值只有2个字符,哈希空间有限。现在密码存储不应使用它,这里仅作原理演示。

MD5算法(11前缀):

<?php
if (CRYPT_MD5 == 1) {
    echo "MD5: " . crypt('codehao', '$1$codehaolearn$') . "\n";
}

盐值以$1$开头,PHP会按MD5-crypt格式解析。输出的哈希串同样以$1$开头,包含盐值和哈希结果。

Blowfish算法(2a2a前缀):

<?php
if (CRYPT_BLOWFISH == 1) {
    $salt = '$2a$09$anexamplestringforsalt$';
    echo "Blowfish: " . crypt('codehao', $salt) . "\n";
}

$2a$后面的09是cost参数,表示迭代次数的对数(2^9=512轮)。cost越高,计算越慢,破解成本越大。Blowfish的盐值需要22个base64字符,格式要求较严格。

SHA-256算法(55前缀):

<?php
if (CRYPT_SHA_256 == 1) {
    $salt = '$5$rounds=5000$anexamplestringforsalt$';
    echo "SHA-256: " . crypt('codehao', $salt) . "\n";
}

rounds=5000指定迭代轮数,默认值就是5000。可以调高到更大值来增加计算成本。

SHA-512算法(66前缀):

<?php
if (CRYPT_SHA_512 == 1) {
    $salt = '$6$rounds=5000$anexamplestringforsalt$';
    echo "SHA-512: " . crypt('codehao', $salt) . "\n";
}

SHA-512与SHA-256的调用方式接近,只是前缀和输出长度不同。

四、项目反思:crypt()的历史地位与现实选择

我在2026年接手一个2010年前后开发的用户系统,密码字段用的是crypt()配合MD5格式存储。这段维护经历让我对crypt()的定位有了具体认识。

踩坑经历一:盐值格式决定一切

系统日志中偶尔出现"密码验证失败"的投诉,但用户坚称密码没错。排查后发现,部分老用户的密码哈希是用标准DES生成的,而新代码统一用MD5格式的盐值调用crypt()。由于crypt()只根据盐值判断算法,验证老密码时传入MD5格式盐值,计算出的哈希自然对不上。解决方案是先读取数据库中存储的哈希前缀($1$、$5$等),用相同的格式构造盐值再验证。

踩坑经历二:跨平台兼容性

crypt()依赖操作系统的crypt实现。同一段代码在Linux和macOS上支持的算法可能不同。我曾遇到在开发机(macOS)上Blowfish正常,部署到某台老Linux服务器后CRYPT_BLOWFISH返回0,函数自动降级到DES,导致新注册用户的密码哈希强度骤降。部署前检查CRYPT_*常量的值,应该成为上线流程的一部分。

踩坑经历三:为什么现在不直接用它

PHP5.5引入了password_hash(),它封装了crypt()的底层能力,但做了几件关键的事:自动生成安全盐值、自动选择当前推荐的算法、提供统一的验证接口。相比之下,手动调用crypt()需要开发者自己处理盐值生成、算法选择、格式解析,出错空间大。

我后续把所有密码存储逻辑迁移到password_hash()+password_verify(),代码量减少,安全性反而提升。crypt()在这个场景中已经不需要直接出现。

个人建议:新项目一律使用password_hash()。crypt()的价值在于理解密码哈希的底层机制,以及在维护老系统时能够正确解析和迁移历史数据。不要在新代码中手写crypt()调用。

五、本节课程知识要点

  • crypt()是单向哈希函数,盐值格式决定使用的算法。

  • 支持DES、扩展DES、MD5、Blowfish、SHA-256、SHA-512。

  • 盐值格式不符合已知算法时会降级到标准DES。

  • crypt()行为依赖操作系统实现,跨平台部署需检查CRYPT_*常量。

  • 新项目应使用password_hash()和password_verify()。

  • 维护老系统时,需按存储的哈希前缀构造盐值进行验证。

六、crypt()与password_hash()对比

维度 crypt() password_hash()
盐值生成 手动 自动
算法选择 靠盐值格式 自动选择推荐算法
验证方式 手动比对哈希串 password_verify()
错误处理 无 有返回值语义
算法升级 需手动迁移 password_needs_rehash()
推荐场景 遗留系统维护 新项目

password_hash()目前默认使用bcrypt(即Blowfish的现在形式),并支持Argon2。它把盐值生成、算法标识、哈希结果编码进一个字符串,验证时自动解析,开发者不需要接触底层细节。

七、一个容易忽略的细节

crypt()返回的哈希串中已经包含了盐值。以MD5格式为例,输出$1$codehaolearn$/QyBQ/V7dJjcGaOq83EhL0中,$1$是算法标识,codehaolearn是盐值,后面的部分是哈希结果。验证密码时不需要单独存储盐值——直接从哈希串中提取即可。这也是为什么数据库只需存一个字段,而不是"哈希+盐"两个字段。

但要注意,Blowfish和SHA-2系列的盐值格式更复杂,手动解析容易出错。如果必须处理老数据,建议用password_verify()尝试验证——它内部能识别多种crypt格式。

← PHP crc32()函数:循环冗余校验与数据完整性验证 PHP echo()函数:字符串输出机制与工程实践 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号