您的位置:

PHP MD5加密解密原理详解

一、什么是MD5加密?

MD5加密是一种单向加密算法,将任意长度的数据加密成固定长度的密文。主要被用于密码加密,数字签名等领域。

MD5加密特点:

1、加密结果是固定长度的128bit字符串,不管加密前数据的长度多长,密文长度始终为128bit。

2、同样的数据进行MD5加密后,生成的密文总是相同的。

3、密文经MD5加密后无法通过任何手段还原为明文。

二、MD5加密的实现原理

MD5加密通过对明文进行多次处理,生成固定长度的密文。处理过程主要包括以下几步:

1、填充

将明文进行长度扩展,使其长度满足448mod512,并在其末尾添加一个64bit的数据,用于保存明文本身的长度。


//填充函数
function padding($str){
    $len = strlen($str);        //得到字符串长度
    $pad_len = 448 - $len % 512;    //计算需要填充的长度
    if($pad_len <= 64)   //如果需要填充的长度介于1-64之间,则需要加一个分组
        $pad_len += 512;
    $pad_char = chr($pad_len/8);    //计算需要填充的字符
    $padding_str = "";
    for($i=0; $i<$pad_len/8; $i++)  
        $padding_str .= $pad_char;
    $padding_str .= $str.pack("Q*", $len*8);    //连接原始数据长度
    return $padding_str;  
} 

2、初始化

对填充后的明文进行初始化,生成4个32bit的缓存区,对应a、b、c、d。根据RFC1321规格定义的初始值,将a、b、c、d设置成如下值:

a = 0x67452301

b = 0xEFCDAB89

c= 0x98BADCFE

d=0x10325476


//初始化MD5状态变量
$A = 0x67452301;
$B = 0xEFCDAB89;
$C = 0x98BADCFE;
$D = 0x10325476; 

3、四轮迭代

将初始化后的缓存区与每个512位的分组进行四轮迭代计算,每轮迭代对缓存区进行ABCD四个变量的置换。


//四轮迭代
function md5_loop($f, $m, $s, $i){
    global $A, $B, $C, $D;
    if($f == 'F')
        $res = ($B & $C) | (~$B & $D);
    else if($f == 'G')
        $res = ($B & $D) | ($C & ~$D);
    else if($f == 'H')
        $res = ($B ^ $C ^ $D);
    else if($f == 'I')
        $res = ($C ^ ($B | ~$D));
    $temp = $B + shiftleft(($A + $res + $m + $s[$i]), $i*5%32);
    $A = $D;
    $D = $C;
    $C = $B;
    $B = $temp;
}

//用于循环左移
function shiftleft($num, $bits){
    $num = $num & 0xffffffff;    //强制截断为32位
    $num_str = decbin($num);    //转换为二进制字符串
    $num_str = str_pad($num_str, 32, 0, STR_PAD_LEFT);    //补充0,使其长度变成32位
    $num_str = substr($num_str, $bits).substr($num_str, 0, $bits);    //将字符串分为两部分,分别进行循环左移
    return bindec($num_str);    //将处理结果返回为十进制数
}

4、生成密文

经过四轮迭代之后,将四个缓存区连接起来,组成128bit长度的MD5摘要,即为密文。


//生成MD5摘要
function md5($str){
    $padding_str = padding($str); 
    $chunk = strlen($padding_str) / 64;    //分段
    for($i=0; $i<$chunk; $i++){
        $str_chunk = substr($padding_str, $i*64, 64);    //得到每一段字符串
        $word = array();
        for($j=0; $j<16; $j++){
            $word[] = unpack("V", substr($str_chunk, $j*4, 4))[1];    //将字符串分为16组,每组4个字节,然后将每组转换成unsigned int数值
        }
        //四轮迭代
        $s = array(7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21);
        $K = array(0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,    0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391);
        for($j=0; $j<64; $j++){
            if($j<16){
                md5_loop('F', $word[$j], $s, $j);
            }
            else if($j<32){
                md5_loop('G', $word[(5*$j+1)%16], $s, $j);
            }
            else if($j<48){
                md5_loop('H', $word[(3*$j+5)%16], $s, $j);
            }
            else{
                md5_loop('I', $word[(7*$j)%16], $s, $j);
            }
        }
    }
    $md5_str = pack("V4", $A, $B, $C, $D);    //将4个缓存区合并成字符串
    return bin2hex($md5_str);    //将字符串转换成16进制数值
}

三、MD5解密

MD5加密是一种单向加密算法,无法通过密文还原明文。但是可以通过穷举法,对所有可能的明文进行加密,找到和密文一致的密文,即为原来的明文。


//MD5解密
function md5_decrypt($str, $target_md5_str){
    $padding_str = padding($str); 
    $chunk = strlen($padding_str) / 64;
    for($i=0; $i<$chunk; $i++){
        $str_chunk = substr($padding_str, $i*64, 64);    //得到每一段字符串
        $word = array();
        for($j=0; $j<16; $j++){
            $word[] = unpack("V", substr($str_chunk, $j*4, 4))[1];    //将字符串分为16组,每组4个字节,然后将每组转换成unsigned int数值
        }
        //四轮迭代
        $s = array(7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21);
        $K = array(0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,    0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd