Search code examples
pythonphpcryptographyaes

how to decrypt in php an encrypted text by python using AES-256-CCM


im trying to decrypt a chiphertext in PHP that was encrypted with AES-256-CCM using cryptography.hazmat in python what i did in my python code is :

from cryptography.hazmat.primitives.ciphers.aead import AESCCM
from os import urandom
import base64

#Text To Encrypt
plaintext = bytes("message from python", encoding='utf-8')
#AES 256 Key Genrator
key = AESCCM.generate_key(256)
#Genrate Nonce
nonce= urandom(12)
#chipher 
cipher = AESCCM(key, tag_length=8)
#Encryption
ciphertext = cipher.encrypt(nonce, plaintext, None)

then i convert the key , nonce and ciphertext to base64

key_b64 = base64.standard_b64encode(key)
ciphertext_b64 = base64.standard_b64encode(ciphertext)
nonce_b64 = base64.standard_b64encode(nonce)

in my example i got this results

key = b'\xcb\x14\x96{,0(\x15\x86 \xda\xf8\x1b"i|M\xbd\xc5d\xe7\xa6I\xdf\x7f\xe11\xae\xe8\x8a\xb3j'
key_b64 = b'yxSWeywwKBWGINr4GyJpfE29xWTnpknff+ExruiKs2o='

nonce = b'\xc7f\xdc\xe3\xe4\x03>M\x9by\x92\x9d
nonce_b64 = b'x2bc4+QDPk2beZKd'

ciphertext = b'R\x9f\xe6D\\_\xdexC\x82\xf8\x8e\x9b;\x91\xc7OLo\xc2\t/\x8fV>G='
ciphertext_b64 = b'Up/mRFxf3nhDgviOmzuRx09Mb8IJL49WPkc9'

i use the base64 results in my PHP code

<?php
$key_from_python = base64_decode('yxSWeywwKBWGINr4GyJpfE29xWTnpknff+ExruiKs2o=');

$ciphertext_from_python = base64_decode('ooGUzo0YiwKPs9+2wXySYEpdBNfSpyLUHm1M');

$nonce_from_python = base64_decode('Up/x2bc4+QDPk2beZKd');

$cipher = "aes-256-ccm";

if (in_array($cipher, openssl_get_cipher_methods())){
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$decrypted_mesage_from_pythom = 
openssl_decrypt($encrypted_from_python_,$cipher,$key_from_python,$options=0 , $iv, $tag);
echo $decrypted_mesage_from_pythom;
}

its based on an example that i find here http://php.babo.ist/#/en/function.openssl-encrypt.html and i cant find another example the decryption processes dose not return anything
and what really confusing me is :

  1. we didn't use IV to encrypt in python code but the PHP need non-NULL IV ,how to solve that ?
  2. what $tag represent in PHP code and $tag_lenght both in PHP and python(cipher = AESCCM(key, tag_length=8)) ?
  3. if the decryption need nonce how to use it in my PHP code ?

How to get this work? encrypt from python and decrypt the same chiphertext in PHP

Note : i have to use python for encryption and php for decryption and i have to use AES-CCM, the python code is fixed , thank you for your understanding

thank you


Solution

  • There seems to be a bug in the PHP implementation for AES-CCM for a nonce length of 12 bytes (used by the OP) which results in a wrong ciphertext/tag. However, this bug is hidden by a number of flaws in the OP's PHP code. Therefore, these defects have to be fixed first:

    • The Python and PHP implementations differ in that in the Python code the ciphertext and tag are concatenated in that order, whereas in the PHP code the ciphertext and tag are processed separately.
    • The nonce in the Python code corresponds to the IV in the PHP code.
    • In the PHP code the OPENSSL_RAW_DATA flag must be set if the ciphertext is passed as raw data and not Base64 encoded.
    • The values for nonce and ciphertext (+ tag) differ in both codes. However, a correction is pointless because of the chosen 12 bytes nonce in combination with the PHP bug for a 12 bytes nonce, see below. I.e. a prerequisite for a successful test is a nonce size unequal to 12 bytes.

    A PHP implementation that takes these points into account is e.g.

    <?php
    // Data from Python code
    $key_from_python = base64_decode('<Base64 encoded key from Python>');
    $ciphertext_from_python = base64_decode('<Base64 encoded (ciphertext + tag) from Python>');
    $nonce_from_python = base64_decode('<Base64 encoded nonce from Python>');
    $cipher = 'aes-256-ccm';
    
    // Separate ciphertext and tag
    $tagLength = 8;
    $ciphertext = substr($ciphertext_from_python, 0, -$tagLength);
    $tag = substr($ciphertext_from_python, -$tagLength);
    
    // Decrypt
    if (in_array($cipher, openssl_get_cipher_methods())){
        $decrypted_mesage_from_pythom = openssl_decrypt($ciphertext, $cipher, $key_from_python, OPENSSL_RAW_DATA, $nonce_from_python, $tag);
        echo $decrypted_mesage_from_pythom;
    }
    ?>
    

    With this PHP code it's possible to decrypt the data from the Python code as long as the length of the nonce is not equal to 12 bytes.

    Python and PHP implementations allow a nonce with a length of 7 to 13 bytes (both inclusive), s. here for Python. Concerning the issue for a 12 bytes nonce, the following turns out: If in the PHP code the 12 bytes nonce is truncated to 7 bytes by removing the last 5 bytes, the same ciphertext/tag is created. The following PHP code illustrates this for the tag lengths of 8 and 16 bytes (PHP version 7.4.4):

    <?php
    function printCiphertextTag($plaintext, $key, $iv, $taglength){
        $encrypted = openssl_encrypt($plaintext, "aes-256-ccm", $key, OPENSSL_RAW_DATA, $iv, $tag, NULL, $taglength);
        echo sprintf("tag size: %2s, IV size: %2s, IV (hex): %-' 24s, ciphertext (hex): %s, tag (hex): %s\n", $taglength, strlen($iv), bin2hex($iv), bin2hex($encrypted), bin2hex($tag));
    }
    
    $plaintext = 'message from python'; 
    $key = '01234567890123456789012345678901';
    $nonce12 = openssl_random_pseudo_bytes(12);
    $nonce7 = substr($nonce12, 0, 7);
    
    printCiphertextTag($plaintext, $key, $iv = $nonce12, $taglength = 8);
    printCiphertextTag($plaintext, $key, $iv = $nonce7, $taglength = 8);
    
    printCiphertextTag($plaintext, $key, $iv = $nonce12, $taglength = 16);
    printCiphertextTag($plaintext, $key, $iv = $nonce7, $taglength = 16);
    ?> 
    

    This result indicates a bug in the PHP implementation.

    The Python code in contrast generates different ciphertexts/tags for a 12 bytes nonce compared to the PHP code (which is why the (corrected) OP's PHP code that uses a 12 bytes nonce fails). A check with Java/BC with identical parameters produces the same ciphertexts/tags for a 12 bytes nonce as the Python code, which verifies the values of the Python code and again indicates a bug in the PHP implementation.

    EDIT: I've filed an issue here: https://bugs.php.net/bug.php?id=79601. Note: The issue was set to private by the admins, so that it cannot be opened (at least for now) without the appropriate permissions, s. here.