解密由openssl_encrypt在PHP中加密的AES-256-CBC密文时出现错误的块大小错误

时间:2018-11-25 17:12:47

标签: php go encryption aes

我有一个PHP模块,该模块使用aes-256-cbcopenssl_encrypt加密电子邮件。

此模块生成的密文也可以由该模块解密。

但是,如果我尝试在Go中使用相同的IV和Key通过aes-256-cbc的实现对它们进行解密,则会出现严重的块大小错误。块大小应该为16的倍数,但PHP生成的密文不是16的倍数。

这是代码

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/sha256"
    "encoding/base64"
    "encoding/hex"
    "fmt"
)

var (
    IV  = []byte("fg3Dk54f4340fKF2JTC9")
    KEY = []byte("13GsJd6076v69^f4(fdB")
)

func main() {

    h := sha256.New()
    h.Write(KEY)
    KEY = []byte(hex.EncodeToString(h.Sum(nil))[:32])

    h.Reset()
    h.Write(IV)
    IV = []byte(hex.EncodeToString(h.Sum(nil))[:16])

   fmt.Println("Key", string(KEY))
   fmt.Println("IV", string(IV))

   // This ciphertext was generated by the PHP module
   de := "ZHRodkpCK3R5QXlCMnh3MFdudDh3Zz09"

   q, err := decrypt(KEY, IV, []byte(de))

   fmt.Println(string(q), err)
}

// Returns slice of the original data without padding.
func pkcs7Unpad(data []byte, blocklen int) ([]byte, error) {
    if blocklen <= 0 {
        return nil, fmt.Errorf("invalid blocklen %d", blocklen)
    }
    if len(data)%blocklen != 0 || len(data) == 0 {
        return nil, fmt.Errorf("invalid data len %d", len(data))
    }
    padlen := int(data[len(data)-1])
    if padlen > blocklen || padlen == 0 {
        return nil, fmt.Errorf("invalid padding")
    }
   // check padding
   pad := data[len(data)-padlen:]
   for i := 0; i < padlen; i++ {
        if pad[i] != byte(padlen) {
            return nil, fmt.Errorf("invalid padding")
        }
   }

   return data[:len(data)-padlen], nil
}

func decrypt(key, iv, data []byte) ([]byte, error) {
    var err error
    data, err = base64.StdEncoding.DecodeString(string(data))
    if err != nil {
        return nil, err
    }

    if len(data) == 0 || len(data)%aes.BlockSize != 0 {
        return nil, fmt.Errorf("bad blocksize(%v), aes.BlockSize = %v\n", len(data), aes.BlockSize)
    }
    c, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    cbc := cipher.NewCBCDecrypter(c, iv)
    cbc.CryptBlocks(data[aes.BlockSize:], data[aes.BlockSize:])
    out, err := pkcs7Unpad(data[aes.BlockSize:], aes.BlockSize)
    if err != nil {
        return out, err
    }
    return out, nil
}

我尝试了不同的操作,例如在PHP模块中使用OPENSSL_RAW_DATAOPENSSL_ZERO_PADDING,但是绝对没有任何效果。

以上程序中使用的密文是在options的{​​{1}}字段设置为0的情况下生成的。

我的猜测是,PHP在加密输入之前不会填充输入,从而生成的密文不是16的倍数。

PHP实现

openssl_encrypt

1 个答案:

答案 0 :(得分:0)

我发现您的代码存在一些问题:

  • PHP hash()函数默认返回十六进制编码的字符串。您需要传递TRUE作为第三个参数才能启用“原始”模式。

  • 类似地,无需在Go版本中hex.EncodeToString

  • 默认情况下,PHP openssl_encrypt()openssl_decrypt()函数可用于Base64编码的字符串。因此,无需base64_decode

以下是固定版本:

PHP:

<?php
class MBM_Encrypt_Decrypt {
    const ENCRYPT_METHOD = 'AES-256-CBC'; // type of encryption
    const SECRET_KEY = '13GsJd6076v69^f4(fdB'; // secret key
    const SECRET_IV = 'fg3Dk54f4340fKF2JTC9'; // secret iv

    public function encrypt($string) {
        return $this->encrypt_decrypt('encrypt', $string);
    }

    public function decrypt($string) {
        return $this->encrypt_decrypt('decrypt', $string);
    }
    private function encrypt_decrypt($action, $string)
    {
        $key = hash('sha256', self::SECRET_KEY, true);
        $iv = substr(hash('sha256', self::SECRET_IV, true), 0, 16);
        if ($action == 'encrypt') {
            $output = openssl_encrypt($string, self::ENCRYPT_METHOD, $key, 0, $iv);
        } else if ($action == 'decrypt') {
            $output = openssl_decrypt($string, self::ENCRYPT_METHOD, $key, 0, $iv);
        }
        $output = (!empty($output)) ? $output : false;
        return $output;
    }
}


$class_encrypt = new MBM_Encrypt_Decrypt();

$plain_txt = "xyz@abc.com";
echo 'Plain Text: ' . $plain_txt . PHP_EOL;

$encrypted_txt = $class_encrypt->encrypt($plain_txt);
echo 'Ciphertext: ' . $encrypted_txt . PHP_EOL;

$decrypted_txt = $class_encrypt->decrypt($encrypted_txt);
echo 'Decrypted Text: ' . $decrypted_txt . PHP_EOL;

if ($plain_txt === $decrypted_txt) echo 'SUCCESS' . PHP_EOL;
else echo 'FAILED' . PHP_EOL;

echo PHP_EOL . 'Length of Plain Text: ' . strlen($plain_txt);
echo PHP_EOL . 'Length of Encrypted Text: ' . strlen($encrypted_txt). PHP_EOL;

开始:

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/sha256"
    "encoding/base64"
    "encoding/hex"
    "fmt"
)

var (
    IV  = []byte("fg3Dk54f4340fKF2JTC9")
    KEY = []byte("13GsJd6076v69^f4(fdB")
)

func main() {

    h := sha256.New()
    h.Write(KEY)
    KEY = h.Sum(nil)

    h.Reset()
    h.Write(IV)
    IV = h.Sum(nil)[:16]

   fmt.Println("Key", hex.EncodeToString(KEY))
   fmt.Println("IV", hex.EncodeToString(IV))

   // This ciphertext was generated by the PHP module
   de := "rDAnykzTorR5/SgpdD7slA=="
   q, err := decrypt(KEY, IV, de)

   fmt.Println(string(q), err)
}

// Returns slice of the original data without padding.
func pkcs7Unpad(data []byte, blocklen int) ([]byte, error) {
    if blocklen <= 0 {
        return nil, fmt.Errorf("invalid blocklen %d", blocklen)
    }
    if len(data)%blocklen != 0 || len(data) == 0 {
        return nil, fmt.Errorf("invalid data len %d", len(data))
    }
    padlen := int(data[len(data)-1])
    if padlen > blocklen || padlen == 0 {
        return nil, fmt.Errorf("invalid padding")
    }
   // check padding
   pad := data[len(data)-padlen:]
   for i := 0; i < padlen; i++ {
        if pad[i] != byte(padlen) {
            return nil, fmt.Errorf("invalid padding")
        }
   }

   return data[:len(data)-padlen], nil
}

func decrypt(key []byte, iv []byte, encrypted string) ([]byte, error) {
    data, err := base64.StdEncoding.DecodeString(encrypted)
    if err != nil {
        return nil, err
    }

    if len(data) == 0 || len(data)%aes.BlockSize != 0 {
        return nil, fmt.Errorf("bad blocksize(%v), aes.BlockSize = %v\n", len(data), aes.BlockSize)
    }
    c, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    cbc := cipher.NewCBCDecrypter(c, iv)
    cbc.CryptBlocks(data, data)
    out, err := pkcs7Unpad(data, aes.BlockSize)
    if err != nil {
        return out, err
    }
    return out, nil
}

输出:

$ php test.php
Plain Text: xyz@abc.com
Ciphertext: rDAnykzTorR5/SgpdD7slA==
Decrypted Text: xyz@abc.com
SUCCESS

Length of Plain Text: 11
Length of Encrypted Text: 24

$ go test
Key 45ede7f4300fcc407d734020f12c8176463e7d493aa0395cdfa32e31ff914b0a
IV 9f79430dfdd761b3ed128bc38bfeadc5
xyz@abc.com <nil>