尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
PHP crypt()函数的用法讲解
前言crypt()是 PHP 里历史最悠久的字符串函数之一它直接来自 C 标准库的crypt(3)被设计出来只为一件事把用户口令变成一段不可逆的散列值写进类似 Unix 口令文件那样的存储里。围绕它有两个非常普遍的误解。第一个是把crypt()当成「加密函数」以为加密之后还能解密回来——它不是加密而是单向散列one-way hashing没有密钥也没有逆运算salt 只影响「同一个密码得到不同的散列」以及「一次散列要花多少计算量」。第二个误解是把它当成今天存密码的推荐做法——这也是错的PHP 5.5 起提供的password_hash()/password_verify()已经完全覆盖了crypt()的用途而且替你把 salt 生成、算法前缀、失败检测都处理好了。本文要讲清三件事crypt()的签名以及 salt 前缀如何决定算法、PHP 8.0 对它的行为改动、以及在必须面对老代码和老数据时怎么安全地过渡到password_hash()。一、签名与 salt 前缀决定算法crypt(string $string, string $salt): string注意第三个事实crypt()没有「算法」参数。用哪个算法完全由salt字符串的开头几个字符决定。这是 Unix 口令文件格式留下的设计也是所有初学困惑的根源。salt 形式对应常量说明两个普通字符如rlCRYPT_STD_DES传统 DES只取密码前 8 字节输出 13 字符强度早已不足_J9..开头的 9 字符CRYPT_EXT_DES扩展 DES$1$开头CRYPT_MD5MD5 cryptsalt 最多 8 字符输出 34 字符$2y$开头CRYPT_BLOWFISHbcrypt本节重点$5$开头CRYPT_SHA256SHA-256 cryptsalt 最多 16 字符$6$开头CRYPT_SHA512SHA-512 cryptsalt 最多 16 字符这些常量不是「配置项」而是探测结果它们只有在当前系统的 libcrypt 支持该算法时才被定义。所以判断某个算法能不能用必须写成?php // 适用于 PHP 7.0// 常量只在系统支持时才存在直接用会报 undefined constantif (defined(CRYPT_BLOWFISH) CRYPT_BLOWFISH) {echo bcrypt 可用\n;}二、bcrypt 的 salt 长什么样bcrypt 的 salt 不是「随便一串字符」它有严格格式拼错了crypt()会返回一个失败值而不是抛出异常——这正是最容易造成「密码存进去了但谁也登不上」的地方。格式是$2y$ 两位 cost $ 22 个来自./A-Za-z0-9的字符。总共 29 个字符输出固定 60 个字符。?php // 适用于 PHP 8.0// 生成一个符合 bcrypt 规范的 salt$cost 10;$salt sprintf($2y$%02d$, $cost). substr(strtr(base64_encode(random_bytes(16)), , .), 0, 22);$hash crypt(correct horse battery staple, $salt);// 校验格式bcrypt 的输出一定是 60 个字符且以 $2y$ 开头var_dump(strlen($hash)); // int(60)var_dump(str_starts_with($hash, $2y$)); // bool(true)// 校验密码把「已存库的完整散列」当成 salt 再算一次$ok hash_equals($hash, crypt(correct horse battery staple, $hash));var_dump($ok); // bool(true)var_dump(hash_equals($hash, crypt(wrong, $hash))); // bool(false)两个细节值得说明。第一random_bytes()在 PHP 7.0 才可用它拿不到足够熵时会抛Exception这比mt_rand()拼 salt 安全得多mt_rand()是伪随机数绝不可用于安全场景。第二比较必须用hash_equals()而不是或因为字符串比较会提前返回理论上可被时序攻击timing attack逐字节试探。三、PHP 8.0 的行为变化与失败返回值crypt()的失败信号非常隐蔽。手册的说明是失败时返回一个短于 13 个字符、且保证与该 salt 不同的字符串。也就是说它不会返回false也不会抛异常你拿到的仍然是一个「看起来像字符串」的东西。?php // 适用于 PHP 8.0// 故意给一个不合法的 saltbcrypt 前缀但 salt 太短$bad crypt(password, $2y$10$tooshort);var_dump($bad); // 不是 60 字符的合法散列var_dump(strlen($bad) 13); // 失败判据之一版本上要特别注意PHP 5.6 起省略salt参数会触发E_NOTICE安全警告是 notice不是E_DEPRECATED——手册的 changelog 写的是「Raise E_NOTICE security warning if salt is omitted」。PHP 8.0 起salt参数不再可以省略必须显式传入。此时它已经是签名里的必填参数漏传不再是警告而是直接抛ArgumentCountErrorcrypt() expects exactly 2 parameters, 1 given。老代码里crypt($password)这种单参数写法在 PHP 8 上会中断执行不能照抄。mcrypt_*系列是另一回事它在 PHP 7.2 被废弃、7.2 之后需要 PECL 安装已不适合新项目。但它和crypt()无关——crypt()一直属于标准库。四、为什么新代码应该直接写 password_hash()把上面几节的内容合起来看crypt()要求调用者自己负责 salt 生成、格式拼接、失败检测、常量时间比较——任何一步写错都可能是「静默失效」的。password_hash()把这一整套封装掉了?php // 适用于 PHP 7.4PASSWORD_DEFAULT 推荐用 PHP 8.0 的默认 bcrypt$hash password_hash($password, PASSWORD_DEFAULT);// 校验password_verify() 会自动识别 $2y$ / $argon2i$ 等前缀if (password_verify($password, $hash)) {echo 密码正确\n;}// 参数升级例如以后把 cost 调高后用 needs_rehash 检测if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {$newHash password_hash($password, PASSWORD_DEFAULT);// 此处应把 $newHash 写回数据库}// 想用 Argon2id 需要 PHP 7.3 且编译时带 Argon2 支持if (defined(PASSWORD_ARGON2ID)) {$argon password_hash($password, PASSWORD_ARGON2ID);}还有一个实用的过渡点password_verify()可以校验crypt()生成的 bcrypt 散列因为两者用的是同一套格式。所以老系统迁移时不必逼所有用户立刻改密码——先用password_verify()接管校验登录成功时判断password_needs_rehash()顺手把散列升级成password_hash()的输出即可。另外要澄清一个容易误解的点bcrypt 只看密码的前 72 字节超出部分被直接忽略。这是算法本身的限制password_hash()用 bcrypt 时也受同样约束如果确实需要超长口令得先用hash(sha256, $pwd, true)之类做一次预处理再交给 bcrypt但这样会引入自己的兼容性问题普通业务不必这么做。常见坑点❌ 用密码自己当 saltcrypt($password, $password)想靠「算出来一样就说明对」来验证。这样每次 salt 都不同永远对不上。✅ 校验时把存库的完整散列当 salthash_equals($stored, crypt($password, $stored))或者直接用password_verify($password, $stored)。❌ 用比较散列if ($stored crypt($input, $stored))。字符串比较不是常量时间。✅hash_equals($stored, $computed)。❌ 省略 salt$h crypt($password);。PHP 5.67.x 只是发E_NOTICE容易被错误抑制掩盖PHP 8.0 起salt变成必填参数直接抛ArgumentCountError脚本中断。✅password_hash($password, PASSWORD_DEFAULT)完全不用自己拼 salt。❌ salt 里混入 base64 的//~等非 bcrypt 字母表字符crypt()静默返回失败值代码却当成功写库。✅ 生成后校验输出必须以$2y$开头且长度为 60否则当成错误处理。❌ 把crypt()当可逆加密到处找「解密」办法。✅ 它是单向散列。真要可逆用sodium_crypto_secretbox()PHP 7.2 内置或openssl_encrypt()并且每次都用随机 nonce / IV。❌ 用$2a$或$2x$前缀以为和$2y$等价。$2x$/$2y$是针对早期实现中符号扩展缺陷的修正变体混用会导致同一密码在不同实现上算出不同结果。✅ 新代码统一使用$2y$或干脆交给password_hash()。❌ 用md5($password)/sha1($password)存密码或者用md5($salt . $password)自己「加盐」。✅ 快哈希MD5/SHA-1/SHA-256在这类场景下就是错误工具它们是设计成「快」的。存密码只能用password_hash()/password_verify()。❌ 认为 13 字符的 DES crypt 输出仍然够安全拿它存新密码。✅CRYPT_STD_DES只使用密码前 8 个字符且密钥空间极小现代机器可暴力枚举只能用于识别老数据不能用于新数据。总结关注点老写法现行推荐生成散列crypt($pwd, $salt)自己拼 saltpassword_hash($pwd, PASSWORD_DEFAULT)校验密码crypt($pwd, $hash) $hashpassword_verify($pwd, $hash)常量时间比较hash_equals()参数升级手工重建 saltpassword_needs_rehash() 重新password_hash()算法DES / MD5 cryptbcrypt默认或 Argon2idPHP 7.3 且编译支持可逆加密误用crypt()sodium_crypto_secretbox()/openssl_encrypt() 随机 IVcrypt()不是坏函数但它把太多安全细节暴露给了调用者任何一个细节写错都是静默失效。新代码直接使用password_hash()/password_verify()只有在维护老系统的存量 bcrypt 散列时才需要理解 salt 前缀这套机制而且理解它的最好用途就是——把数据平滑迁移出去。
RELATED

相关推荐

Android文件存在却打不开?Ext4底层状态诊断指南

Android文件存在却打不开?Ext4底层状态诊断指南

1. 为什么Android设备上“文件明明存在却打不开”不是App bug,而是Ext4底层在报警你有没有遇到过这样的场景:在鸿蒙或安卓手机上,工行App卡在加载页不动,点开某个PDF预览直接报错“文件不存在”,但用文件管理器进去一看…

📅 2026/10/8 10:31:48
WorkBuddy装完先补哪5个技能?find-skills、humanizer-zh等实战指南

WorkBuddy装完先补哪5个技能?find-skills、humanizer-zh等实战指南

1. 为什么装完 WorkBuddy 的第一件事不是开干,而是补技能 刚装好 WorkBuddy 的那几天,我猜你跟我当初一样,满脑子都是"赶紧跑个任务试试"。结果呢?要么是它答非所问,要么是输出一股浓浓的机器味,…

📅 2026/10/8 10:31:48
DeepSeek Harness桌面版+Obsidian:本地知识库搭建与RAG检索实战

DeepSeek Harness桌面版+Obsidian:本地知识库搭建与RAG检索实战

1. 为什么我最终把知识库从"网页版"搬回了桌面 我用了大概两年多的在线知识库工具,从最早的纯笔记软件到后来的各种云端协作平台,中间换过至少四五套方案。每次换工具的理由都差不多:要么是同步太慢,要么是搜索不准&…

📅 2026/10/8 10:31:48
MORE NEWS

更多资讯

📰

Agent技能体系构建:从工具函数到可复用Skill包的实战指南

最近好几个朋友跑来问我同一个问题:为什么用同一个基础模型,别人家的Agent像个靠谱的执行助理,自家的Agent像个只会接话的聊天机器人?说实话,模型能力的差距真有那么大吗?不一定。大多数差距出在“能力的组…

📰

Agent技能设计实战:从技能定义到调度编排的稳定性指南

Agent能力的天花板,很大程度不在模型参数,而在技能设计。最近"agent-skills"这个词在技术社区里高频出现,我前后也折腾了小半年,踩了不少坑,算是把这个概念从理论撸到了落地。这篇就把我对agent-skills的理解…

📰

Agent-Reach:本地AI能力统一调度的CLI中枢

1. 项目概述:Agent-Reach 是什么,它解决的到底是什么问题? Agent-Reach 不是一个玩具级命令行工具,也不是某个大厂刚发布的营销概念。我第一次在 Reddit 的 r/LocalLLM 板块看到有人贴出 agent-reach --model deepseek-chat --ta…

📰

M Plan全模态额度池实测:H3视频生成与Claude Code/Cursor接入指南

MiniMax这次的动作确实够狠:Token Plan说退就退,M Plan直接把全模态额度打进一个池子,H3视频模型解禁,从5秒短片一路做到分钟级长视频。更关键的是,M Plan的密钥能直接用在Claude Code和Cursor这类开发工具里&#xff…

📰

大模型上下文管理:context-mode的设计、压缩与踩坑实践

1. context-mode 解决的痛点:为什么上下文塞得越满,模型越容易翻车 最近在给团队的自研 Agent 工具加一个上下文管理模块,名字就叫 context-mode。起因特别朴素——我们接入了三家不同厂商的大模型接口之后,发现同一个任务&#x…

📰

JavaWeb校园菜鸟驿站管理系统:从建表到部署的毕设实战指南

简介:一套基于Javaweb开发的校园菜鸟驿站管理系统,面向高校毕业设计学生及JavaEE入门学习者,针对校园快递代取、站点管理、订单跟踪等常见场景提供完整实现方案。项目评审分达95分以上,源码已在本地编译验证可运行,难度…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬