Ошибка неправильного размера блока при расшифровке зашифрованного текста AES-256-CBC, зашифрованного openssl_encrypt в PHP
У меня есть модуль PHP, который шифрует электронную почту с aes-256-cbc
с помощью openssl_encrypt
,
Шифротексты, сгенерированные этим модулем, также могут быть расшифрованы этим модулем.
Но если я пытаюсь расшифровать их с помощью реализации aes-256-cbc в Go с тем же IV и Key, я получаю ошибку с ошибкой размера блока. Предполагается, что размер блока кратен 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 ответ
Я вижу несколько проблем с вашим кодом:
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;
Идти:
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>