ошибка неправильного размера блока при расшифровке зашифрованного текста AES-256-CBC, зашифрованного openssl_encrypt в PHP - PullRequest
0 голосов
/ 25 ноября 2018

У меня есть модуль PHP, который шифрует электронную почту с помощью aes-256-cbc с использованием openssl_encrypt.

Зашифрованные тексты, сгенерированные этим модулем, также могут быть расшифрованы этим модулем.

Но если я попытаюсь расшифровать их с помощью реализации aes-256-cbc в Go с тем же IV иКлюч, я получаю плохую ошибку размера блока.Предполагается, что размер блока должен быть кратным 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
}

Я пробовал разные вещи, такие как использованиеOPENSSL_RAW_DATA и OPENSSL_ZERO_PADDING в модуле PHP, но абсолютно ничего не работает.

Зашифрованный текст, использованный в приведенной выше программе, был сгенерирован с полем options в openssl_encrypt, установленным на 0.

Я предполагаю, что PHP не дополняет ввод перед шифрованием, что генерирует зашифрованные тексты, которыене кратны 16.

Реализация 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);
        $iv = substr(hash('sha256', self::SECRET_IV), 0, 16);
        if ($action == 'encrypt') {
            $output = openssl_encrypt($string, self::ENCRYPT_METHOD, $key, 0, $iv);
        } else if ($action == 'decrypt') {
            $output = openssl_decrypt(base64_decode($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;

$decrypted_txt = $class_encrypt->decrypt("ZHRodkpCK3R5QXlCMnh3MFdudDh3Zz09");
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;

1 Ответ

0 голосов
/ 26 ноября 2018

Я вижу несколько проблем с вашим кодом:

  • Функция PHP hash() возвращает строки в шестнадцатеричном формате по умолчанию.Вам нужно передать TRUE в качестве третьего аргумента, чтобы включить «сырой» режим.

  • Аналогично нет необходимости в hex.EncodeToString в версии Go.

  • Функции 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;

Go:

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>
...