Search code examples
pythonphpencryptionaes

Python AES CTR to PHP


I try to make this AES CTR encryption (that work fine in python) in php

working python

from Crypto.Cipher import AES
import Crypto.Util.Counter
from Crypto.Util import Counter
key = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
encryptkey = bytes(key)
ctr = Counter.new(128)
crypto = AES.new(encryptkey, AES.MODE_CTR, counter=ctr)
text = "E5ZA,K2JV,PA01,J1W3,386S,AGVZ,9O9T,F640,FR20,40LX,D443,1913,031V"
bytetext = bytes(text,'utf-8')
encryptedtext = crypto.encrypt(bytetext)
encryptedtext = encryptedtext.hex()
print(encryptedtext)

my php try

<?php
function strToHex($string){
    $hex='';
    for ($i=0; $i < strlen($string); $i++){
        $hex .= dechex(ord($string[$i]));
    }
    return $hex;
}

$bytes = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
$lkey= implode(array_map("chr", $bytes));
$method = "AES-256-CTR";
$password = $lkey;
$data = array("E5ZA", "K2JV", "PA01", "J1W3", "386S", "AGVZ", "9O9T", "F640", "FR20", "40LX", "D443", "1913", "031V");
$string= implode(array_map("chr", $data));
$valid = openssl_encrypt ($string, $method, $password);
echo strToHex($valid)
?>

the error and wrong result I get

Warning: openssl_encrypt(): Using an empty Initialization Vector (iv) is potentially insecure and not recommended in /test.php on line 16
387041417471684a6c74437032356f5477673d3d

the response I expect:

b5682cef66f2adaff0dacb7078f31a773feb86f28614b5ee24e9ee634200a8d6eb177a151eb55003c6cc81b3e9cb6d1a1673a2881ec194370af242d9f1fd5818

Thanks in advance for your help


Solution

  • Both codes give different results for the following reasons:

    1. In the Python code, the counter is assigned a size of 16 bytes. The initial value of the counter is 1 (by default). As already mentioned in the other answer [1], the PHP code lacks the IV. To get the same result in both codes, the same IV must be used in each case.
    2. In the Python code the data are separated by commas. Therefore the comma must be used as delimiter when concatenating in the PHP code.
    3. In the PHP code, the data (with regard to the subsequent hexadecimal encoding) must not be returned Base64 encoded, but as raw data, i.e. the flag OPENSSL_RAW_DATA must be set.
    4. When converting to a hexadecimal string, each value must be output with two digits, i.e. with a leading 0 if necessary.

    The following PHP code contains the necessary changes and therefore gives the same result as the Python code:

    function strToHex($string){
        $hex='';
        for ($i=0; $i < strlen($string); $i++){
            $hex .= sprintf("%02s",dechex(ord($string[$i])));                        // 4. formatting                                                   
        }
        return $hex;
    }
    
    $bytes = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
    $lkey= implode(array_map("chr", $bytes));
    $method = "AES-256-CTR";
    $password = $lkey;
    $data = array("E5ZA", "K2JV", "PA01", "J1W3", "386S", "AGVZ", "9O9T", "F640", "FR20", "40LX", "D443", "1913", "031V");
    $string= implode(",", $data);                                                    // 2. delimiter
    $iv = hex2bin("00000000000000000000000000000001");                               // 1. right IV
    $valid = openssl_encrypt ($string, $method, $password, OPENSSL_RAW_DATA, $iv);   // 3. raw data 
    echo "Result:             " . strToHex($valid) . "\n";
    echo "Result from Python: " . "b5682cef66f2adaff0dacb7078f31a773feb86f28614b5ee24e9ee634200a8d6eb177a151eb55003c6cc81b3e9cb6d1a1673a2881ec194370af242d9f1fd5818" . "\n";
    

    By the way, instead of strToHex the built-in function bin2hex can be used.

    Note: For CTR the reusing of an IV under the same key destroys security [2]. Thus, with the current implementation, a new key must be used for each encryption (since a fix IV is used). Another approach is to split the IV into a nonce and a counter, with the nonce being generated randomly for each encryption [3][4].