PHP 中的 sha1() 函数
PHP 中的 sha1() 函数
基本定义
sha1() 是 PHP 内置的哈希函数,用于计算字符串的 SHA-1(Secure Hash Algorithm 1) 散列值。
它会把任意长度的输入,变成一个固定 40 个字符的十六进制字符串。
<?php
echo sha1("hello");
// 输出: aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
?>
函数签名
sha1(string $string, bool $binary = false): string
两个参数的含义:
$string:要计算哈希的原始字符串$binary(可选,默认false):false→ 返回 40 位十六进制字符串(常见用法)true→ 返回 20 字节的原始二进制数据
在 CTF 中为什么重要?
SHA-1 在 CTF-Web 题目中经常出现,主要涉及以下考点:
1. 弱类型比较绕过(==)
这是最经典的考点。PHP 的 == 是松散比较,会做类型转换:
<?php
if (sha1($_GET['a']) == sha1($_GET['b'])) {
echo "flag";
}
?>
绕过方法: 找两个不同的字符串,它们的 SHA-1 值都以 0e 开头,PHP 会把 0e... 当作科学计数法,认为它们都等于 0。
已知的 sha1() 后以 0e 开头的字符串:
aaroZmOk → sha1 = 0e00...
aaK1STfY → sha1 = 0e00...
所以传入 ?a=aaroZmOk&b=aaK1STfY 就能绕过。
2. 数组绕过
<?php
if (sha1($_GET['a']) === sha1($_GET['b'])) {
// 这里用了 === 强比较,0e 那招不行了
echo "flag";
}
?>
绕过方法: 传入数组。sha1() 接收到数组时会报 Warning 并返回 NULL,两个 NULL === NULL 为 true:
?a[]=1&b[]=2
两边都返回 NULL,NULL === NULL 成立,绕过成功。
3. SHA-1 碰撞(真实碰撞)
2017 年 Google 公布了第一个 SHA-1 碰撞(SHAttered),两个不同的 PDF 文件拥有相同的 SHA-1 值。在某些题目中如果过滤了数组,可能需要用到真实碰撞数据。
SHA-1 vs MD5
| 特性 | md5() |
sha1() |
|---|---|---|
| 输出长度 | 32 位十六进制 | 40 位十六进制 |
| 安全性 | 更弱,碰撞更容易 | 稍强,但也已不安全 |
| CTF 绕过思路 | 基本一样 | 基本一样 |
它们在 CTF 中的绕过思路(弱类型、数组绕过)几乎完全相同。
学习资料
- PHP 官方文档:https://www.php.net/manual/zh/function.sha1.php
- PHP 类型比较表(理解
==和===的区别):https://www.php.net/manual/zh/types.comparisons.php - CTF Wiki - PHP 弱类型:https://ctf-wiki.org/web/php/php/
- SHAttered(SHA-1 碰撞):https://shattered.io/
如果你之前学过 md5() 的绕过,sha1() 的套路完全一样,可以类比记忆。
相同的值,SHA-1 结果一定相同
是的,同一个字符串经过 sha1() 计算,永远得到同一个结果。
这是哈希函数的基本性质——确定性(Deterministic)。
<?php
echo sha1("hello"); // aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
echo sha1("hello"); // aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
// 无论执行多少次,结果永远一样
?>
哈希函数的核心性质
你可以把哈希函数理解为一个单向的"指纹提取器":
| 性质 | 含义 |
|---|---|
| 确定性 | 相同输入 → 永远相同输出 |
| 单向性 | 知道哈希值,无法反推出原始输入 |
| 雪崩效应 | 输入改一点点,输出就完全不同 |
| 抗碰撞性 | 很难找到两个不同输入产生相同输出(但理论上一定存在) |
举个雪崩效应的例子:
<?php
echo sha1("hello");
// aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
echo sha1("hellp"); // 只改了一个字母
// 完全不同的结果
?>
所以 CTF 中比较 SHA-1 的本质是什么?
当题目写:
<?php
if (sha1($a) === sha1($b)) {
echo "flag";
}
?>
它的意思是:要求你找两个不同的值 $a 和 $b,使得它们的 SHA-1 "指纹"恰好相同。
正常情况下这几乎不可能,所以我们才需要用到上一条讲的那些绕过技巧(数组绕过、弱类型、真实碰撞等),本质上是在利用 PHP 语言层面的漏洞,而不是真的去破解 SHA-1 算法本身。
具体举例说明
假设题目代码是这样的:
<?php
$a = $_GET['a'];
$b = $_GET['b'];
if ($a != $b && sha1($a) === sha1($b)) {
echo $flag;
}
?>
题目要求:$a 和 $b 的值不同,但它们的 SHA-1 必须相同(强比较 ===)。
绕过方法:传数组
我们构造这样的 URL:
?a[]=1&b[]=2
然后一步步看 PHP 内部发生了什么:
第一步:PHP 接收参数
$a = array(1) // 因为 a[] 语法,PHP 把它解析成数组
$b = array(2) // 同理
第二步:检查 $a != $b
array(1) != array(2) → true ✅ 通过
两个数组内容不同,所以不相等,条件满足。
第三步:计算 sha1($a) 和 sha1($b)
sha1(array(1)) → PHP Warning: sha1() expects parameter 1 to be string, array given
→ 返回 NULL
sha1(array(2)) → 同样报 Warning
→ 返回 NULL
sha1() 期望接收字符串,收到数组就"懵了",只能返回 NULL。
第四步:比较 sha1($a) === sha1($b)
NULL === NULL → true ✅ 通过
两个条件都满足,输出 flag!
如果是弱比较 == 呢?
<?php
if ($a != $b && sha1($a) == sha1($b)) {
echo $flag;
}
?>
这时候可以用 0e 绕过:
?a=aaroZmOk&b=aaK1STfY
过程如下:
sha1("aaroZmOk") = 0e1948...... (以 0e 开头)
sha1("aaK1STfY") = 0e0455...... (以 0e 开头)
PHP 用 == 比较时,看到 0e 开头的字符串,会当作科学计数法:
0e1948... → 0 × 10^1948 → 0
0e0455... → 0 × 10^455 → 0
0 == 0 → true ✅
两个完全不同的字符串,SHA-1 值也不同,但 PHP 认为它们"相等"。
一句话总结
| 比较方式 | 绕过方法 | 原理 |
|---|---|---|
==(弱比较) |
传 0e 开头的值 |
PHP 把 0e... 当成科学计数法,都等于 0 |
===(强比较) |
传数组 a[]=1&b[]=2 |
sha1() 收到数组返回 NULL,NULL === NULL 为 true |
这就是 CTF 中 sha1() 最常见的两种绕过场景,和 md5() 的思路完全一样。

浙公网安备 33010602011771号