添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

PHP的实现相当简单,两行代码就行,结果就是偶尔与其他平台不一致,原因就是屏蔽了很多细节。这只是一篇很无聊的笔记,略过。

如下面两行代码,虽然实现了目的,但效果并不理想

$iv = @ mcrypt_create_iv ( mcrypt_get_iv_size ( MCRYPT_RIJNDAEL_128 , MCRYPT_MODE_CBC ) , MCRYPT_RAND ) ;
echo @ base64_encode ( mcrypt_encrypt ( MCRYPT_RIJNDAEL_128 , md5 ( "loveyu.org" ) , "loveyu.org" , MCRYPT_MODE_CBC , $iv ) ) ;

秘钥长度与拓展选择

PHP支持N多的加密算法,而这些算法则来源于Openssl,如果使用函数 openssl_get_cipher_methods() 会得到一份列表,过滤后AES相关的如下:

aes-128-cbc
aes-128-cbc-hmac-sha1
aes-128-cbc-hmac-sha256
aes-128-ccm
aes-128-cfb
aes-128-cfb1
aes-128-cfb8
aes-128-ctr
aes-128-ecb
aes-128-gcm
aes-128-ofb
aes-128-xts
aes-192-cbc
aes-192-ccm
aes-192-cfb
aes-192-cfb1
aes-192-cfb8
aes-192-ctr
aes-192-ecb
aes-192-gcm
aes-192-ofb
aes-256-cbc
aes-256-cbc-hmac-sha1
aes-256-cbc-hmac-sha256
aes-256-ccm
aes-256-cfb
aes-256-cfb1
aes-256-cfb8
aes-256-ctr
aes-256-ecb
aes-256-gcm
aes-256-ofb
aes-256-xts
id-aes128-CCM
id-aes128-GCM
id-aes128-wrap
id-aes192-CCM
id-aes192-GCM
id-aes192-wrap
id-aes256-CCM
id-aes256-GCM
id-aes256-wrap

也就是说,它该有的都有了,问题来了这里使用的是Openssl拓展,而不是mcrypt拓展,原因只有一点,官方对mcrypt_encrypt函数做了如下标记:

Warning: This function has been DEPRECATED as of PHP 7.1.0. Relying on this function is highly discouraged. @see

向量的作用

看看具体的例子: mcrypt_create_iv 为什么要做这样的事情,简单点说就是 安全考虑与保证加密结果的一致性 ,复杂点说详见Wiki 初始向量 ,那么这个向量需要保密么,当然是可选的,毕竟不是秘钥。一个更通俗的解释就是,在加密之前对区块进行初始化(如随机数算法给你一个种子每次就一样了)

那么如果向量不一致能解密么?
可以的,只是会导致解密的数据结果顺序异常,重新组合下就好了,所以保密的作用并不是特别大,实际呢,你试试就知道了。

秘钥长度的理解

AES 128指什么 AES的区块长度固定为128比特(16字节)
AES192 和 AES256类似,区块大了24字节和32字节
怎么加密的呢,一块一块加密,如果一段文本很长很长,就截断为对应长度再加密
单个区块如何加密?这个问题可以看看这里: Link
这样就很好理解秘钥长度是多少了,对应的

  • AES 128 => 16个字符
  • AES 192 => 24个字符
  • AES 256 => 32个字符
  • 如果要说,还有没有其他长度呢,当然有,不细说,没啥作用。

    加密模式有哪些呢

    CBC、ECB、CTR、OCF、CFB 安全性不一样,工作模式不一样,所需要的参数不一样,具体场景具体选择

    CBC 优点:

  • 不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准
  • CBC缺点:

  • 不利于并行计算
  • 需要初始化向量IV
  • 具体内容可以看看: http://www.cnblogs.com/happyhippy/archive/2006/12/23/601353.html

    为什么会有填充算法的存在呢?假设我们执行如下AES加密,得出结果:

    1) aes("loveyu")       = C1955B/EdtfgKc0dWAwGFA==
    2) aes("loveyu123456") = q+VvUfq79k0He8Rf9dEOrA==

    解密的结果是怎么样的呢?如下

    string(16) "loveyu          "   >>>>>>  HEX  6c6f76657975000000000000000000
    string(16) "loveyu123456    "

    结果不算太出人意料,但得到了Mcrypt的默认填充算法,零填充,也能发现这样的算法有很大的局限性。

    填充算法的种类

    ANSI X.923
    … | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 04 |

    ISO 10126 ????
    … | DD DD DD DD DD DD DD DD | DD DD DD DD 81 A6 23 04 |

    PKCS7 与 PKCS5(两者一致除非有64的填充)
    … | DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |

    Zeropadding
    … | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 00 |

    大多数时候,各个不同的平台使用的默认填充算法略有差异,使用起来差别不大,了解了细节就没啥难度了。对于PKCS7和PKCS5,主要在于长度不一样,而AES貌似没有64位的填充。

    差异来自于哪里

    $iv = @ mcrypt_create_iv ( mcrypt_get_iv_size ( MCRYPT_RIJNDAEL_128 , MCRYPT_MODE_CBC ) , MCRYPT_RAND ) ;
    echo @ base64_encode ( mcrypt_encrypt ( MCRYPT_RIJNDAEL_128 , md5 ( "loveyu.org" ) , "loveyu.org" , MCRYPT_MODE_CBC , $iv ) ) ;
  • 使用了随机向量,PHP官方例子就这样写的
  • 使用了AES128的算法
  • 使用了32个长度的秘钥
  • 使用了默认填充算法
  • 会有哪些问题?
  • 你怎么告诉别人你的向量是什么,MCRYPT_RAND 生成的么,还是Base64一下
  • 你怎么解释你的AES128使用了32位的秘钥,大家都是PHP无所谓,如果对方用openssl解密怎么办
  • 加密解密前后的结果不一致,非必现,偶尔有一样
  • 日常使用会有哪些参数是必要?

    AES 256 aes-cbc-256 PKCS7_TEXT MD5(“loveyu”) 1234567890123456

    为什么这里的向量长度是16而不是32?

    echo openssl_cipher_iv_length ( "aes-256-cbc" ) ;
    echo @ mcrypt_get_iv_size ( MCRYPT_RIJNDAEL_256 , MCRYPT_MODE_CBC ) ;

    结果为:16和32,差异来自这里
    由于AES-CBC 总会输出16的整数倍数据,所以需要的向量大小也是16个字节

    算法实现的不同会导致所需的条件不一致,而大量客户端均采用openssl的实现,所以使用16长度的向量

    Aes-128-cbc zeroPadding

    $mode = "aes-128-cbc";
    $key = "1234567890123456";
    $iv = "1234567890123456";
    $padding = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
    $data = "1234567890\0\0\0\0\0\0";
    echo base64_encode(openssl_encrypt($data, $mode, $key, $padding, $iv)), "\n";
    echo @base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv));

    输出结果:

    yOW03u6FmhY2rHTmiy7Ttg==
    yOW03u6FmhY2rHTmiy7Ttg==

    要通过openssl_encrypt实现zeroPadding并不容易,而且还不好转换

    算法实现 nodejs

    const crypto = require ( 'crypto' ) ;
    let mode = "aes-128-cbc" ;
    let key = "1234567890123456" ;
    let iv = "1234567890123456" ;
    let data = "1234567890 \0 \0 \0 \0 \0 \0 " ;
    let cipher = crypto. createCipheriv ( mode , key , iv ) ;
    cipher. setAutoPadding ( false ) ;
    let crypt = cipher. update ( data , 'utf8' , 'base64' ) ;
    crypt += cipher. final ( "base64" ) ;
    console. log ( crypt ) ;

    Output:yOW03u6FmhY2rHTmiy7Ttg==

    当我们使用相同的参数配置时,得到的结果总是可靠的

    Nodejs和PHP的mcrypt一样,会默认对数据进行填充,只是算法略有不同,为 PKCS

    Aes-256-cbc加密的实现

    AES 256 aes-256-cbc PKCS7_TEXT a4055e3e9879951930961af352066068 1234567890123456 ABCDEF0123456789 size:16 ABCDEF012345678 size:15

    PHP openSSL加密结果:

    8xpgNuUhQNdQLsuq5DqjAvws3S9hWNKV24kZij3+aSA= size:32
    L5zn/8HqNmengnanZ9qQ4w== size:16
    const crypto = require ( 'crypto' ) ;
    let mode = "aes-256-cbc" ;
    let key = "a4055e3e9879951930961af352066068" ;
    let iv = "1234567890123456" ;
    // let data = "ABCDEF0123456789";
    let data = "ABCDEF012345678" ;
    let cipher = crypto. createCipheriv ( mode , key , iv ) ;
    let crypt = cipher. update ( data , 'utf8' , 'base64' ) ;
    crypt += cipher. final ( "base64" ) ;
    console. log ( crypt ) ;

    NodeJS 加密结果:

    8xpgNuUhQNdQLsuq5DqjAvws3S9hWNKV24kZij3+aSA= size:32
    L5zn/8HqNmengnanZ9qQ4w== size:16

    Mcrypt此时无结果,并扔给你一个警告:
    mcrypt_encrypt(): Received initialization vector of size 16, but size 32 is required for this encryption mode.

    Aes-256-cbc解密的实现

    PHP版本

    $mode = "aes-256-cbc" ;
    $key = "a4055e3e9879951930961af352066068" ;
    $iv = "1234567890123456" ;
    $padding = PKCS7_TEXT ;
    $data = "8xpgNuUhQNdQLsuq5DqjAvws3S9hWNKV24kZij3+aSA=" ;
    var_dump ( openssl_decrypt ( base64_decode ( $data ) , $mode , $key , $padding , $iv ) ) ;

    输出结果:string(16) “ABCDEF0123456789”

    NodeJS版本

    const crypto = require ( 'crypto' ) ;
    let mode = "aes-256-cbc" ;
    let key = "a4055e3e9879951930961af352066068" ;
    let iv = "1234567890123456" ;
    let data = "L5zn/8HqNmengnanZ9qQ4w==" ;
    let decipher = crypto. createDecipheriv ( mode , key , iv ) ;
    let decode = decipher. update ( data , 'base64' , 'utf8' ) ;
    decode += decipher. final ( "utf8" ) ;
    console. log ( decode , "size:" + decode. length ) ;

    输出结果:ABCDEF012345678 size:15

    到这里,大部分实现都处理了,有很多细枝末节不兼容也正常,如果使用相同的实现和相同的算法能解决很多问题,而大部分时候标准的Openssl兼容性是极好的。

    2条评论在“PHP AES算法使用细节”