xiaolingzi's blog

每天都在成长...

欢迎您:亲

PHP由mcrypt扩展加密改为openssl扩展加密

xiaolingzi 发表于 2018-03-14 13:26:08

一、背景

最近将php版本升级到php7.2, 升级过程发现由于php_mcrypt扩展太过于老旧已经被移除,建议使用php_openssl扩展来替代。我们的项目中有使用到php_mcrypt来进行AES加密,所以就开始用openssl的方式来替代原有的方式,在替代的过程中也发现不少问题。

二、原有php_mcrypt加密

原有的加密是使用AES进行对称加密,加密长度使用MCRYPT_RIJNDAEL_128,加密模式使用MCRYPT_MODE_CBC,密钥和向量都是使用16位字符串,然后默认padding是采用补零的的方式。

代码如下:

加密:

$result = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_CBC, $iv);
$result = base64_encode($result);

其中$str为要加密的字符串,$key为16位密钥字符串,$iv为16位向量字符串。

解密:

$str = base64_decode($str);
$result = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_CBC, $iv);

其中$str为要解密的base64字符串,$key和$iv同上。


三、openssl加密

使用openssl进行AES加密我们主要使用到以下两个方法:

1. 加密 openssl_encrypt($data, $method, $password, $options, $iv)

$data 要密码的数据

$method 加密的方式,比如我们要使用到的AES-128-CBC

$password 密钥串

$options 选项有0 OPENSSL_RAW_DATA=1 OPENSSL_ZERO_PADDING=2 OPENSSL_NO_PADDING=3

$iv 向量字符串

2. 解密 openssl_decrypt($data, $method, $password, $options, $iv)

$data 要解密密码的数据

其它参数含义同加密方法


下面我们就开始使用openssl进行加密解密,我们目标是替换原来的加解密,所以期望是两种方式得到的加解密结果是一致的。

第一次试验,我们使用AES-128-CBC加密方式,使用跟原来一样密钥和向量,$options选项我们使用默认的0   

public static function encrypt($str, $key, $iv, $options=0)
{
    $result = openssl_encrypt($str, 'AES-128-CBC', $key, $options, $iv);
    return $result;
}

从返回的结果来看,已经是一个base64加密串了,但得到的结果跟原来加密的结果不一样。经过查找资料和试验得到以下结论:

1. 默认选项下加密结果直接返回base64加密串

2. 默认选项下会自动使用PKCS7进行填充。而我们原来默认是使用0进行填充,所以解释了结果不一致的问题。


第二次试验,既然原来是补零的方式的,那么我就使用OPENSSL_ZERO_PADDING选项来进行加密,其它保持一样。

结果:如果单纯将$options的值由0改为OPENSSL_ZERO_PADDING也就是2,结果返回了false。

经过查找资料和试验得到以下结论:

1. openssl不推荐补0的方式,所以就算使用了该选项也一样不会自动进行padding,仍然需要手动进行padding。

2. 返回结果已经是base64字符串

所以我们手动进行补零试试,简单代码如下:

public static function padZero($data, $blocksize = 16)
{
    $pad = $blocksize - (strlen($data) % $blocksize);
    return $data . str_repeat("\0", $pad);
}
           
public static function unpadZero($data)
{
    return rtrim($data,"\0");
}

手动进行补零后,得到结果就跟mcrypt_encrypt加密的结果一样。所以如果要兼容以前的就按手动进行补零的方式进行padding。


第三次试验,我们来测试使用OPENSSL_NO_PADDING的方式来进行加密,其它保持一样。

结果:如果单纯将$options的值由0改为OPENSSL_NO_PADDING也就是3,结果返回了false。

经过查找资料和试验得到以下结论:

1. openssl是不支持No Padding的,如果选择OPENSSL_NO_PADDING,就得自己手动进行padding。

2. padding之后返回结果默认也没有进行base64编码,需要手动进行


第四次试验我们来测试使用OPENSSL_RAW_DATA的方式来进行加密,其它保持一样。

结果:返回了加密结果,但没有进行base64编码,但编码之后的加密结果跟mcrypt_encrypt也不一样,说明默认padding不是补零。

经过查找资料和试验得到以下结论:

1. 该方式会自动进行PKCS7方式的填充

2. 返回的结果需要自行进行base64


最后PKCS7的padding和unpadding的方法如下:

public static function pad($data, $blocksize = 16)
{
    $pad = $blocksize - (strlen($data) % $blocksize);
    return $data . str_repeat(chr($pad), $pad);
}
                       
public static function unpad($text)
{
    $pad = ord($text[strlen($text) - 1]);
                       
    if ($pad > strlen($text))
    {
        return false;
    }
                           
    if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) 
    {
        return false;
    }
                       
    return substr($text, 0, -1 * $pad);
}

如果需要padding和base64方法如下: 

public static function encrypt($str, $key, $iv, $options=0)
{
    $str = self::pad($str);
    $result = openssl_encrypt($str, 'AES-128-CBC', $key, $options, $iv);
    result = base64_encode($result);
    return $result;
}


四、结论

1. 从上面的试验来看,openssl的方式都是要进行padding的。

2. 原来mcrypt_encrypt默认采用补零的方式,所以如果要跟原来结果保持一致,openssl加密时也一样要通过补零的方式进行padding。

3. 如果原来mcrypt加密是采用PKCS7的方式,那么使用同样的padding方式就可以得到一样的结果。

4. 上面都是基于16位密钥加密的情况,如果使用32位密钥进行加密,那么MCRYPT_RIJNDAEL_128对应于openssl加密方式是AES-256-CBC,这一点要切记。MCRYPT_RIJNDAEL_128的定义有误解的地方。

最后的结论是,如果原来使用MCRYPT_RIJNDAEL_128长度,两种方式都对加密串进行相同的padding,然后密钥是16位时openssl使用AES-128-CBC,密钥是32位时openssl使用AES-256-CBC,就可以得到一样的结果。


      

转载请注明出处:http://www.xxling.com/article/3114.aspx

  • 分类: PHP
  • 阅读: (2127)
  • 评论: (4)
砖墙
灵成
mcrypt_encrypt加密时是用0填充的,不是No Padding,所以用openssl_encrypt加密前手动用0填充,然后就可以得到和mcrypt_encrypt一致的结果了,填充代码如下,key和iv都是16位 $str='123456789'; $str_padded = $str; if (strlen($str_padded) % 16) {   $str_padded = str_pad($str_padded,strlen($str_padded) + 16 - strlen($str_padded) % 16, "\0"); }
xiaolingzi 灵成
你说的对,我搞错了,默认是用0来填充的。已经更新文章内容,非常感谢!
呼呼
谢谢博主和楼上的兄弟,学到了。
xiaolingzi 呼呼
不客气,多交流,一起进步。
拍砖 取消
请输入昵称
请输入邮箱
*
 选择评论类型
300字以内  请输入评论内容