How to generate keypairs and encrypt with PHPECC - php

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.

Related

AES Encryption producing different results between PHP and Javascript

Hi there StackOverflow community,
After researching for countless of hours, I'm unable to find an explanation as to why my ouputs differ between javascript and my laravel application.
I could use input type hidden to make a post from my web browser, but that would defeat the purpose of having a secure client side processing and I fear that if I don't find the reason as to why this is happening, then decryption (which I plan to do through php) would not work either.
my php code is as follows:
$payload = "this is my plaintext";
$binary_signature = "";
$private_key = openssl_pkey_get_private(file_get_contents(storage_path('privatekey.pem')), 'enc123456789');
openssl_sign($payload, $binary_signature, $private_key, OPENSSL_ALGO_SHA256);
$signature = base64_encode($binary_signature);
$new_payload = $payload."&sign=".$signature; // where my actual plaintext is also used in my javascript code
$key = "thisismykey";
$iv = "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0";
$encryption = openssl_encrypt($new_payload, 'AES-256-CBC', $key,OPENSSL_RAW_DATA, $iv);
dd(base64_encode($encyrption));
which outputs the following:
AEyHK+4DQWYjw8GeVV3mfzUJtk7ylxZINeryAdFptEbDyKOVbmNg8z32J2JgxGpFsQKpXxWaqDLf0IPNIq1jof0rKWhhDpaWzvTd0Tq/zgze7oGtZzEIqtdRDqax3ZvPkzNfuO/O14iW/YTwFkm9FLy9kGIirZDUTuAcOIjXGCgxhrhZHLn+V6SZpW5dYnH8u5rPDCeez2/HkUPI71YjD6hZ0DRjIkiXCyjPYH4fjNykz4yXo8hD+489Zxm8QPq1O1dyjR9JXSdrlYMWdixt6w0vz8EtPC8gZ+bDP/N/UEK07M52VB693zYb5uD1u7WuUUtsOjkr5ocF6QbEW7sjzI4q9yAxqvxRW/bkKqodcVqtglW6YsdJjrTR0EfA/Or/QF3e3QWVM5/2g4rT3ccE17OP6Rp/46yTpW9lOgS6Qiz2hY95GoaxbLfHB/Vb0Es+UppwDu8bd/u2Qax5erBi5ObZu3AjKNpTem45paspsKH3/vc2Jc810XrVQPjnDdZ8VrXvCgPiulywn5Mj28O7uUQ5bay3Zxy3bmHb7ESDEVMKiSEoru3LzDJ7wwPlidJzPcfWtuiMEMsPOv1Y6LaxtlizWM5/zYJFX/RA4d+KBl+Rn6BoPZDcX/6eh3oUoNhy
My JS Code (the plaintext is received through an ajax call which has the same sign method as you see from the php code, the encryption plaintext is of the same value from my php code)
function encrypt(plaintext, secretkey) {
var randomSeed = "";
randomSeed = secretkey;
var key = CryptoJS.enc.Utf8.parse(randomSeed.toUpperCase());
var iv = CryptoJS.enc.Utf8.parse("0000000000000000");
var encrypt = {};
encrypt = CryptoJS.AES.encrypt(plaintext, key, {
mode : CryptoJS.mode.CBC,
iv : iv
});
console.log("encryption is "+encrypt.toString());
return encrypt.toString(encrypt.toString());
}
Returns the correct value which is:
jLmAUr+JyCjbpctU4z6+dlF61jbHRphwTS0iAk3IRiy3jkfCtaCSWdiIO0awuX6G1jAlZroTiAuMl9OW0zj0q4HitndfGFtFUoMMCqZTzvMr6cy1TyG9EFz20T6ByrBnOvGuoVjv3Flufuk2Ghz5in2W2A3T+wF+SPXX/bIAnHtE3uW0bPl2q5tn6KyUI1uoQaYcMZKRPyzAQS7WSSwSOmAcVrRuDANgZQuO+3mh86QAdeFaYqFdZUnxD4c2kkbkGy17SUFfSK8Qjv+8tkTcYXV9QRRdWjGZiQQeyAr3PDKA4SDVzrcMNwJjTaLJiZv0Iau66HGpbf2yvRDLtIOoXQmnhs6NKTZpcSwZ07hHqVvBZmNRq+jqZOGw1s8GRH+Bz4yxSRycTS0DEddhyMoxhZcUc5wt42vDOYIEH2nuw/uu4gjrwpx0rVO1ssoZYRxvBaA6zSC4N04Wdn4JE2/LtXertDLEdLBtmk3c3n4QDU0tK5v31HMY7P7+fdQXU62niVxCNPSt9dpYa82IUrQuigNXgrbphQvZNmmcONi/4pnxJjKcKYpCn/1KhkBVUAhYm6UKJJvMNAo0M+cfsvReImrJx6IzPRdzTTFAQF5kW2NFkV4EIb0DtCF679RtdAhg5ShaP7QhqYL6EgFCs9WnJTACC26TmV20DAqUiuIYULLtjDW4qFOWi/y8D1JOWTar
I don't understand why my PHP encryption is giving the wrong output while my JavaScript encryption is giving the correct output
I'm hoping someone could give me an insight as to what I'm doing wrong from my PHP side. Wha I'm hoping to achieve is that my PHP encryption will output the same result as my JavaScript encryption.
Thank you in advance :)
I have solved my issue.
To those having a similar issue, here's a brief explanation. Crypto JS uses the following:
var key = CryptoJS.enc.Utf8.parse(randomSeed.toUpperCase());
var iv = CryptoJS.enc.Utf8.parse("0000000000000000");
Which translates to a word array if you console log the output. PHP needs to have the similar word array value that cryptoJS produces for it's AES encryption method. To solve this, you have to convert your $key and iv to be in hex format and format your code in php such as:
$key = pack("H*", "4a424d56595753555047553830334d42505a314f414256414c5a565239324659");
$iv = pack("H*", "30303030303030303030303030303030");
Then when you proceed to using openssl_encrypt
$encrypted_data = openssl_encrypt($plaintext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
you will get the same output as crpytoJS.
Hope this helps.

PHP and Android Keystore encryption / decryption

I've been trying to get this for hours now, and I can't find what's wrong. I'm using a php RESTful API that I made to encrypt data using asymmetric encryption.
First, I save my user's public key in the server by exporting it in android:
fun exportPublicKey() : String {
val publicKey = getPublicKey()
return android.util.Base64.encodeToString(
publicKey!!.encoded,
android.util.Base64.NO_WRAP
)
}
This allows me in the PHP server to do that:
$public_key_core = $_POST["public_key"];
$public_key = "-----BEGIN PUBLIC KEY-----\n" . $public_key_core . "\n-----END PUBLIC KEY-----";
I am unsure that's the right way but openssl seems to be "ok" with that key ?
I then tested my keystore in local using both keys, and it works just fine using this code:
Encrypt:
fun encryptAsymmetricData(data: String, usePrivateKey : Boolean = true): ByteArray {
val cipher : Cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
val encryptedBytes: ByteArray
if (usePrivateKey){
cipher.init(Cipher.ENCRYPT_MODE, getPrivateKey())
encryptedBytes = cipher.doFinal(data.toByteArray(Charsets.UTF_8))
} else {
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey())
encryptedBytes= cipher.doFinal(data.toByteArray(Charsets.UTF_8))
}
return encryptedBytes
}
Decrypt:
fun decryptAsymmetricData(data: ByteArray): String{
val cipher : Cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey())
return cipher.doFinal(data).toString(Charsets.UTF_8)
}
Using this works because I do ".toByteArray(Charsets.UTF_8)" on the encryptData result.
Now here's the problem, I use base64 encoding and do the following to encrypt in PHP:
openssl_public_encrypt($token->token, $encrypted_token, $user->public_key);
openssl_public_encrypt($user->id, $encrypted_id, $user->public_key);
[...]
'encrypted_user_id' => base64_encode($encrypted_id),
'encrypted_token' => base64_encode($encrypted_token)
But when I try to decrypt this in Android I'm getting an exception "javax.crypto.IllegalBlockSizeException" caused by this code:
val tokenBA = String(getDecoder().decode(this.encryptedToken), Charsets.UTF_8).toByteArray(Charsets.UTF_8)
val userIDBA = String(getDecoder().decode(this.encryptedUserId), Charsets.UTF_8).toByteArray(Charsets.UTF_8)
val token = App.encryptionController.decryptAsymmetricData(tokenBA)
val userID = App.encryptionController.decryptAsymmetricData(userIDBA)
(The logic being, I use base64 to send back my data in PHP, so I convert it to UTF8 in Android, then get the associated ByteArray to decrypt it ?)
I know that the encryption works in "local" but it doesn't when using both PHP and KeyStore, so I guess the problem is coming either from the PHP encryption, or from the way I try to decrypt it in android, but I can't seem to find what wrong, could you guys help me there please ?
Thank you by advance!
Ok, after searching and making sure the issue wasn't the public key stored in the PHP server, I found the answer. It was caused by the way to convert the "base64" string in an actual ByteArray in the App. This worked:
val token = App.encryptionController.decryptAsymmetricData(getDecoder().decode(encryptedToken))
val userID = App.encryptionController.decryptAsymmetricData(getDecoder().decode(encryptedUserId))
This is only working because I do the "base64_encode" in the server, for some (bad) reason I thought it was needed to go back to UTF8 to get the ByteArray in the app.

AES CryptoJS encryption and phpseclib decryption

I have a next problem
On Node.js I have a next code
var iv = CryptoJS.enc.Hex.parse('00000000000000000000000000000000'); //it's for tests, later it will be dynamically generated
var key256Bits = 'A5178B6A965AACF3CD60B07A15061719';
var cipher = CryptoJS.AES.encrypt(
'Some text',
key256Bits,
{
iv: iv,
padding:CryptoJS.pad.ZeroPadding
}
).toString();
Then when I try to decode it with phpseclib
$key = 'A5178B6A965AACF3CD60B07A15061719';
$data = /*text encrypted by JS*/;
$cipher = new AES();
$cipher->setKeyLength(256);
$cipher->setKey($key);
$res = $cipher->decrypt($data);
And then $res becomes an empty string
What do I do wrong?
If you pass in a string to CryptoJS.<cipher>.encrypt as a key, CryptoJS treats it as a password and will derive the actual key from that using OpenSSL's EVP_BytesToKey with a random salt and one iteration of MD5.
phpseclib doesn't have an implementation of that, so you could just pass in the actual key:
var key256Bits = CryptoJS.enc.Utf8.parse('A5178B6A965AACF3CD60B07A15061719');
Since this key is only 32 hexits long, it only has 128 bit of entropy, but still uses AES-256. You need 64 hexits which you can decode before use to get 32 bytes for a secure key size.
Also, phpseclib implements PKCS#7 padding, so you need to use
padding: CryptoJS.pad.Pkcs7
in CryptoJS.
Example JavaScript code:
var iv = CryptoJS.enc.Hex.parse('00000000000000000000000000000000'); //it's for tests, later it will be dynamically generated
var key256Bits = CryptoJS.enc.Utf8.parse('A5178B6A965AACF3CD60B07A15061719');
var cipher = CryptoJS.AES.encrypt(
'Some text',
key256Bits,
{
iv: iv,
padding: CryptoJS.pad.Pkcs7
}
).toString();
console.log(cipher)
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>
In PHP, you need to make sure to decode the ciphertext before use:
$data = base64_decode("IWkBG3A46rNrxwWN2JD7xQ==");
$key = 'A5178B6A965AACF3CD60B07A15061719';
$cipher = new AES();
$cipher->setKeyLength(256);
$cipher->setKey($key);
$res = $cipher->decrypt($data);
var_dump($res);
Security consideration:
If you're using only symmetric encryption you need the exact same key at the server and the client. If you send the encryption key from the server to the client or the other way around you need to encrypt your symmetric encryption key. The easiest way to do this would be to use TLS. If you use TLS, then the data as well as key are encrypted, so you don't need to encrypt it yourself. This doesn't provide any security, just a little bit of obfuscation. You should read: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/

openssl_dh_compute_key returns false

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);

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