加入收藏 | 设为首页 | 会员中心 | 我要投稿 我爱故事小小网_铜陵站长网 (http://www.0562zz.com/)- 视频终端、云渲染、应用安全、数据安全、安全管理!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

用PHP达成自己的sha-256哈希算法!

发布时间:2022-07-09 12:43:57 所属栏目: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 位。
 
 
 
 
 
 
 
 
 
 
 
 
  /** @var array 质数立方根常量 */
 
  private const K = [
 
      0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
 
      0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
 
      0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
 
      0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
 
      0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
 
      0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
 
      0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
 
      0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
 
  ];
 
  准备二:助手函数
  你可以直接跳过此部分内容,从下面的 步骤一 开始着手去计算哈希值,当需要使用到某一个助手函数的时候再来这里查找即可。
 
  在计算哈希的过程中,我们是把二进制数据存储到数组中的,数组中的每一个元素对应了二进制的一个比特位,所以如果要对这些二进制数组进行 与 非 异或 相加 等操作,我们就需要实现自己的操作函数。
 
  十进制整数转化为二进制数组。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  /**
 
   * 十进制整数转化为二进制数组
 
   * @param int $num 十进制整数
 
   * @param int $fillTo 填充到多少位,不够的用0来补齐
 
   */
 
  public function int2bits(int $num, int $fillTo = 0): array
 
  {
 
      $bits = str_split(decbin($num));
 
      array_walk($bits, function (&$val) {
 
          $val = intval($val);
 
      });
 
      for ($len = count($bits); $len < $fillTo; $len++) {
 
          array_unshift($bits, 0);
 
      }
 
      return $bits;
 
  }
 
  二进制数组向右移动指定位数。
 
 
 
 
 
 
 
 
 
 
 
 
  /**
 
   * 二进制数组向右移动
 
   * @param array $bits 二进制数组
 
   */
 
  public function rightShift(array $bits, int $move): array
 
  {
 
      $len = count($bits);
 
      $move = $move % $len;
 
      if ($move <= 0) return $bits;
 
      return array_merge(array_fill(0, $move, 0), array_slice($bits, 0, $len-$move));
 
  }
 
  二进制数组向右旋转,与右移类似,不过移出去的数要插回到头部。
 
 
 
 
 
 
 
 
 
 
 
 
  /**
 
   * 二进制数组向右旋转
 
   * @param array $bits 二进制数组
 
   */
 
  public function rightRotate(array $bits, int $move): array
 
  {
 
      $len = count($bits);
 
      $move = $move % $len;
 
      if ($move <= 0) return $bits;
 
      return array_merge(array_slice($bits, $len-$move, $move), array_slice($bits, 0, $len-$move));
 
  }
 
  二进制数组求 非。
 
 
 
 
 
 
 
 
 
 
 
 
  /**
 
   * 二进制数组求非
 
   * @param array $bits 二进制数组
 
   */
 
  public function not(array $bits): array
 
  {
 
      for ($i = count($bits)-1; $i >= 0; $i--) {
 
          $bits[$i] = ($bits[$i] == 0) ? 1 : 0;
 
      }
 
      return $bits;
 
  }
 
  多个二进制数组相 与。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  /**
 
   * 二进制数组求与
 
   * @param array $args 二进制数组
 
   */
 
  public function and(array ...$args): array
 
  {
 
      $argc = count($args);
 
      if ($argc == 0) return [];
 
      for ($i = 1; $i < $argc; $i++) {
 
          $j = count($args[0]) - 1;
 
          $k = count($args[$i]) - 1;
 
          while ($j >= 0 || $k >= 0) {
 
              $j < 0 and array_unshift($args[0], 0) and $j = 0; // 如果是$args[0]不够长就头插补齐
 
              ($args[$i][$k] ?? 0) == 0 and $args[0][$j] = 0;
 
              $j--;
 
              $k--;
 
          }
 
      }
 
      return $args[0];
 
  }
 
  多个二进制数组求 异或。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  /**
 
   * 二进制数组求异或
 
   * @param array $args 二进制数组
 
   */
 
  public function xor(array ...$args): array
 
  {
 
      $argc = count($args);
 
      if ($argc == 0) return [];
 
      for ($i = 1; $i < $argc; $i++) {
 
          $j = count($args[0]) - 1;
 
          $k = count($args[$i]) - 1;
 
          while ($j >= 0 || $k >= 0) {
 
              $j < 0 and array_unshift($args[0], 0) and $j = 0; // 如果是$args[0]不够长就头插补齐
 
              $args[0][$j] = intval($args[0][$j] != ($args[$i][$k] ?? 0));
 
              $j--;
 
              $k--;
 
          }
 
      }
 
      return $args[0];
 
  }
 
  多个二进制数组 相加。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  /**
 
   * 二进制数组相加
 
   * @param array $args 二进制数组
 
   */
 
  public function add(array ...$args): array
 
  {
 
      $argc = count($args);
 
      if ($argc == 0) return [];
 
      for ($i = 1; $i < $argc; $i++) {
 
          $carry = 0;
 
          $j = count($args[0]) - 1;
 
          $k = count($args[$i]) - 1;
 
          while ($j >= 0 || $k >= 0) {
 
              $j < 0 and array_unshift($args[0], 0) and $j = 0; // 如果是$args[0]不够长就头插补齐
 
              $carry += $args[0][$j] + ($args[$i][$k] ?? 0);
 
              switch ($carry) {
 
                  case 1: $carry = 0; $args[0][$j] = 1; break;
 
                  case 2: $carry = 1; $args[0][$j] = 0; break;
 
                  case 3: $carry = 1; $args[0][$j] = 1; break;
 
              }
 
              $j--;
 
              $k--;
 
          }
 
          $carry == 1 and array_unshift($args[0], $carry); // 计算完后还有进位则加长存放
 
      }
 
      return array_slice($args[0], -32); // 计算结果只保留32位
 
  }
 
  打印二进制数组,用于调试用途,每 8 位会补一个空格,每 32 位补两个空格,每 64 位换一行,每 512 位空一行,让打印的数据更容易查看。

(编辑:我爱故事小小网_铜陵站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读