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 === NULLtrue

?a[]=1&b[]=2

两边都返回 NULLNULL === NULL 成立,绕过成功。

3. SHA-1 碰撞(真实碰撞)

2017 年 Google 公布了第一个 SHA-1 碰撞(SHAttered),两个不同的 PDF 文件拥有相同的 SHA-1 值。在某些题目中如果过滤了数组,可能需要用到真实碰撞数据。


SHA-1 vs MD5

特性 md5() sha1()
输出长度 32 位十六进制 40 位十六进制
安全性 更弱,碰撞更容易 稍强,但也已不安全
CTF 绕过思路 基本一样 基本一样

它们在 CTF 中的绕过思路(弱类型、数组绕过)几乎完全相同。


学习资料

如果你之前学过 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() 的思路完全一样。

posted @ 2026-03-24 20:43  yorkchain  阅读(89)  评论(2)    收藏  举报