찾다
백엔드 개발PHP 튜토리얼PHP에서 자신만의 sha-256 해싱 알고리즘을 구현해보세요!

해시는 "해시"라고도 합니다. 임의의 길이의 입력 정보 집합을 받아 해시 알고리즘을 통해 고정 길이 데이터 지문으로 변환합니다. 전반적으로 해시는 메시지 다이제스트라고 생각할 수 있습니다.

PHP에는 문자열의 해시 값을 계산할 수 있는 hash() 함수가 있습니다. 호기심에 해시 계산의 특정 단계를 검색하고 PHP를 사용하여 sha-256 해시 값에 대한 계산 세트를 작성했습니다. 암호. 물론 sha-256 외에도 다른 해시 알고리즘이 있지만 현재는 sha-256이 더 일반적으로 사용됩니다. 다음은 현재 국립표준기술원에서 발표한 해시 알고리즘입니다.

해시 알고리즘 입력 크기(비트) 블록 크기(비트) 행 크기(비트) 바이너리 길이 생성 (비트) 16진수 길이(문자) 생성
sha1 512 32 160 40
샤 -224 < ; 64 512 32 224 56
sha-256 512 32 256 64
sha-384 1024 64 384 96
sha-512 1024 64 512 128
샤- 512/224 1024 64 224 56
sha-512/256 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/

이 글은 내용이 많고 주로 다음 부분으로 나누어져 있습니다. 읽을 때 독자들은 준비 2: 보조 방법을 건너뛰고 바로 다음 페이지로 이동할 수 있습니다. 단계 부분을 읽은 후 단계 부분에서 지정된 방법을 사용해야 하는 경우 돌아가서 준비 2: 보조 방법의 기능을 확인하세요.

  • 준비 1: 코드 본문

  • 준비 2: 보조 방법(읽을 때 건너뛸 수 있음)

  • 1단계: 문자열을 바이너리로 변환

  • 2단계: 숫자 1 추가

  • 3단계: 512의 배수로 채우기

  • 4단계: 원본 길이 정보 추가

  • 5단계: 블록을 분할하여 2048비트로 채우기

  • 6단계: 블록 데이터 수정

  • 단계 7: 압축

준비 1: 코드 본문

해시를 계산하는 데 필요한 메서드와 속성을 저장하는 알고리즘 클래스를 만듭니다. 이 클래스에는 공용 메소드 sha256()이 하나만 있습니다. 이 메소드는 문자열 매개변수를 전달하고 문자열의 sha-256 해시 값을 출력합니다. 해시 계산을 완료하려면 먼저 sha256()의 함수 본문에 이 7단계의 호출을 작성해야 합니다.

<?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의 제곱근 중 이진 소수 부분의 처음 32자리를 취하여 구합니다. , 13, 17, 19.

/** @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, 변환 16진수 표현: 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,
];

준비 2: 보조 기능

이 부분을 직접 건너뛰고 아래 1단계부터 시작하여 특정 보조 기능을 사용해야 할 경우 여기로 와서 검색하면 됩니다.

해시를 계산하는 과정에서 우리는 이진 데이터를 배열에 저장합니다. 배열의 각 요소는 이진 비트에 해당하므로 이러한 이진 배열에 AND, non-XOR 및 덧셈 등을 수행하려면 , 우리는 우리 자신의 작업 기능을 구현해야 합니다.

십진 정수를 이진 배열로 변환합니다.

/**
 * 十进制整数转化为二进制数组
 * @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;
}

AND 다중 바이너리 배열.

/**
 * 二进制数组求与
 * @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];
}

XOR 다중 바이너리 배열.

/**
 * 二进制数组求异或
 * @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비트마다 한 줄이 남습니다. 인쇄된 데이터를 보기 쉽게 만듭니다.

/**
 * 打印二进制数组
 * @param array $bits 二进制数组
 */
public function printBits(array $bits): void
{
    $len = 0;
    foreach ($bits as $bit) {
        if ($len > 0) {
            if ($len % 512 == 0) echo PHP_EOL;
            if ($len % 64 == 0) {
                echo PHP_EOL;   
            } else {
                if ($len % 32 == 0) echo &#39; &#39;;
                if ($len % 8 == 0) echo &#39; &#39;;
            }
        }
        echo $bit;
        $len++;
    }
    echo PHP_EOL;
}

이진수를 해시 값 문자열로 변환하는 마지막 단계에서 이진수 배열을 16진수로 변환합니다.

/**
 * 二进制数组转化为十六进制
 * @param array $bits 二进制数组
 */
public function bits2hex(array $bits): string
{
    $str = &#39;&#39;;
    for ($i = count($bits)-1; $i >= 0; $i -= 4) {
        $dec = $bits[$i] + ($bits[$i-1] ?? 0)*2 + ($bits[$i-2] ?? 0)*4 + ($bits[$i-3] ?? 0)*8;
        switch ($dec) {
            case 0:  $str = &#39;0&#39; . $str; break;
            case 1:  $str = &#39;1&#39; . $str; break;
            case 2:  $str = &#39;2&#39; . $str; break;
            case 3:  $str = &#39;3&#39; . $str; break;
            case 4:  $str = &#39;4&#39; . $str; break;
            case 5:  $str = &#39;5&#39; . $str; break;
            case 6:  $str = &#39;6&#39; . $str; break;
            case 7:  $str = &#39;7&#39; . $str; break;
            case 8:  $str = &#39;8&#39; . $str; break;
            case 9:  $str = &#39;9&#39; . $str; break;
            case 10: $str = &#39;a&#39; . $str; break;
            case 11: $str = &#39;b&#39; . $str; break;
            case 12: $str = &#39;c&#39; . $str; break;
            case 13: $str = &#39;d&#39; . $str; break;
            case 14: $str = &#39;e&#39; . $str; break;
            case 15: $str = &#39;f&#39; . $str; break;
        }
    }
    return $str;
}

1단계: 문자열을 바이너리로 변환

여기에서는 "hello world" 문자열을 사용하여 전체 해시 계산 프로세스를 보여줍니다. 먼저 PHP에 내장된 해시 함수를 사용하여 결과를 계산할 수 있습니다. "hello world"의 해시 값은 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"입니다. 최종 계산한 해시 값이 이 값과 같으면 계산 논리가 올바른 것입니다.

먼저 "hello world"를 문자로 나눕니다. 각 문자에는 해당 ASCII 코드 값이 있습니다. 이 ASCII 코드 값은 모두 0-256의 정수입니다. PHP의 ord() 함수를 사용하여 이러한 문자를 정수로 변환한 다음 이 정수를 해당 바이너리로 변환하고 $bits 속성에 저장할 수 있습니다. 그리고 이때 $bits의 길이 값을 $originLen 속성에 저장합니다.

"hello world"를 이진 데이터로 변환하면 다음과 같습니다.

“hello world”
01101000 01100101 01101100 01101100  01101111 00100000 01110111 01101111
01110010 01101100 01100100
/**
 * 步骤一:将字符串转化为二进制
 * @param string $str 原始字符串
 */
public function step1_convert_str_to_bits(string $str): void
{
    $this->bits = [];
    $chars = str_split($str);
    foreach ($chars as $char) {
        $this->bits = array_merge($this->bits, $this->int2bits(ord($char), 8));
    }
    $this->originLen = count($this->bits);
}

2단계: 숫자 1

을 추가한 다음 이진 배열 끝에 1을 추가합니다.

$bits
01101000 01100101 01101100 01101100  01101111 00100000 01110111 01101111
01110010 01101100 01100100 1
rrree

3단계: 512의 배수로 채우기

在二进制数组的末尾添加 0 以使得整个二进制数组的个数刚好是 512 的倍数。需要注意的是,二进制数组的最末尾要预留 64 位用于存放原始二进制的长度。也就是一开始将字符串转换成二进制时的长度,我们在 步骤一 中将这个长度值保存到了 $originLen 属性里。

$bits
01101000 01100101 01101100 01101100  01101111 00100000 01110111 01101111
01110010 01101100 01100100 10000000  00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000000
[    预留 64 位用于存储原始字符串的长度    ]
/**
 * 步骤三:在数据末尾添加0,确保二进制的个数是512的倍数,最后预留64位用于存储原始长度信息
 */
public function step3_extend_to_multiple_of_512(): void
{
    $rem = (count($this->bits) + 64) % 512;
    if ($rem > 0) {
        while ($rem < 512) {
            $this->bits[] = 0;
            $rem++;
        }
    }
}

步骤四:追加原始长度信息

把之前记录的原始数据长度 $originLen 转换为 64 位的二进制追加到 $bits 末尾。

$bits
01101000 01100101 01101100 01101100  01101111 00100000 01110111 01101111
01110010 01101100 01100100 10000000  00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000  00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000  00000000 00000000 00000000 01011000
/**
 * 步骤四:把原始字符串位长度,填充到预留在最后的64位(8个字节的长整型)中
 */
public function step4_append_origin_length(): void
{
    $this->bits = array_merge($this->bits, $this->int2bits($this->originLen, 64));
}

步骤五:切分区块并填充至 2048 位

经过 步骤四 之后,$bits 二进制数组的个数已经是 512 的倍数,现在以每 512 位分为一个区块,然后在每个区块末尾填充 0,让每个区块的大小变成 2048 位。每个区块的 2048 位数据以 32 位作为一行,那么就有 64 行。由于 "hello world" 数据比较短,我们就只有一个区块。

- $blocks[0] $blocks[0] -
0
2
4
6
8
10
12
14

16
18
20
22
24
26
28
30

32
34
36
38
40
42
44
46

48
50
52
54
56
58
60
62
01101000 01100101 01101100 01101100
01110010 01101100 01100100 10000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
01101111 00100000 01110111 01101111
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 01011000

00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
1
3
5
7
9
11
13
15

17
19
21
23
25
27
29
31

33
35
37
39
41
43
45
47

49
51
53
55
57
59
61
63
/**
 * 步骤五:每一个512位切分区块,在区块末尾填充0,使得每个区块位数为2048位,经计算
 * 每个区块还需要添加48x32个0
 */
public function step5_split_blocks_and_append_48_lines(): void
{
    $this->blocks = [];
    $append = $this->int2bits(0, 48 * 32);
    $len = count($this->bits);
    for ($i = 0; $i < $len; $i += 512) {
        $this->blocks[] = array_merge(array_slice($this->bits, $i, 512), $append);
    }
}

步骤六:区块数据修改

上一步中我们给每一个区块末尾添加了很多 0,在这一步中,通过一些位操作将这些数据进一步调整。按 32 位为一行,我们需要修改新增加的 16-63 行的数据。修改的逻辑如下:

算法逻辑

For i from w[16…63]:
    s0 = (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3)
    s1 = (w[i-2] rightrotate 17) xor (w[i- 2] rightrotate 19) xor (w[i- 2] rightshift 10)
    w[i] = w[i-16] + s0 + w[i-7] + s1

其中 w 是每个区块的行数组,w[i] 就是第 i 行。

rightshift 是右移,rightrotate 是旋转右移, xor 是异或。

这里以第 16 行的处理为例:

算法详解

i = 16
(w[1] rightrotate 7) = 01101111001000000111011101101111 -> 11011110110111100100000011101110
(w[1] rightrotate 18) = 01101111001000000111011101101111 -> 00011101110110111101101111001000
(w[1] rightshift 3) = 01101111001000000111011101101111 -> 00001101111001000000111011101101
s0 = (w[1] rightrotate 7) xor (w[1] rightrotate 18) xor (w[1] rightshift 3)
 = 11001110111000011001010111001011
(w[14] rightrotate 17) = 00000000000000000000000000000000 -> 00000000000000000000000000000000
(w[14] rightrotate 19) = 00000000000000000000000000000000 -> 00000000000000000000000000000000
(w[14] rightshift 10) = 00000000000000000000000000000000 -> 00000000000000000000000000000000
s1 = (w[14] rightrotate 17) xor (w[14] rightrotate 19) xor (w[14] rightshift 10)
= 00000000000000000000000000000000
w[i] = w[0] + s0 + w[9] + s1
= 00110111010001110000001000110111(相加得到的值如果超过 32 位,则抹去高位)
/**
 * 步骤六:针对每一个2048位区块处理:以32位为一行,总共有64行,修改【16-63】行的数据,
 * 这【16-63】行就是上一步新增的48x32个0
 */
public function step6_modify_blocks_appended_48_lines(): void
{
    foreach ($this->blocks as &$block) {
        for ($i = 16; $i < 64; $i++) {
            $w0 = array_slice($block, ($i-16)*32, 32);
            $w1 = array_slice($block, ($i-15)*32, 32);
            $w9 = array_slice($block, ($i-7)*32, 32);
            $w14 = array_slice($block, ($i-2)*32, 32);
            $s0 = $this->xor(                
                $this->rightRotate($w1, 7),
                $this->rightRotate($w1, 18),
                $this->rightShift($w1, 3)
            );
            $s1 = $this->xor(
                $this->rightRotate($w14, 17),
                $this->rightRotate($w14, 19),
                $this->rightShift($w14, 10)
            );
            $wi = $this->add($w0, $s0, $w9, $s1);
            // 如果$wi的长度超过了32位,则只取32位,舍弃高位
            $k = count($wi) - 1;
            for ($j = $i * 32 + 31; $j >= $i * 32; $j--) {
                $block[$j] = $wi[$k] ?? 0;
                $k--;
            }
        }
    }
}

步骤七:压缩

新建变量 $a、$b、$c、$d、$e、$f、$g、$h 值依次分别等于哈希常量 H[0-7],接着循环每一个区块的每一行,通过 与 非 异或 等操作将信息压缩到 $a、$b、$c、$d、$e、$f、$g、$h 中,最后将 $a、$b、$c、$d、$e、$f、$g、$h 的值与原始常量 H[0-7] 相加,拼接相加后的二进制结果 h0~h7 并转化为十六进制字符串得到最终的哈希值。

具体的压缩算法如下:

算法逻辑

For i from 0 to 63
    s1 = (e rightrotate 6) xor (e rightrotate 11) xor (e rightrotate 25)
    ch = (e and f) xor ((not e) and g)
    temp1 = h + s1 + ch + k[i] + w[i]
    s0 = (a rightrotate 2) xor (a rightrotate 13) xor (a rightrotate 22)
    maj = (a and b) xor (a and c) xor (b and c)
    temp2 := s0 + maj
    h = g
    g = f
    f = e
    e = d + temp1
    d = c
    c = b
    b = a
    a = temp1 + temp2

这里以第 0 行的处理为例,列出了变量计算结果方便大家对照调试:

计算结果

i = 0
s1 = 00110101100001110010011100101011
ch = 00011111100001011100100110001100
temp1 = 01011011110111010101100111010100
s0 = 11001110001000001011010001111110
maj = 00111010011011111110011001100111
temp2 = 00001000100100001001101011100101
h = 00011111100000111101100110101011
g = 10011011000001010110100010001100
f = 01010001000011100101001001111111
e = 00000001001011010100111100001110
d = 00111100011011101111001101110010
c = 10111011011001111010111010000101
b = 01101010000010011110011001100111
a = 01100100011011011111010010111001
/**
 * 步骤七:压缩数据
 */
public function step7_compress_to_final_hash(): string
{
    $a = $h0 = $this->int2bits(static::H[0], 32);
    $b = $h1 = $this->int2bits(static::H[1], 32);
    $c = $h2 = $this->int2bits(static::H[2], 32);
    $d = $h3 = $this->int2bits(static::H[3], 32);
    $e = $h4 = $this->int2bits(static::H[4], 32);
    $f = $h5 = $this->int2bits(static::H[5], 32);
    $g = $h6 = $this->int2bits(static::H[6], 32);
    $h = $h7 = $this->int2bits(static::H[7], 32);
    foreach ($this->blocks as $block) {
        for ($i = 0; $i < 64; $i++) {
            $s1 = $this->xor(
                $this->rightRotate($e, 6),
                $this->rightRotate($e, 11),
                $this->rightRotate($e, 25)
            );
            $ch = $this->xor(
                $this->and($e, $f),
                $this->and($this->not($e), $g)
            );
            $ki = $this->int2bits(static::K[$i], 32);
            $wi = array_slice($block, $i*32, 32);
            $temp1 = $this->add($h, $s1, $ch, $ki, $wi);
            $s0 = $this->xor(
                $this->rightRotate($a, 2),
                $this->rightRotate($a, 13),
                $this->rightRotate($a, 22),
            );
            $maj = $this->xor(
                $this->and($a, $b),
                $this->and($a, $c),
                $this->and($b, $c)
            );
            $temp2 = $this->add($s0, $maj);
            $h = $g;
            $g = $f;
            $f = $e;
            $e = $this->add($d, $temp1);
            $d = $c;
            $c = $b;
            $b = $a;
            $a = $this->add($temp1, $temp2);
        }
    }
    $h0 = $this->add($h0, $a);
    $h1 = $this->add($h1, $b);
    $h2 = $this->add($h2, $c);
    $h3 = $this->add($h3, $d);
    $h4 = $this->add($h4, $e);
    $h5 = $this->add($h5, $f);
    $h6 = $this->add($h6, $g);
    $h7 = $this->add($h7, $h);
    return $this->bits2hex(array_merge($h0, $h1, $h2, $h3, $h4, $h5, $h6, $h7));
}

至此整个哈希 sha-256 计算流程就完成了, 计算得到的哈希值也与 PHP 自带的 hash() 函数计算结果一致。

위 내용은 PHP에서 자신만의 sha-256 해싱 알고리즘을 구현해보세요!의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명
이 기사는 learnku에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제
PHP의 현재 상태 : 웹 개발 동향을 살펴보십시오PHP의 현재 상태 : 웹 개발 동향을 살펴보십시오Apr 13, 2025 am 12:20 AM

PHP는 현대 웹 개발, 특히 컨텐츠 관리 및 전자 상거래 플랫폼에서 중요합니다. 1) PHP는 Laravel 및 Symfony와 같은 풍부한 생태계와 강력한 프레임 워크 지원을 가지고 있습니다. 2) Opcache 및 Nginx를 통해 성능 최적화를 달성 할 수 있습니다. 3) PHP8.0은 성능을 향상시키기 위해 JIT 컴파일러를 소개합니다. 4) 클라우드 네이티브 애플리케이션은 Docker 및 Kubernetes를 통해 배포되어 유연성과 확장 성을 향상시킵니다.

PHP 대 기타 언어 : 비교PHP 대 기타 언어 : 비교Apr 13, 2025 am 12:19 AM

PHP는 특히 빠른 개발 및 동적 컨텐츠를 처리하는 데 웹 개발에 적합하지만 데이터 과학 및 엔터프라이즈 수준의 애플리케이션에는 적합하지 않습니다. Python과 비교할 때 PHP는 웹 개발에 더 많은 장점이 있지만 데이터 과학 분야에서는 Python만큼 좋지 않습니다. Java와 비교할 때 PHP는 엔터프라이즈 레벨 애플리케이션에서 더 나빠지지만 웹 개발에서는 더 유연합니다. JavaScript와 비교할 때 PHP는 백엔드 개발에서 더 간결하지만 프론트 엔드 개발에서는 JavaScript만큼 좋지 않습니다.

PHP vs. Python : 핵심 기능 및 기능PHP vs. Python : 핵심 기능 및 기능Apr 13, 2025 am 12:16 AM

PHP와 Python은 각각 고유 한 장점이 있으며 다양한 시나리오에 적합합니다. 1.PHP는 웹 개발에 적합하며 내장 웹 서버 및 풍부한 기능 라이브러리를 제공합니다. 2. Python은 간결한 구문과 강력한 표준 라이브러리가있는 데이터 과학 및 기계 학습에 적합합니다. 선택할 때 프로젝트 요구 사항에 따라 결정해야합니다.

PHP : 웹 개발의 핵심 언어PHP : 웹 개발의 핵심 언어Apr 13, 2025 am 12:08 AM

PHP는 서버 측에서 널리 사용되는 스크립팅 언어이며 특히 웹 개발에 적합합니다. 1.PHP는 HTML을 포함하고 HTTP 요청 및 응답을 처리 할 수 ​​있으며 다양한 데이터베이스를 지원할 수 있습니다. 2.PHP는 강력한 커뮤니티 지원 및 오픈 소스 리소스를 통해 동적 웹 컨텐츠, 프로세스 양식 데이터, 액세스 데이터베이스 등을 생성하는 데 사용됩니다. 3. PHP는 해석 된 언어이며, 실행 프로세스에는 어휘 분석, 문법 분석, 편집 및 실행이 포함됩니다. 4. PHP는 사용자 등록 시스템과 같은 고급 응용 프로그램을 위해 MySQL과 결합 할 수 있습니다. 5. PHP를 디버깅 할 때 error_reporting () 및 var_dump ()와 같은 함수를 사용할 수 있습니다. 6. 캐싱 메커니즘을 사용하여 PHP 코드를 최적화하고 데이터베이스 쿼리를 최적화하며 내장 기능을 사용하십시오. 7

PHP : 많은 웹 사이트의 기초PHP : 많은 웹 사이트의 기초Apr 13, 2025 am 12:07 AM

PHP가 많은 웹 사이트에서 선호되는 기술 스택 인 이유에는 사용 편의성, 강력한 커뮤니티 지원 및 광범위한 사용이 포함됩니다. 1) 배우고 사용하기 쉽고 초보자에게 적합합니다. 2) 거대한 개발자 커뮤니티와 풍부한 자원이 있습니다. 3) WordPress, Drupal 및 기타 플랫폼에서 널리 사용됩니다. 4) 웹 서버와 밀접하게 통합하여 개발 배포를 단순화합니다.

과대 광고 : 오늘 PHP의 역할을 평가합니다과대 광고 : 오늘 PHP의 역할을 평가합니다Apr 12, 2025 am 12:17 AM

PHP는 현대적인 프로그래밍, 특히 웹 개발 분야에서 강력하고 널리 사용되는 도구로 남아 있습니다. 1) PHP는 사용하기 쉽고 데이터베이스와 완벽하게 통합되며 많은 개발자에게 가장 먼저 선택됩니다. 2) 동적 컨텐츠 생성 및 객체 지향 프로그래밍을 지원하여 웹 사이트를 신속하게 작성하고 유지 관리하는 데 적합합니다. 3) 데이터베이스 쿼리를 캐싱하고 최적화함으로써 PHP의 성능을 향상시킬 수 있으며, 광범위한 커뮤니티와 풍부한 생태계는 오늘날의 기술 스택에 여전히 중요합니다.

PHP의 약한 참고 자료는 무엇이며 언제 유용합니까?PHP의 약한 참고 자료는 무엇이며 언제 유용합니까?Apr 12, 2025 am 12:13 AM

PHP에서는 약한 참조가 약한 회의 클래스를 통해 구현되며 쓰레기 수집가가 물체를 되 찾는 것을 방해하지 않습니다. 약한 참조는 캐싱 시스템 및 이벤트 리스너와 같은 시나리오에 적합합니다. 물체의 생존을 보장 할 수 없으며 쓰레기 수집이 지연 될 수 있음에 주목해야합니다.

PHP의 __invoke 마법 방법을 설명하십시오.PHP의 __invoke 마법 방법을 설명하십시오.Apr 12, 2025 am 12:07 AM

\ _ \ _ 호출 메소드를 사용하면 객체를 함수처럼 호출 할 수 있습니다. 1. 객체를 호출 할 수 있도록 메소드를 호출하는 \ _ \ _ 정의하십시오. 2. $ obj (...) 구문을 사용할 때 PHP는 \ _ \ _ invoke 메소드를 실행합니다. 3. 로깅 및 계산기, 코드 유연성 및 가독성 향상과 같은 시나리오에 적합합니다.

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
3 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
4 몇 주 전By尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

MinGW - Windows용 미니멀리스트 GNU

MinGW - Windows용 미니멀리스트 GNU

이 프로젝트는 osdn.net/projects/mingw로 마이그레이션되는 중입니다. 계속해서 그곳에서 우리를 팔로우할 수 있습니다. MinGW: GCC(GNU Compiler Collection)의 기본 Windows 포트로, 기본 Windows 애플리케이션을 구축하기 위한 무료 배포 가능 가져오기 라이브러리 및 헤더 파일로 C99 기능을 지원하는 MSVC 런타임에 대한 확장이 포함되어 있습니다. 모든 MinGW 소프트웨어는 64비트 Windows 플랫폼에서 실행될 수 있습니다.

WebStorm Mac 버전

WebStorm Mac 버전

유용한 JavaScript 개발 도구

SecList

SecList

SecLists는 최고의 보안 테스터의 동반자입니다. 보안 평가 시 자주 사용되는 다양한 유형의 목록을 한 곳에 모아 놓은 것입니다. SecLists는 보안 테스터에게 필요할 수 있는 모든 목록을 편리하게 제공하여 보안 테스트를 더욱 효율적이고 생산적으로 만드는 데 도움이 됩니다. 목록 유형에는 사용자 이름, 비밀번호, URL, 퍼징 페이로드, 민감한 데이터 패턴, 웹 셸 등이 포함됩니다. 테스터는 이 저장소를 새로운 테스트 시스템으로 간단히 가져올 수 있으며 필요한 모든 유형의 목록에 액세스할 수 있습니다.

Dreamweaver Mac版

Dreamweaver Mac版

시각적 웹 개발 도구

안전한 시험 브라우저

안전한 시험 브라우저

안전한 시험 브라우저는 온라인 시험을 안전하게 치르기 위한 보안 브라우저 환경입니다. 이 소프트웨어는 모든 컴퓨터를 안전한 워크스테이션으로 바꿔줍니다. 이는 모든 유틸리티에 대한 액세스를 제어하고 학생들이 승인되지 않은 리소스를 사용하는 것을 방지합니다.