最近客户端和服务端加密通信遇到一个困难,我发现同样的KEY和IV下,C++加密结果和PHP加密结果不同,同样也导致服务端返回的加密数据无法被C++客户端解密
以下是C++实现部分void Cryptology::OpenSSLAESEncodeByMapleArrayForPHP(MapleByteArray &source, MapleByteArray &key, MapleByteArray &iv, MapleByteArray &res)
{ //16B对齐
int buqi_len = ((source.GetLength() + 15) / 16) * 16;
for (int i = 0; i < buqi_len; i++)
{
if (i < source.GetLength())
{
}
else
{
source.AddByte((char)0x00);
}
}
////////
EVP_CIPHER_CTX en;
EVP_CIPHER_CTX_init(&en);
EVP_EncryptInit_ex(&en, EVP_aes_256_cbc(), NULL, key.data_ptr(), iv.data_ptr()); int c_len = source.GetLength() + AES_BLOCK_SIZE, f_len = 0;
unsigned char *ciphertext = (unsigned char *)malloc(c_len);
ZeroMemory(ciphertext, c_len); EVP_EncryptInit_ex(&en, NULL, NULL, NULL, NULL);
EVP_EncryptUpdate(&en, ciphertext, &c_len, (unsigned char *)source.data_ptr(), source.GetLength());
EVP_EncryptFinal_ex(&en, ciphertext + c_len, &f_len);

EVP_CIPHER_CTX_cleanup(&en);
 
res.fromByte((BYTE*)ciphertext, c_len);
ZeroMemory(ciphertext, c_len);

free(ciphertext);
}以下是PHP实现部分:
//Encrtption.php
<?phpnamespace phpaes;interface Encryption {    /**
     * Encrypt $data
     *
     * @param string $data
     *
     * @return string
     */
    public function encrypt($data);    /**
     * Decrypt $ciphertext
     *
     * @param string $ciphertext
     *
     * @return string
     */
    public function decrypt($ciphertext);}//AES.PHP
<?phpnamespace phpaes;abstract class AES implements Encryption {    /** @var string */
    private $key;    /** @var string */
    private $iv;    /**
     * @param string $iv
     * @throws \InvalidArgumentException
     */
    public function setIv($iv) {
        if (!is_string($iv)) {
            throw new \InvalidArgumentException("IV must be a string");
        }
        if (strlen($iv) != 16) {
            throw new \InvalidArgumentException("IV length must be 16 bytes");
        }
        $this->iv = $iv;
    }    /**
     * @param string $key
     * @throws \InvalidArgumentException
     */
    public function setKey($key) {
        if (!is_string($key)) {
            throw new \InvalidArgumentException("Key must be a string");
        }
        if (!in_array(strlen($key), array(16,24,32))) {
            throw new \InvalidArgumentException("Key length must be 16, 24, or 32 bytes");
        }
        $this->key = $key;
    }    /**
     * @throws \LogicException
     * @return string
     */
    public function getIv() {
        if (!isset($this->iv)) {
            throw new \LogicException('The iv is not set, call setIv() prior to usage');
        }
        return $this->iv;
    }    /**
     * @throws \LogicException
     * @return string
     */
    public function getKey() {
        if (!isset($this->key)) {
            throw new \LogicException('The key is not set, call setKey() prior to usage');
        }
        return $this->key;
    }}
//AES_CBC_OpenSSL.PHP<?phpnamespace phpaes;/**
 * You'll note that this class doesn't do it's own padding.
 *
 * openssl uses pkcs#7 padding by default and as I generally recommend
 * that, I've not provided an option to do any other kind of padding.
 *
 * There isn't anything that stops you, of course, from pre-padding
 * your data with whatever method you like before pushing it into
 * these methods.
 */
class AES_CBC_OpenSSL extends AES {    /** @var string */
    private $aesmode = '';    private $rawoption;    public function setKey($key) {
        parent::setKey($key);
        // Transform the key into the bit size and set the openssl mode string
        $this->aesmode = 'aes-256-cbc';
        // in 5.3 the 3rd option to these calls was a boolean for raw/not raw, but became a bitmask in 5.4
        // pick the right variant like this:
        $this->rawoption =  OPENSSL_ZERO_PADDING;
    }    /** @inheritdoc */
    public function encrypt($text) {
        return openssl_encrypt($text, $this->aesmode, $this->getKey(), $this->rawoption, $this->getIv());
    }    /** @inheritdoc */
    public function decrypt($cipherText) {
        return openssl_decrypt($cipherText, $this->aesmode, $this->getKey(), $this->rawoption, $this->getIv());
    }}//test.php
<?php//require __DIR__ . '/../vendor/autoload.php';
//require __DIR__ . '/shared-data.php';require_once("D:\code\phpaes-master\phpaes-master\src\Encryption.php");
require_once("D:\code\phpaes-master\phpaes-master\src\AES.php");
require_once("D:\code\phpaes-master\phpaes-master\src\AES_CBC_OpenSSL.php");
require_once("D:\code\phpaes-master\phpaes-master\src\Util.php"); 
$aescbc = new phpaes\AES_CBC_OpenSSL();
$util   = new phpaes\Util();
$key = "12345678901234567890123456789012";
$iv = "1234567890123456";
$plainText = "1234567890123456";$aescbc->setKey($key);
$aescbc->setIv($iv);
    $cipherText  = $aescbc->encrypt($plainText);
    $decodedText = $aescbc->decrypt($cipherText);
    echo "-------------------------------------------------------<br>";
    echo "               Key: $key<br>";
    echo "         Plaintext: $plainText<br>";
    echo " Plaintext (bytes): ". bin2hex($plainText) ."<br>";
    echo "Ciphertext (bytes): ". bin2hex($cipherText) ."<br>";
    echo "  Ciphertext (len): ". strlen($cipherText) ."<br>";
    echo "     Decryptedtext: $decodedText<br>";测试时,为了避免可能是补齐导致的问题,所以要加密的字符串刚好是16字节不多不少,这时候发现如果Padding为OPENSSL_ZERO_PADDING时,PHP结果(HEX)为6535526e6e6c4a6b763451476e47686b4d77667667413d3d (24字节)
而当Padding为OPENSSL_RAW_DATA时,结果为7b94679e5264bf84069c68643307ef800a9e195e288c42965e89921500425701(32字节)在C++下,我没有试pkcs#7填充,而是用补0的方式补齐,结果为7b94679e5264bf84069c68643307ef80(16字节)比较发现,C++加密结果的16字节和PHP的OPENSSL_RAW_DATA补齐时前16个字节完全吻合,但是PHP不知道为什么后面又多了16字节,整个加密结果是32字节,而OPENSSL_ZERO_PADDING下加密结果就完全不一致了,而我测试时的$plainText = "1234567890123456"; 一共刚好16个字符,按理来说没有补齐的需要,十分费解为什么会出现这种问题,希望大大帮忙解答一下

解决方案 »

  1.   

    这里是一个PHP的AES加密和解密,之前和他们做JAVA的对接程序用到了
    function encrypt($str, $key) { $block = mcrypt_get_block_size('des', 'ecb');
    $pad = $block - (strlen($str) % $block);
    $str .= str_repeat(chr($pad), $pad);
    return base64_encode(mcrypt_encrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_CBC, IV));
    } function decrypt($str, $key) {
    $str = base64_decode($str);
    $str = mcrypt_decrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_CBC, IV);
    $block = mcrypt_get_block_size('des', 'ecb');
    $pad = ord($str[($len = strlen($str)) - 1]);
    return substr($str, 0, strlen($str) - $pad);
    }