php实现3DES加密算法,工作模式CBC,填充模式PKCS7 Padding

概述:

    最近工作中需要通过接口交换一些数据,数据采用sha-1,3DES加密,sha-1加密方式可以直接使用php的函数sha1()完成,php对3DES加密方式的支持并不完全,部分功能需要自己实现。

    对方提供的接口说明了3DES算法所用参数,包括:

    1.工作模式:CBC

    2.填充方式:PKCS7 Padding

原理:

    3DES:

    对于3DES加密算法,我个人也不是太了解,因为这涉及到密码学的范畴,我并没有详细的研究这个算法本身。所以这里我也只能写一点简单的说明和理解,对不住各位看官了。大家有兴趣的话可以自己研究,关于3DES和DES的详细细节,请参见http://csrc.nist.gov/publications/fips/fips46-3/fips46-3.pdf

    wikipedia的关于3DES算法的描述是“3DES(Triple DES),是三重数据加密算法(TDES,Triple Data Encryption Algorithm)的通称。它相当于是对每个数据块应用三次DES加密算法。”

    这就要提到DES算法,DES算法是一种块密码加密算法,将明文分成多个长64bit的组,并使用一个64bit的密钥,对每个明文块使用同样的算法从而获得同样长度的密文块。

加密算法流程如下:

    

    首先将64bit的明文数据分为左(L0),右(R0)两部分,然后R0经过费斯妥函数处理,将结果与L0做异或操作。异或的结果作为R1,R0作为L1,这样算是完成一个“回次”(round)。在经过16个回次以后(最有一个回次完成异或后不交换位置),再将两个32bit的块合并,这样就得到了这个明文块对应的密文块。

    PS:限于篇幅和本人的能力,这里就不具体展开费斯妥函数的处理过程了,有兴趣的朋友可以看一下上面提供的详细内容的链接。

    因为3DES算法是对一个数据进行三次DES算法,所以有3个64bit密钥k1、k2、k3。

    加密算法为:

密文 = EK3(DK2(EK1(明文)))

    也就是说,使用K1为密钥进行DES加密,再用K2为密钥进行DES“解密”,最后以K3进行DES加密。

    而解密则为其反过程:

明文 = DK1(EK2(DK3(密文)))

    即以K3解密,以K2“加密”,最后以K1解密。

    如果k1=k2=k3,则此时3DES算法兼容DES算法,结果一致。

    CBC:    

    下面我们来看工作模式。上文我们简单描述了DES算法的流程,不过那是针对一个64bit的明文块的加密操作,结果是这个明文块对应的64bit的密文块。那么多个明文块加密是怎么衔接的呢,这就要依靠块密码的工作模式了。

    密码学中,块密码的工作模式允许使用同一个块密码密钥对多于一个一块的数据进行加密,并保证其安全性。常见的工作模式包括:ECB,CBC,OFB和CFB等。这里我们就只简单了解一下CBC的工作模式。关于更多工作模式,可以参考wikipedia 的块密码的工作模式

    在CBC模式中,每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。第一个明文块没有前一个密文块,所以我们使用一组初始化向量(IV)代替。CBC模式的加密流程如下图所示:

CBC工作模式

    PKCS7:

    正如上文所述,明文会被以64bit为一组划分为若干租进行加密,每一组使用DES算法由明文获得密文。可是待加密的明文并不能保证总是可以正好分成若干个64bit的组,最后一组正好满64bit的可能性往往是比较低的,那么为了加密方便,应该怎么办呢,Padding就是用来解决这个问题的。

    我们这里简单了解一下Byte Padding中的Zero Padding、PKCS7 Padding 和 PKCS5 Padding。更多信息请参考wikipedia的Padding_(cryptography)

    Zero Padding:所有需要填充的地方都以0填充。 下面的例子是每8byte为一块的数据格式,最后一块只有4byte,所以要填充4byte的\x00。

      ... | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 00 |

    PKCS7 Padding:填充的内容是需要填充的字节数。如果最后一个数据块长度为len,每个块的长度为k,则要填充的内容为:

      01 -- if lth mod k = k-1

      02 -- if lth mod k = k-2

                        .

                        .

                        .

      k k ... k k -- if lth mod k = 0

    需要注意的是,如果最后一个数据块的长度len恰好等于k,则需要在后面再添加一个完整的padding块,kk...kk。下面的例子是每8byte为一块,最后一块有8byte,需要填充8byte的\x08。

  ... | DD DD DD DD DD DD DD DD | DD DD DD DD DD DD DD DD | 08 08 08 08 08 08 08 08 |

    PKCS5 Padding:PKCS5 和 PKCS7 的唯一区别是PKCS5只能用来填充64bit的数据块,除此之外可以混用。

    PHP的mcrypt 默认的填充值为 null ('\0'),java或.NET 默认填充方式为 PKCS7 。如果把java或.NET 填充模式改为 Zeros 即可得到与mcrypt 一致的结果。

实现:

    由于加密算法的参数对方在接口中已经定义,所以我就只能自己实现对对方的兼容,所以自己写了PKCS7 Padding的方法,具体实现如下:

 1 <?php
2 $keyForTDES = "XXXXXXX";//加密所用密钥
3 $defaultIV = "XXXXXXX";//初始化向量IV
4
5 class Cryptogram {
6 /**
7 * 使用3DES加密源数据
8 * @param string $oriSource 源数据
9 * @param string $key 密钥
10 * @param string $defaultIV 加解密向量
11 * @return string $result 密文
12 */
13 public function encryptByTDES($oriSource, $key, $defaultIV){
14 $oriSource = $this->addPKCS7Padding($oriSource);
15 $td = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_CBC, '');
16 mcrypt_generic_init($td, $key, $defaultIV);
17 $result = mcrypt_generic($td, $oriSource);
18 mcrypt_generic_deinit($td);
19 mcrypt_module_close($td);
20 return $result;
21 }
22
23
24 /**
25 * 使用3DES解密密文
26 * @param string $encryptedData 密文
27 * @param string $key 密钥
28 * @param string $defaultIV 加解密向量
29 * @return string $result 解密后的原文
30 */
31 public function decryptByTDES($encryptedData, $key, $defaultIV){
32 $td = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_CBC, '');
33 mcrypt_generic_init($td, $key, $defaultIV);
34 $result = mdecrypt_generic($td, $encryptedData);
35 mcrypt_generic_deinit($td);
36 mcrypt_module_close($td);
37 $result = $this->stripPKSC7Padding($result);
38 return $result;
39 }
40
41
42 /**
43 * 为字符串添加PKCS7 Padding
44 * @param string $source 源字符串
45 */
46 private function addPKCS7Padding($source){
47 $block = mcrypt_get_block_size('tripledes', 'cbc');
48 $pad = $block - (strlen($source) % $block);
49 if ($pad <= $block) {
50 $char = chr($pad);
51 $source .= str_repeat($char, $pad);
52 }
53 return $source;
54 }
55
56
57 /**
58 * 去除字符串末尾的PKCS7 Padding
59 * @param string $source 带有padding字符的字符串
60 */
61 public function stripPKSC7Padding($source){
62 $block = mcrypt_get_block_size('tripledes', 'cbc');
63 $char = substr($source, -1, 1);
64 $num = ord($char);
65 if($num > 8){
66 return $source;
67 }
68 $len = strlen($source);
69 for($i = $len - 1; $i >= $len - $num; $i--){
70 if(ord(substr($source, $i, 1)) != $num){
71 return $source;
72 }
73 }
74 $source = substr($source, 0, -$num);
75 return $source;
76 }
77 }
78 ?>

 

    由于加密密钥和初始化向量IV都已经在文档中定义了,所以我就没有直接生成。对于没有定义IV的同学, 可以使用mcrypt_create_iv函数生成。

    关于如何处理KEY和IV,有一篇文章写得已经很好了,笔者就不赘述了,这里摘录部分,原文详见 高度php博客的http://www.php8848.com/wp/zh/792.html

    3DES加密CBC模式接收的$key是24位,$vi是8位,新手一般高不清楚应该如何处理 加密向量和密钥

    所以如果你的得到的 $key为48位, $vi 为16位, 那么请用 pack()函数来处理他们两个

    $key = pack(‘H48′,$key);结果为24位

    $vi = pack(‘H16′,$vi);结果为8位

    如果你得到的key  为32位 vi为 12位,可以对他们进行如下处理

    $key = base64_decode($key);结果为24位

    $vi = base64_decode($vi);结果为8位

    如果本身就为 24位和8位,那你就带入试一下吧

    PS:笔者的开发中还遇到一个问题,就是使用sha1函数的加密的结果和对方提供的java写的demo的结果不一致,查了一下手册,发现php的sha1()定义为:

    string sha1 ( string $str [, bool $raw_output = false ] ),如果可选的 raw_output 参数被设置为 TRUE,那么 sha1 摘要将以 20 字符长度的原始格式返回,否则返回值是一个 40 字符长度的十六进制数字。

    当我将raw_output 参数设置为true时,和对方的加密结果一致。


总结:

    笔者有个刨根问底的习惯,总希望能够理解事物背后的原理,但是无奈对密码学真的没有什么研究,基础太浅,这篇文章写得比较仓促,内容比较浅,很多问题没有展开,还望大家见谅。

好了,就啰嗦这么多了,希望此文能为需要的人提供帮助。


参考文献:

1.DES,3DES算法 http://csrc.nist.gov/publications/fips/fips46-3/fips46-3.pdf

2.块密码工作模式 http://zh.wikipedia.org/wiki/%E5%9D%97%E5%AF%86%E7%A0%81%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F

3.Padding http://en.wikipedia.org/wiki/Padding_(cryptography)

4.PKCS7 Padding http://tools.ietf.org/html/rfc2315

5.密钥,IV处理 http://www.php8848.com/wp/zh/792.html

 

 

 

 

  

posted on 2012-02-24 11:22  darkbluever  阅读(4329)  评论(0编辑  收藏  举报

导航