Search code examples
phpaescbc-modeecb

AES 256 ECB decyption with valid key returns strange/characters


I am trying to decrypt in PHP using functions openssl_decrypt.

The problem is that part of the string that is returned is correct but part of is gibberish

`@M{-Y  f{5678","token":null}`

Any kind of help will be appreciated

First I though it was an encoding issue so I tried to changed the encoding using this function but no luck

mb_convert_encoding($data,'UTF-8');

Here is the simple function which I am using

$secret="#oc*Zd'&'&%rez`&;957.u1c:(|'%c81";


$data="SsxrFLoAWTPP7t8AR1/QFXSkZF6Xl2DXGV8Ay90rXk1sgwN46CmSmWsBqTvhbeUT";

$decrypted=openssl_decrypt($data,'aes-256-cbc',$secret);

var_dump($decrypted);

this should be the expected output

`{"reg_no":"UP2345678","token":null}

Solution

  • The CBC mode requires an IV. The IV of the encryption must be used for decryption. In the posted example no IV is passed in the openssl_decrypt-call (which corresponds to an IV with 0-values) and therefore the first block (1 block = 16 Bytes) is decrypted incorrectly.

    In principle, the IV can be reconstructed if key, plaintext and ciphertext are known: The first block of the ciphertext is decrypted (without IV) and the result is XORed with the first block of the plaintext. This results in the following IV for the data in the posted example (as hexadecimal string): 3B297D2864244336363339332B2D2826. Using this IV the output of

    $secret = "#oc*Zd'&'&%rez`&;957.u1c:(|'%c81";
    $iv = hex2bin('3B297D2864244336363339332B2D2826');
    $data = "SsxrFLoAWTPP7t8AR1/QFXSkZF6Xl2DXGV8Ay90rXk1sgwN46CmSmWsBqTvhbeUT";
    $decrypted = openssl_decrypt($data, 'aes-256-cbc',$secret, 0, $iv);
    print('Decrypted data: '.$decrypted);
    

    corresponds to the expected result:

    {"reg_no":"UP2345678","token":null}
    

    EDIT:

    Although I think it's clear, I'd like to mention that the way described below is of course not the regular way how the IV is determined for decryption (that wouldn't work at all, because the plaintext is not known). Usually the IV used for encryption is simply sent to the recipient together with the ciphertext. This is possible because the IV does not have to be kept secret. I.e. in the posted example the IV is missing for some reason, although it should actually be present.

    Anyway, the IV can also be determined in the following way, using plaintext, ciphertext and key: In the description of the CBC-mode it can be seen that at the beginning of the encryption the first block of the plaintext and the IV are XOR-ed and the result is encrypted afterwards. The IV can therefore be determined by first decrypting this first encrypted block and then XOR-ing the result with the plaintext. The corresponding PHP-code is:

    // Step 1: Decrypt the first block of the ciphertext (no IV is used which is equivalent to a 0-IV)  
    $ciphertext = base64_decode('SsxrFLoAWTPP7t8AR1/QFXSkZF6Xl2DXGV8Ay90rXk1sgwN46CmSmWsBqTvhbeUT');
    $ciphertextFirstBlock = substr($ciphertext, 0, 16);                                                                               // First block / 16 Byte of encrypted data
    $decryptedFirstBlock = openssl_decrypt($ciphertextFirstBlock, 'aes-256-cbc', $secret,  OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);  // First block / 16 Byte of decrypted data
    print('Decrypted first block: '.bin2hex($decryptedFirstBlock)."\n");
    
    // Step 2: XOR the result with the first block of the plaintext
    $plaintext = '{"reg_no":"UP2345678","token":null}';
    $plaintextFirstBlock = substr($plaintext, 0 , 16);                                                                                // First block / 16 Byte of plaintext
    $ivReconstructed = $decryptedFirstBlock ^ $plaintextFirstBlock;
    print('Reconstructed IV: '.bin2hex($ivReconstructed)."\n");