openssl_dh_compute_key returns false - php

I'm trying to compute the shared secret for ECDH (Elliptic Curve Diffie Hellman) using PHP.
Assume I have someone's public key.
$clientPublickey = "BOLcHOg4ajSHR6BjbSBeX_6aXjMu1V5RrUYXqyV_FqtQSd8RzdU1gkMv1DlRPDIUtFK6Nd16Jql0eSzyZh4V2uc";
I generate my keys using OpenSSL.
exec('openssl ecparam -genkey -name prime256v1 -noout -out example-ecc.pem');
$private = openssl_pkey_get_private("file://example-ecc.pem");
Using the following code, I get false as value for $sharedSecret.
$sharedSecret = openssl_dh_compute_key(base64_decode($clientPublickey), $private);
openssl_error_string() doesn't return any error.
I've var_dumped openssl_pkey_get_details($private) and I verified it was created properly.
How do I use openssl_dh_compute_key()?

https://www.openssl.org/docs/manmaster/crypto/DH_compute_key.html describes that function as being for (non-EC) Diffie Hellman. You would need ECDH_compute_key (which I don't know if PHP exposes).
Though https://wiki.openssl.org/index.php/Elliptic_Curve_Diffie_Hellman recommends using the EVP_PKEY wrappers instead of the low level routines. But, again, I can't say what support PHP has.

Quite an old post, but still..
I think your public key is not formatted in Base64, but in URL-safe-Base64.
See: https://en.wikipedia.org/wiki/Base64#URL_applications
Furthermore, you can create the keys also from PHP now. Using openssl_pkey_new().
See: https://www.php.net/manual/en/function.openssl-pkey-new.php

openssl_dh_compute_key() supports ECDH only in PHP 8.1.0, with OpenSSL 3.0.0. Before PHP 8.1.0, openssl_dh_compute_key() always returned false.
Starting from PHP 7.3, there is openssl_pkey_derive() which derives a shared secret from a set of public key and private key. It works either with DH or EC keys. The example code given for this function is simpler than the example given for openssl_dh_compute_key(), which generate a public/private DH keypair using the command line.
$private = openssl_pkey_get_private("-----BEGIN PRIVATE KEY-----
MIICJgIBADCCARcGCSqGSIb3DQEDATCCAQgCggEBAJLxRCaZ933uW+AXmabHFDDy
upojBIRlbmQLJZfigDaSA1f9YOTsIv+WwVFTX/J1mtCyx9uBcz0Nt2kmVwxWuc2f
VtCEMPsmLsVXX7xRUFLpyX1Y1IYGBVXQOoOvLWYQjpZgnx47Pkh1Ok1+smffztfC
0DCNt4KorWrbsPcmqBejXHN79KvWFjZmXOksRiNu/Bn76RiqvofC4z8Ri3kHXQG2
197JGZzzFXHadGC3xbkg8UxsNbYhVMKbm0iANfafUH7/hoS9UjAVQYtvwe7YNiW/
HnyfVCrKwcc7sadd8Iphh+3lf5P1AhaQEAMytanrzq9RDXKBxuvpSJifRYasZYsC
AQIEggEEAoIBAGwAYC2E81Y1U2Aox0U7u1+vBcbht/OO87tutMvc4NTLf6NLPHsW
cPqBixs+3rSn4fADzAIvdLBmogjtiIZoB6qyHrllF/2xwTVGEeYaZIupQH3bMK2b
6eUvnpuu4Ytksiz6VpXBBRMrIsj3frM+zUtnq8vKUr+TbjV2qyKR8l3eNDwzqz30
dlbKh9kIhZafclHfRVfyp+fVSKPfgrRAcLUgAbsVjOjPeJ90xQ4DTMZ6vjiv6tHM
hkSjJIcGhRtSBzVF/cT38GyCeTmiIA/dRz2d70lWrqDQCdp9ArijgnpjNKAAulSY
CirnMsGZTDGmLOHg4xOZ5FEAzZI2sFNLlcw=
-----END PRIVATE KEY-----
");
$public = openssl_pkey_get_public("-----BEGIN PUBLIC KEY-----
MIICJDCCARcGCSqGSIb3DQEDATCCAQgCggEBAJLxRCaZ933uW+AXmabHFDDyupoj
BIRlbmQLJZfigDaSA1f9YOTsIv+WwVFTX/J1mtCyx9uBcz0Nt2kmVwxWuc2fVtCE
MPsmLsVXX7xRUFLpyX1Y1IYGBVXQOoOvLWYQjpZgnx47Pkh1Ok1+smffztfC0DCN
t4KorWrbsPcmqBejXHN79KvWFjZmXOksRiNu/Bn76RiqvofC4z8Ri3kHXQG2197J
GZzzFXHadGC3xbkg8UxsNbYhVMKbm0iANfafUH7/hoS9UjAVQYtvwe7YNiW/Hnyf
VCrKwcc7sadd8Iphh+3lf5P1AhaQEAMytanrzq9RDXKBxuvpSJifRYasZYsCAQID
ggEFAAKCAQAiCSBpxvGgsTorxAWtcAlSmzAJnJxFgSPef0g7OjhESytnc8G2QYmx
ovMt5KVergcitztWh08hZQUdAYm4rI+zMlAFDdN8LWwBT/mGKSzRkWeprd8E7mvy
ucqC1YXCMqmIwPySvLQUB/Dl8kgau7BLAnIJm8VP+MVrn8g9gghD0qRCgPgtEaDV
vocfgnOU43rhKnIgO0cHOKtw2qybSFB8QuZrYugq4j8Bwkrzh6rdMMeyMl/ej5Aj
c0wamOzuBDtXt0T9+Fx3khHaowjCc7xJZRgZCxg43SbqMWJ9lUg94I7+LTX61Gyv
dtlkbGbtoDOnxeNnN93gwQZngGYZYciu
-----END PUBLIC KEY-----
");
echo bin2hex(openssl_pkey_derive($public, $private));
For other PHP 7 releases, there are pure PHP implementations like phpecc/phpecc, which can generate a shared encryption key using code similar to the following one. (It's one of the library examples.)
use Mdanter\Ecc\EccFactory;
use Mdanter\Ecc\Primitives\GeneratorPoint;
use Mdanter\Ecc\Serializer\PrivateKey\PemPrivateKeySerializer;
use Mdanter\Ecc\Serializer\PrivateKey\DerPrivateKeySerializer;
use Mdanter\Ecc\Serializer\PublicKey\DerPublicKeySerializer;
use Mdanter\Ecc\Serializer\PublicKey\PemPublicKeySerializer;
use Mdanter\Ecc\Util\NumberSize;
// ECDSA domain is defined by curve/generator/hash algorithm,
// which a verifier must be aware of.
$adapter = EccFactory::getAdapter();
$generator = EccFactory::getNistCurves()->generator384();
$useDerandomizedSignatures = true;
$derPub = new DerPublicKeySerializer();
$pemPub = new PemPublicKeySerializer($derPub);
$pemPriv = new PemPrivateKeySerializer(new DerPrivateKeySerializer($adapter, $derPub));
# These .pem and .key are for different keys
$alicePriv = $pemPriv->parse(file_get_contents(__DIR__ . '/../tests/data/openssl-secp256r1.pem'));
$bobPub = $pemPub->parse(file_get_contents(__DIR__ . '/../tests/data/openssl-secp256r1.1.pub.pem'));
$exchange = $alicePriv->createExchange($bobPub);
$shared = $exchange->calculateSharedKey();
echo "Shared secret: " . gmp_strval($shared, 10).PHP_EOL;
# The shared key is never used directly, but used with a key derivation function (KDF)
$kdf = function (GeneratorPoint $G, \GMP $sharedSecret) {
$adapter = $G->getAdapter();
$binary = $adapter->intToFixedSizeString(
$sharedSecret,
NumberSize::bnNumBytes($adapter, $G->getOrder())
);
$hash = hash('sha256', $binary, true);
return $hash;
};
$key = $kdf($generator, $shared);

Related

Is it possible to generate an ED25519 keypair with the built-in openssl_pkey_new function in PHP?

I'm trying to generate an ED25519 private/public keypair with the built-in openssl_pkey_new in PHP, but i don't get it working.
Not sure, but isn't it possible?
Using PHP-7.3.13 and OpenSSL-1.1.1d.
<?php
...
// OpenSSL config
$openssl_config = array(
'curve_name' => 'ed25519', // <- not working
'private_key_type' => OPENSSL_KEYTYPE_EC
);
// Create OpenSSL keypair
$openssl_keypair = openssl_pkey_new($openssl_config);
openssl_pkey_export($openssl_keypair, $privatekey);
$openssl_details = openssl_pkey_get_details($openssl_keypair);
$publickey = $openssl_details['key'];
echo $privatekey;
echo $publickey;
...
?>
Looks like impossible, read doc on https://www.php.net/manual/en/function.openssl-get-curve-names.php, there is no ED25519 curve name.
ED25519 is used for signing, so to generate an ED25519 key pair in php.
use 'sodium_crypto_sign_keypair' function.

PHP openssl_get_privatekey and openssl_sign in Python

Does anyone know what is the equivalent of this two line of PHP code in Python?
$pkeyid = openssl_get_privatekey($priv_key);
openssl_sign($data, $binary_signature, $pkeyid, OPENSSL_ALGO_SHA1);
Thanks in advance!
Edited:
$fpx_msgToken = "01";
$fpx_msgType = "BE";
$fpx_sellerExId = "ABC000012345";
$fpx_version = "6.0";
/* Generating signing String */
$data = $fpx_msgToken."|".$fpx_msgType."|".$fpx_sellerExId."|".$fpx_version;
/* Reading key */
$priv_key = file_get_contents('C:\\pki-keys\\DevExchange\\EX00002220.key');
$pkeyid = openssl_get_privatekey($priv_key);
openssl_sign($data, $binary_signature, $pkeyid, OPENSSL_ALGO_SHA1);
$fpx_checkSum = strtoupper(bin2hex( $binary_signature ) );
In python I would use the cryptography package.
Examples shown can be found here: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa/
You can create a private key with the following code.
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import dsa
private_key = dsa.generate_private_key(key_size=2048, backend=default_backend())
This will create the key to generate the signature of your data.
I would suggest you 2048 bits or above for the key length.
The following code is an example for signing a message.
from cryptography.hazmat.primitives import hashes
data = b"this is some test data"
signature = private_key.sign(data, hashes.SHA256())
If you now want to verify a signature you have to get the public key from the private key.
public_key = private_key.public_key()
public_key.verify(signature, data, hashes.SHA256())
This public key corresponds with your private key and is used to verify signatures that were created with your private key.
Don't focus on each line too much, every language and library will have different methods and ways of doing basically the same thing.
Now for a complete example you can just put the above examples together.
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dsa
private_key = dsa.generate_private_key(key_size=2048, backend=default_backend())
data = b"this is some test data"
signature = private_key.sign(data, hashes.SHA256())
public_key = private_key.public_key()
public_key.verify(signature, data, hashes.SHA256())
public_key.verify() will raise an InvalidSignature exception if the signature happens to be invalid (Source: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa/#verification).
I have found the solution of Python equivalent code for the above PHP code as below.
from OpenSSL import crypto
import binascii
fpx_msg_token = "01"
fpx_msg_type = "BE"
fpx_seller_exchange_id = "ABC0000123"
fpx_version = "6.0"
# Generating signing String
data = "{}|{}|{}|{}".format(fpx_msg_token, fpx_msg_type, fpx_seller_exchange_id, fpx_version)
key_id = open('C:\\pki-keys\\DevExchange\\EX00002220.key').read();
# Check is TraditionalOpenSSL or PKCS8 format
if key_id.startswith('-----BEGIN RSA PRIVATE KEY'):
# TraditionalOpenSSL format;
priv_key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_id)
else:
# PKCS8 format;
priv_key = crypto.load_pkcs12(key_id).get_privatekey()
# return signature is in binary string;
signature_bin_str = crypto.sign(priv_key, data, 'sha1')
# Convert binary string to hexidecimal
signature_hex = binascii.hexlify(signature_bin_str)
# Convert binary to string;
signature = signature_hex.decode("ascii")
# Convert signature to upper case;
fpx_checksum = str(signature).upper()
At the end I got the same value as in PHP code.
:)

RSA Decryption in C++, Encryption in PHP

I am working on an application which generates RSA encrypted session keys and stores them in a database. Later theses keys are transfered to a C++ application via Javascript. Therefore I want to use the OpenSSL library. I generated a 2048 bit key pair with openssl, which is used in both methods.
My PHP functions works like this:
function encrypt_with_public_key($input, $key)
{
openssl_public_encrypt($input, $crypttext, $key, OPENSSL_PKCS1_OAEP_PADDING);
return $crypttext;
}
and
$fp = fopen("public.pem","r");
$public_pem = fread($fp,8192);
fclose($fp);
$public_key = openssl_get_publickey($public_pem);
$sessionKey = ...;
$encSessionKey = encrypt_with_public_key($sessionKey, $public_key);
I tested this part successfully. The part I have trouble with, is the C++ part. I use MS Visual Studio 2013. Edit: added hex encoding (using Crypto++)
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include <openssl/crypto.h>
...
string decrypted, encoded;
decrypted.clear();
char privateKey[] =
"-----BEGIN RSA PRIVATE KEY-----\n"\
...
RSA *rsa = NULL;
BIO *keybio;
keybio = BIO_new_mem_buf(privateKey, strlen(privateKey));
rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);
StringSource ss2(input, true,
new HexEncoder(
new StringSink(encoded)
)
);
RSA_private_decrypt(encoded.length(), (unsigned char *)encoded.data(), (unsigned char *)decrypted.data(), rsa, RSA_PKCS1_OAEP_PADDING);
FBLOG_INFO("", ERR_error_string(ERR_get_error(), NULL));
return decrypted;
Note that the private key is not read from a file.
OpenSSL returns the following error: 0406506C: lib(4): func(101): reason(108).
It means afaik that my input data is longer than the modulus length (please correct me if I'm wrong). Anyone who knows how to handle this? I thought such problems are solved through the padding parameters.
The input data is the direct output of the php encrypt function (no base64 oder anything).

How to generate keypairs and encrypt with PHPECC

Using the PHPECC package from MDanter, how can I generate public/private key pairs and encrypt a message?
I found this library here: https://github.com/mdanter/phpecc
But no tutorial or explaination is provided.
I tried the following, which works but I only have public keys, I don't know where to get private keys and how to change the key length.
$g = NISTcurve::generator_192();
$Alice = new EcDH($g);
$Bob = new EcDH($g);
//Alice and bob generate their private keys and public Point
$pubPointA = $Alice->getPublicPoint();
$pubPointB = $Bob->getPublicPoint();
//Alice sends Bob her public key and vice versa
$Alice->setPublicPoint($pubPointB);
$Bob->setPublicPoint($pubPointA);
//key_A == key_B
$key_A = $Alice->calculateKey();
$key_B = $Bob->calculateKey();
//String to encrypt
$str='My test msg.';
echo 'encoding '.$str;
//Alice encrypt the string
$Ae = $Alice->encrypt($str);
echo $Ae;
echo '<hr>';
//Bob receive the string and decrypt it
$Bd = $Bob->decrypt($Ae);
echo 'Bob decrypt '.$Bd;
Any help is appreciated,
thank you
The code sample you posted is for Diffie-Hellman, which is a key-agreement protocol for deriving keys for symmetric encryption. This will not let you generate public/private keys.
the package you posted also provides ECDSA, which is a signing algorithm. It can't really be used for encryption.
for asymmetric encryption, you pretty much want to use RSA. If you want to use something with Elliptic Curve Cryptography, you can try to find an implementation of the ElGamal-EC algorithm, but I don't really know of one for PHP.

Can PHP OpenSSL generate private/public key/certificate pairs?

I wonder if PHP's OpenSSL extension can be used to generate private/public key/certificate pairs?
Sure, use openssl_pkey_new:
$privateKey = openssl_pkey_new(array('private_key_bits' => 2048));
$details = openssl_pkey_get_details($privateKey);
$publicKey = $details['key'];
You can export the keys with openssl_pkey_export or openssl_pkey_export_to_file.
I really appreciate the answer from phihag but was still struggling.
Ultimately, this helped:
$privateKeyResource = openssl_pkey_new([
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA
]);
// Save the private key to a file. Never share this file with anyone. See https://serverfault.com/questions/9708/what-is-a-pem-file-and-how-does-it-differ-from-other-openssl-generated-key-file
openssl_pkey_export_to_file($privateKeyResource, '/path/to/myNewPrivateKey.key');
// Generate the public key for the private key
$privateKeyDetailsArray = openssl_pkey_get_details($privateKeyResource);
// Save the public key to another file. Make this file available to anyone (especially anyone who wants to send you encrypted data).
file_put_contents('/path/to/myNewPublicKey.key', $privateKeyDetailsArray['key']);
// Free the key from memory.
openssl_free_key($privateKeyResource);
See docs:
https://www.php.net/manual/en/function.openssl-pkey-new.php
https://www.php.net/manual/en/function.openssl-pkey-get-details.php

Categories