用PHP实现自己的sha-256哈希算法!
发布时间:2022-08-10 11:20:40 所属栏目:PHP教程 来源:互联网
导读:哈希 又称作 散列,它接收任何一组任意长度的输入信息,通过 哈希 算法变换成固定长度的数据指纹,该指纹就是 哈希值。总体而言,哈希 可理解为一种消息摘要。 在 PHP 中有这个函数 hash(),可以计算字符串的哈希值,出于好奇我 Google 了一下哈希计算的具体
|
哈希 又称作 “散列”,它接收任何一组任意长度的输入信息,通过 哈希 算法变换成固定长度的数据指纹,该指纹就是 哈希值。总体而言,哈希 可理解为一种消息摘要。 在 PHP 中有这个函数 hash(),可以计算字符串的哈希值,出于好奇我 Google 了一下哈希计算的具体步骤,并使用 PHP 编写了一套计算 sha-256 哈希值的代码。当然除了 sha-256 以外还有一些别的哈希算法,只是目前 sha-256 用的多一些。下面是目前 美国国家标准与技术研究院 发布哈希算法: 哈希算法 输入大小(bits) 分块大小(bits) 行大小(bits) 生成二进制长度(bits) 生成十六进制长度(chars) sha1 < 2^64 512 32 160 40 sha-224 < 2^64 512 32 224 56 sha-256 < 2^64 512 32 256 64 sha-384 < 2^128 1024 64 384 96 sha-512 < 2^128 1024 64 512 128 sha-512/224 < 2^128 1024 64 224 56 sha-512/256 < 2^128 1024 64 256 64 在编写过程中我主要参考了以下文档和站点: Lane Wagner - How SHA-256 Works Step-By-Step:https://blog.boot.dev/cryptography/how-sha-2-works-step-by-step-sha-256/ Secure Hash Standard (SHS) - FIPS 180-4(官方文档):https://csrc.nist.gov/publications/detail/fips/180/4/final ASCII Table:https://www.asciitable.com/ 本文内容较多,主要分为下面这几个部分,读者阅读时可以先跳过 准备二:助手方法 直接进入 步骤 部分,在阅读 步骤 部分需要用到指定方法时再回过头来查阅 准备二:助手方法 中的函数。 准备一:代码主体 准备二:助手方法(阅读时可先跳过) 步骤一:字符串转二进制 步骤二:追加数字 1 步骤三:填充至 512 的倍数 步骤四:追加原始长度信息 步骤五:切分区块并填充至 2048 位 步骤六:区块数据修改 步骤七:压缩 准备一:代码主体 我们创建一个类 Algorithm 来存放我们计算哈希所需要用到的方法和属性。这个类中只有一个 public 的方法 sha256(),此方法传入一个字符串参数,输出此字符串的 sha-256 哈希值。要完成我们的哈希计算,总共需要经过七个步骤,我们先把这七个步骤的调用写到 sha256() 的函数体中。 <?php declare(strict_types=1); class Algorithm { public function sha256(string $str): string { // 步骤一:将字符串转化为二进制 $this->step1_convert_str_to_bits($str); // 步骤二:在最后面追加一个1 $this->step2_append_1(); // 步骤三:在数据末尾添加0,确保二进制的个数是512的倍数,最后预留64位用于存储原始长度信息 $this->step3_extend_to_multiple_of_512(); // 步骤四:把原始字符串位长度,填充到预留在最后的64位(8个字节的长整型)中 $this->step4_append_origin_length(); // 步骤五:每一个512位切分区块,在区块末尾填充0,使得每个区块位数为2048位,需要增加48行(32位一行) $this->step5_split_blocks_and_append_48_lines(); // 步骤六:针对每一个2048位区块处理:以32位为一行,总共有64行,修改【16-63】行的数据 $this->step6_modify_blocks_appended_48_lines(); // 步骤七:压缩数据,生成最终的哈希值 return $this->step7_compress_to_final_hash(); } } 除了 sha256() 这个函数外, 我们要需要几个成员属性来保存计算过程中产生的数据。 $originLen 属性用于记录字符串被转化为二进制之后的原始长度,这个长度值后续会追加到数据中去。 /** @var int 原始数据的二进制长度 */ private int $originLen = 0; $bits 属性用于储存字符串转化后得到的二进制数据。 /** @var array 存储二进制数组 */ private array $bits; $blocks 存放分块后的二进制数据。 /** @var array 二进制区块 */ private array $blocks; H 哈希计所需的常量,hash-256 的 8 个哈希常量是质数 2、3、5、7、11、13、17、19 各自平方根取二进制小数部分前 32 位所得。 /** @var array 质数平方根常量 */ private const H = [ 0x6a09e667, // 质数2的平方根取二进制小数部分前32位 0xbb67ae85, // 质数3的平方根取二进制小数部分前32位 0x3c6ef372, // 质数5的平方根取二进制小数部分前32位 0xa54ff53a, // 质数7的平方根取二进制小数部分前32位 0x510e527f, // 质数11的平方根取二进制小数部分前32位 0x9b05688c, // 质数13的平方根取二进制小数部分前32位 0x1f83d9ab, // 质数17的平方根取二进制小数部分前32位 0x5be0cd19, // 质数19的平方根取二进制小数部分前32位 ]; 对于上面这几个常量,感兴趣的同学也可以自己计算得到,我这里只提供一个简单的计算示例,以质数 2 为例,我们先通过计算器得到它的平方根:1.4142135623730950488016887242097 然后只取小数部分:0.4142135623730950488016887242097,接着将这个十进制的小数转为二进制,转为流程如下: 小数转二进制 0. 0.4142135623730950488016887242097 x 2 => 0 0.8284271247461900976033774484194 x 2 => 1 0.6568542494923801952067548968388 x 2 => 1 0.3137084989847603904135097936776 x 2 => 0 0.6274169979695207808270195873552 x 2 => 1 0.2548339959390415616540391747104 x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 1 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 1 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 1 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 1 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 1 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 1 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 . . . 上面计算得到的小数部分二进制,取前 32 位:01101010 00001001 11100110 01100111,转为十六进制表示:0x6a09e667,其他几个质数的计算也是类似。当然由于是常量,值是固定不变的,所以我们只要知道其计算原理即可。 和上面的平方根常量类似,hash-256 的另外 64 个常量是质数 2、3、5、…、311 各自立方根取二进制小数部分前 32 位。 (编辑:揭阳站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |



