php / php-src

The PHP Interpreter
https://www.php.net
Other
38.23k stars 7.75k forks source link

openssl_seal()/_open() is not able to handle gcm cipers, e.g. aes-256-gcm #7737

Open rotdrop opened 2 years ago

rotdrop commented 2 years ago

Description

The following code:

<?php

const NUM_KEYS = 2;

for ($i = 0; $i < NUM_KEYS; $i++) {
  $keys[] = $key = openssl_pkey_new();
  $details = openssl_pkey_get_details($key);
  $pubKeys[] = $details['key'];
  $privKeys[] = openssl_pkey_get_private($key);
}

$data = 'Test Data String';

$ciphers = [
  'aes-256-ctr',
  'aes-256-gcm',
];

foreach ($ciphers as $cipherAlgo) {

  echo "*** TESTING $cipherAlgo ***" . PHP_EOL . PHP_EOL;
  $iv = \random_bytes(openssl_cipher_iv_length($cipherAlgo));
  $result = openssl_seal($data, $sealedData, $sealedKeys, $pubKeys, $cipherAlgo, $iv);

  echo "DATA  :   " . strlen($data) . ' ' . $data . PHP_EOL;
  echo "IV-LEN:   " . openssl_cipher_iv_length($cipherAlgo) . PHP_EOL;
  echo "IV    :   " . bin2hex($iv) . PHP_EOL;
  echo "ENC DATA: " . strlen($sealedData) . ' ' . bin2hex($sealedData) . PHP_EOL;
  echo "RESULT:   " . ($result ? 'true' : 'false') . PHP_EOL;
  echo PHP_EOL;

  // Try decrypt
  foreach ($keys as $i => $key) {
    $decrypted = null; // ;)
    $result = openssl_open($sealedData, $decrypted, $sealedKeys[$i], $key, $cipherAlgo, $iv);
    echo "OPEN:    " . $decrypted . PHP_EOL;
    echo "RESULT:  " . ($result ? 'true' : 'false') . PHP_EOL;

    $result = openssl_private_decrypt($sealedKeys[$i], $unsealedKey, $key);
    echo "UNSEAL:  " . bin2hex($unsealedKey) . PHP_EOL;
    echo "RESULT:  " . ($result ? 'true' : 'false') . PHP_EOL;
    echo "DECRYPT: " . openssl_decrypt($sealedData, $cipherAlgo, $unsealedKey, OPENSSL_RAW_DATA, $iv) . PHP_EOL;
  }

  echo PHP_EOL;
}

Resulted in this output:

$ ./openssl-seal-test.php
*** TESTING aes-256-ctr ***

DATA  :   16 Test Data String
IV-LEN:   16
IV    :   37ff67041864b987b8ec2dcd0879f55e
ENC DATA: 16 2a748aa6ae176caf59cf22868304d65a
RESULT:   true

OPEN:    Test Data String
RESULT:  true
UNSEAL:  8745e00bdf3d88bd1caa8ec0150a9b369eade92e57fc2d26ae283f224c66af14
RESULT:  true
DECRYPT: Test Data String
OPEN:    Test Data String
RESULT:  true
UNSEAL:  8745e00bdf3d88bd1caa8ec0150a9b369eade92e57fc2d26ae283f224c66af14
RESULT:  true
DECRYPT: Test Data String

*** TESTING aes-256-gcm ***

DATA  :   16 Test Data String
IV-LEN:   12
IV    :   033b1589f62bebbfffa3adda
ENC DATA: 16 a531f88fc51e373235c928cae1eb7c29
RESULT:   true

OPEN:    
RESULT:  false
UNSEAL:  600cf6e76cf4ae9d48a881e0649af4682aa9065e63bec515a6b8c9678e398d36
RESULT:  true
DECRYPT: 
OPEN:    
RESULT:  false
UNSEAL:  600cf6e76cf4ae9d48a881e0649af4682aa9065e63bec515a6b8c9678e398d36
RESULT:  true
DECRYPT: 

But I expected this output instead:

[...]
*** TESTING aes-256-gcm ***

DATA  :   16 Test Data String
IV-LEN:   12
IV    :   033b1589f62bebbfffa3adda
ENC DATA: 16 a531f88fc51e373235c928cae1eb7c29
RESULT:   true

OPEN:    Test Data String
RESULT:  true
UNSEAL:  600cf6e76cf4ae9d48a881e0649af4682aa9065e63bec515a6b8c9678e398d36
RESULT:  false
DECRYPT: 
OPEN:    Test Data String
RESULT:  true
UNSEAL:  600cf6e76cf4ae9d48a881e0649af4682aa9065e63bec515a6b8c9678e398d36
RESULT:  false
DECRYPT: 

The point is that the gcm-cipher need the authentication tag for decrypting the data. This however does not seem to be saved by the openssl_seal() function. I have also placed a question in the OpenSSL issue tracker: https://github.com/openssl/openssl/issues/17235

PHP Version

PHP 8.0.13 (cli) (built: Nov 27 2021 17:17:19) ( ZTS )

Operating System

Gentoo Linux 5.15.6, but should not matter

cmb69 commented 2 years ago

@bukka, could you please clarify whether this is a bug, or a feature request, or a documentation issue?

rotdrop commented 2 years ago

Well, AFAIK PHP's openssl_seal() is not able to use the gcm-ciphers. So probably

There is also an attempt to use openssl_seal() with aes-256-gcm within the Nextcloud-project: https://github.com/nextcloud/server/pull/25551

bukka commented 2 years ago

I don't think there's any issue in OpenSSL so you can probably close that linked ticket in OpenSSL. This is just a missing functionality in PHP and it's really a feature request. Basically EVP_Seal* are just wrappers around EVP_Encrypt* that allows encrypting the key with the supplied pkey. What PHP doesn't do is to provide a way to return a tag. Although you could probably write your version of sealing in PHP to give you the same functionality ( you might get an idea when you look to https://github.com/openssl/openssl/blob/defe51c178e3dc9f07514c179121021fd78691b4/crypto/evp/p_seal.c ). That said we could possibly add extra parameters to openssl_seal to return tag and also extend openssl_open accordingly. So that's why this should be a feature request.

rotdrop commented 2 years ago

So then. Kind of a missing feature.

You are probably right that an extra parameter string &$tag or so is more in the spirit of the current interface than simply appending the tag to the encrypted keys.

I am still tempted to interpret the current state as a documentation bug. It is not so clear from reading the manual that the the ...-gcm ciphes to not work out of the box. And it still feels a little bit weird that openssl_seal() very happily accepts the ...-gcm cipher just to produce something which cannot be decrypted again.

bukka commented 2 years ago

I agree that it should be noted in docs but doc bugs should be reported to https://github.com/php/doc-en . So for this repo, it's just a feature request... :)

rotdrop commented 2 years ago

Agreed. I have just filed an issue which, well, is just a link to this issue.

come-nc commented 1 year ago

@bukka Hello, I see you are listed as maintainer for the openssl extension.

Regarding this issue:

  1. How hard is it to fix? Is it just a matter of adding the parameter or is there a bigger refactoring needed?
  2. If it was fixed, would it be backported or would it be PHP>8.2 only?
  3. Is the openssl_seal function somehow deprecated? I see negative comments on https://www.php.net/manual/function.openssl-seal.php on both use of RC4 and PKCS1 v1.5. Does it make sense to use this function with newer ciphers in your opinion, or is there another more suited solution?
MohamedAbuZamil commented 1 year ago

<?php

const NUM_KEYS = 2;

for ($i = 0; $i < NUM_KEYS; $i++) { $keys[] = $key = openssl_pkey_new(); $details = openssl_pkey_get_details($key); $pubKeys[] = $details['key']; $privKeys[] = openssl_pkey_get_private($key); }

$data = 'Test Data String';

$ciphers = [ 'aes-256-ctr', 'aes-256-gcm', ];

foreach ($ciphers as $cipherAlgo) {

echo " TESTING $cipherAlgo " . PHP_EOL . PHP_EOL; $iv = \random_bytes(openssl_cipher_iv_length($cipherAlgo));

// Use openssl_encrypt() for GCM mode if ($cipherAlgo === 'aes-256-gcm') { $tag = null; $sealedData = openssl_encrypt($data, $cipherAlgo, $pubKeys[0], OPENSSL_RAW_DATA, $iv, $tag); $sealedKeys = [$tag]; } else { $result = openssl_seal($data, $sealedData, $sealedKeys, $pubKeys, $cipherAlgo, $iv); }

echo "DATA : " . strlen($data) . ' ' . $data . PHP_EOL; echo "IV-LEN: " . openssl_cipher_iv_length($cipherAlgo) . PHP_EOL; echo "IV : " . bin2hex($iv) . PHP_EOL; echo "ENC DATA: " . strlen($sealedData) . ' ' . bin2hex($sealedData) . PHP_EOL; echo "RESULT: " . ($result ? 'true' : 'false') . PHP_EOL; echo PHP_EOL;

// Try decrypt foreach ($keys as $i => $key) { $decrypted = null; // ;) $unsealedKey = $sealedKeys[$i];

// Use openssl_decrypt() for GCM mode
if ($cipherAlgo === 'aes-256-gcm') {
  $result = openssl_decrypt($sealedData, $cipherAlgo, $unsealedKey, OPENSSL_RAW_DATA, $iv, $tag);
  echo "OPEN:    " . $result . PHP_EOL;
  echo "RESULT:  " . ($result !== false ? 'true' : 'false') . PHP_EOL;
} else {
  $result = openssl_open($sealedData, $decrypted, $unsealedKey, $key, $cipherAlgo, $iv);
  echo "OPEN:    " . $decrypted . PHP_EOL;
  echo "RESULT:  " . ($result ? 'true' : 'false') . PHP_EOL;
}

$result = openssl_private_decrypt($unsealedKey, $unsealedKey, $key);
echo "UNSEAL:  " . bin2hex($unsealedKey) . PHP_EOL;
echo "RESULT:  " . ($result ? 'true' : 'false') . PHP_EOL;
echo "DECRYPT: " . openssl_decrypt($sealedData, $cipherAlgo, $unsealedKey, OPENSSL_RAW_DATA, $iv) . PHP_EOL;

}

echo PHP_EOL; }