오늘 주로 배우는 내용은 해시 알고리즘보다는 PHP의 해시 해시 암호화와 관련된 일부 확장 기능을 사용하는 것입니다. 키 알고리즘은 우리가 입력한 문자열이 해시 테이블과 마찬가지로 해당 해시 값을 갖는다는 점에서 본질적으로 일반 데이터 구조의 해시 키-값 매핑과 동일합니다. 즉, 알고리즘이 더 복잡하다는 것입니다. 사실, 일정 기간 동안 PHP 개발을 해본 사람이라면 md5()와 sha1()이라는 두 가지 함수에 확실히 익숙할 것입니다. 이 두 기능은 각각 md5 및 sha1 알고리즘의 해시 암호화를 생성하는 것입니다. 하지만 오늘 우리가 배우는 내용은 이 두 함수보다 더 복잡하고, 알고리즘 형태도 더 풍부합니다.
해시 정보 다이제스트 알고리즘이란
일반적으로 해시 함수에 콘텐츠를 입력한 후 반환되는 해시 문자열은 입력 값의 해시 정보 다이제스트입니다. PHP에서는 md5이든 sha1이든 동일한 입력으로 동일한 결과가 생성됩니다. 따라서 사용자 비밀번호 정보를 저장할 때 해시의 한 계층만 사용하지 않으려고 노력합니다. 이러한 형태의 암호화는 레인보우 테이블을 통한 무차별 대입을 통해 해독될 수 있기 때문입니다. 여러 레이어에서 비밀번호를 해시하고 솔트를 추가하여 해시 값을 복잡하게 만들 수 있습니다.
물론 해시 알고리즘은 일반적으로 사용되는 md5, sha1에 국한되지 않고 다른 유형의 알고리즘도 많이 있지만 일반적으로 사용하지는 않습니다. 하지만 오늘 소개하는 함수들은 다양한 형태의 Hash 암호화를 수행할 수 있는 함수들의 집합일 뿐이므로, PHP의 기본 환경에 통합되어 있으므로 이를 사용하기 위해서는 별도의 확장이 필요하지 않습니다. 암호화된 데이터를 다양화하면 더욱 편리해집니다.
PHP에서 지원하는 해시 알고리즘
print_r(hash_algos()); // Array // ( // [0] => md2 // [1] => md4 // [2] => md5 // [3] => sha1 // [4] => sha224 // [5] => sha256 // [6] => sha384 // [7] => sha512/224 // [8] => sha512/256 // [9] => sha512 // [10] => sha3-224 // [11] => sha3-256 // [12] => sha3-384 // [13] => sha3-512 // [14] => ripemd128 // [15] => ripemd160 // [16] => ripemd256 // [17] => ripemd320 // [18] => whirlpool // [19] => tiger128,3 // [20] => tiger160,3 // [21] => tiger192,3 // [22] => tiger128,4 // [23] => tiger160,4 // [24] => tiger192,4 // [25] => snefru // [26] => snefru256 // [27] => gost // [28] => gost-crypto // [29] => adler32 // [30] => crc32 // [31] => crc32b // [32] => fnv132 // [33] => fnv1a32 // [34] => fnv164 // [35] => fnv1a64 // [36] => joaat // [37] => haval128,3 // [38] => haval160,3 // [39] => haval192,3 // [40] => haval224,3 // [41] => haval256,3 // [42] => haval128,4 // [43] => haval160,4 // [44] => haval192,4 // [45] => haval224,4 // [46] => haval256,4 // [47] => haval128,5 // [48] => haval160,5 // [49] => haval192,5 // [50] => haval224,5 // [51] => haval256,5 // ) $data = "我们来测试一下Hash算法!"; foreach (hash_algos() as $v) { $r = hash($v, $data); echo $v, ':', strlen($r), '::', $r, PHP_EOL; } // md2:32::3d63d5f6ce9f03379fb3ae5e1436bf08 // md4:32::e9dc8afa241bae1bccb7c58d4de8b14d // md5:32::2801b208ec396a2fc80225466e17acac // sha1:40::0f029efe9f1115e401b781de77bf1d469ecee6a9 // sha224:56::3faf937348ec54936be13b63feee846d741f8391be0a62b4d5bbb2c8 // sha256:64::8f0bbe9288f6dfd2c6d526a08b1fed61352c894ce0337c4e432d97570ae521e3 // sha384:96::3d7d51e05076b20f07dad295b161854d769808b54b784909901784f2e76db212612ebe6fe56c6d014b20bd97e5434658 // …… foreach (hash_hmac_algos() as $v) { $r = hash_hmac($v, $data, 'secret'); echo $v, ':', strlen($r), '::', $r, PHP_EOL; } // md2:32::70933e963edd0dcd4666ab9253a55a12 // md4:32::d2eda43ee4fab5afc067fd63ae6390f1 // md5:32::68bf5963e1426a1feff8149da0d0b88d // sha1:40::504bc44704b48ac75435cdccf81e0f056bac98ba // sha224:56::8beaf35baedc2cd5725c760ec77d119e3373f14953c74818f1243f69 // sha256:64::23f2e6685fe368dd3ebe36e1d3d672ce8306500366ba0e8a19467c94e13ddace // sha384:96::740ce7488856737ed57d7b0d1224d053905661ffca083c02c6a9a9230499a4a3d96ff0a951b8d03dbafeeeb5c84a65a6 // ……
hash_algos(), hash_hmac_algos() 함수를 통해 현재 PHP 환경에서 지원하는 모든 해시 알고리즘을 확인할 수 있으며, md2, sha224는 물론, 익숙한 md5, sha1도 확인할 수 있습니다. ,ripemd320, fnv1a64 및 기타 거의 볼 수 없는 알고리즘입니다. 그런 다음 이 두 함수에 의해 반환된 내용을 탐색하고 hash() 및 hash_hmac() 함수를 사용하여 데이터를 해시하고 해당 내용을 보면 각 알고리즘이 서로 다른 암호화된 정보 다이제스트를 성공적으로 반환할 수 있다는 것을 알 수 있습니다. .
hmac 관련 함수는 PHP의 Hash 알고리즘의 또 다른 형태로 hash_hmac()의 세 번째 매개변수인 키가 필요한 알고리즘입니다. 입력 내용이 동일하고 키가 동일한 경우에만 반환되는 결과도 동일합니다. 즉, 이 기능은 대칭적으로 암호화된 정보 전송 확인 토큰에 사용될 수 있습니다. 예를 들어, 두 시스템 간의 인터페이스 상호 통신에 고정 토큰이 필요한 경우 이 기능을 사용하여 이를 달성할 수 있습니다.
md5() 및 sha1() 비교
이 hash() 함수는 너무 강력해서 생성되는 콘텐츠가 md5와 동일합니까?
// 与 md5 sha1 函数对比 echo hash('md5', '我们来测试一下Hash算法!'), PHP_EOL; echo md5('我们来测试一下Hash算法!'), PHP_EOL; // 2801b208ec396a2fc80225466e17acac // 2801b208ec396a2fc80225466e17acac echo hash('sha1', '我们来测试一下Hash算法!'), PHP_EOL; echo sha1('我们来测试一下Hash算法!'), PHP_EOL; // 0f029efe9f1115e401b781de77bf1d469ecee6a9 // 0f029efe9f1115e401b781de77bf1d469ecee6a9 echo hash('fnv164', '我们来测试一下Hash算法!'), PHP_EOL; // b25bd7371f08cea4
물론 md5()와 sha1() 두 함수 자체가 hash() 함수의 구문 설탕이라고 생각합니다. 이 두 가지 알고리즘은 매우 일반적으로 사용되기 때문에 PHP는 현재 두 가지 함수를 직접 캡슐화하고 하나의 매개변수만 필요하므로 매우 간단하고 편리합니다.
파일 HASH
많은 다운로드 사이트에서는 다운로드한 파일이 완전하고 동일한지 확인하고 비교할 수 있도록 다운로드한 파일의 해시 값을 제공합니다. 이것은 파일 해시의 응용 프로그램입니다. 사실 직설적으로 말하면 파일 내용을 해싱한 후 얻은 파일에 대한 정보를 요약한 것일 뿐입니다. 물론 이 기능 세트는 PHP에서도 완벽하게 지원됩니다.
/ 文件 HASH echo hash_file('md5', './create-phar.php'), PHP_EOL; echo md5_file('./create-phar.php'), PHP_EOL; // ba7833e3f6375c1101fb4f1d130cf3d3 // ba7833e3f6375c1101fb4f1d130cf3d3 echo hash_hmac_file('md5', './create-phar.php', 'secret'), PHP_EOL; // 05d1f8eb7683e190340c04fc43eba9db
hkdf와 pbkdf2의 HASH 알고리즘
다음에 소개하는 두 가지 알고리즘은 두 가지 특별한 Hash 알고리즘입니다. hmac과 비슷하지만 hmac보다 더 복잡합니다.
// hkdf pbkdf2 算法 // 算法 明文密码(原始二进制) 输出长度 应用程序/特定于上下文的信息字符串 salt值 $hkdf1 = hash_hkdf('sha256', '123456', 32, 'aes-256-encryption', random_bytes(2)); $hkdf2 = hash_hkdf('sha256', '123456', 32, 'sha-256-authentication', random_bytes(2)); var_dump($hkdf1); var_dump($hkdf2); // string(32) "ԇ`q��X�l� // f�yð����}Ozb+�" // string(32) "%���]�+̀�\JdG��HL��GK�� // -" // 算法 明文密码 salt值 迭代次数 数据长度 echo hash_pbkdf2("sha256", '123456', random_bytes(2), 1000, 20), PHP_EOL; // e27156f9a6e2c55f3b72
hmac에는 키만 필요하고 hash_hkdf()는 반환 길이, 애플리케이션/컨텍스트별 정보 문자열, 솔트 값의 세 가지 매개 변수를 추가하며 암호화된 내용은 바이너리 암호화입니다. 내용이 매우 고급스러운 느낌이 들지 않나요? ? Hash_pbkdf2()는 솔트 값, 반복 횟수 및 데이터 길이라는 세 가지 매개변수를 추가하며 비밀번호 암호화에도 좋은 도우미입니다. 하지만 상대적으로 보안 요구 사항이 매우 높은 비밀번호인 경우에는 이 두 가지 기능을 사용할 수 있습니다.
hash_equals() 함수는 해시 비교를 수행합니다
PHP는 해시 값이 같은지 비교하는 함수도 제공합니다. 어떤 친구들은 문자열 형태의 요약 정보가 반환되기 때문에 단순히 ===만으로 충분하지 않냐고 물을 수도 있습니다. 왜 비교할 특별한 함수가 필요한가요? 걱정하지 마세요. 먼저 코드를 살펴보겠습니다.
// hash_equals 比较函数 $v1 = hash('md5', '测试对比'); $v2 = hash('md5', '测试对比'); $v3 = hash('md5', '测试对比1'); // 比较两个字符串,无论它们是否相等,本函数的时间消耗是恒定的 // 本函数可以用在需要防止时序攻击的字符串比较场景中, 例如,可以用在比较 crypt() 密码哈希值的场景 var_dump(hash_equals($v1, $v2)); var_dump(hash_equals($v1, $v3)); // bool(true) // bool(false)
我在注释中已经写得很清楚了,hash_equals() 函数主要是可以防止时序攻击。一般来说,这个时序攻击就是根据你的系统运行时间长短来判断你的系统中使用了什么函数或者功能,这都是非常厉害的黑客高手玩的东西。比如说,我们比较用户密码的时候,假设是一位一位的进行比较,那么如果第一个字符错了信息很快就会返回,而如果比较到最后一个才错的时候,程序运行时间就会长很多,黑客就可以根据这个时长来判断当前暴力破解的内容是否一步步达到目标,也让破解难度逐步下降。(普通的字符串比较 === 就是基于位移的)。而 hash_equals() 则是不管怎么比较,相同的 Hash 算法长度的内容返回的时间都是相同的。OpenSSL 、 OpenSSH 等软件都曾出现过这种类似的时序攻击漏洞!
当然,这个我们只做了解即可,同样也是对于安全性有特殊要求的一些项目,就可以使用这个函数来避免出现这种时序攻击的漏洞提高系统安全性。
增量 Hash 操作
最后我们要学习的是一套增量 Hash 的操作函数。其实对于字符串来说,大部分情况下我们直接将字符串拼接好再 Hash 就可以了,并不太需要增量 Hash 的能力。但是如果是对于多个文件或者读写流来说,想要获得多文件的 Hash 值,就可以使用这一套增量 Hash 函数来进行操作了。
// 增量 HASH $fp = tmpfile(); fwrite($fp, '初始化一个流文件'); rewind($fp); $h1 = hash_init('md5'); // 开始增量 Hash hash_update($h1, '测试增量'); // 普通字符串 hash_update_file($h1, './create-phar.php'); // 文件 hash_update_stream($h1, $fp); // 流 $v1 = hash_final($h1); // 结束 Hash 返回结果 echo $v1, PHP_EOL; // 373df6cc50a1d7cd53608208e91be1e7 $h2 = hash_init('md5', HASH_HMAC, 'secret'); // 使用 HMAC 算法的增量 HASH hash_update($h2, '测试增量'); hash_update_file($h2, './create-phar.php'); hash_update_stream($h2, $fp); $v2 = hash_final($h2); echo $v2, PHP_EOL; // 34857ee5d8b573f6ee9ee20723470ea4
我们使用 hash_init() 来获得一个增量 Hash 操作句柄并指定好加密算法。然后使用 hash_update() 添加字符串、使用 hash_update_file() 增加文件内容,使用 hash_update_stream() 来增加流内容,最后使用 hash_final() 结束句柄操作进行 Hash 计算并返回结果值。得到的结果值就是包含字符串、文件和流内容一起 Hash 的结果。
推荐学习:《PHP视频教程》
总结
说实话,在没有学习今天的内容之前,我也一直以为 PHP 里面只有 md5 和 sha1 这两种 Hash 算法呢。这回真是大开了眼界,我们不仅拥有丰富的算法库,而且还有很多方便的操作函数能够帮助我们方便的使用这些算法,不多说了,学习继续!
测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/php/202007/source/PHP%E7%9A%84Hash%E4%BF%A1%E6%81%AF%E6%91%98%E8%A6%81%E6%89%A9%E5%B1%95%E6%A1%86%E6%9E%B6.php