Attempting to use dynamic encryption for Paypal on my local WAMP 2.4 server. Openssl is installed in Apache and enabled in PHP. Using exec Openssl fails. Can anyone provide some suggestions or if you feel real generous the code for converting the following PHP code to PHP Openssl requests (preferred method)? BTW I've tried both OPENSSL file pointers, both are found but neither works.
function paypal_encrypt($hash)
{
//Sample PayPal Button Encryption: Copyright 2006-2010 StellarWebSolutions.com
//Not for resale - license agreement at
//http://www.stellarwebsolutions.com/en/eula.php
$MY_KEY_FILE='paypal/encrypt/myprivate_key.pem';
$MY_CERT_FILE='paypal/encrypt/mypublic_cert.pem';
$PAYPAL_CERT_FILE='paypal/encrypt/paypal_cert.pem';
$OPENSSL='../../bin/apache/Apache2.4.4/bin/openssl.exe';
$OPENSSL='../../bin/apache/Apache2.4.4/conf/openssl.cnf';
if (!file_exists($MY_KEY_FILE)) {
echo "ERROR: MY_KEY_FILE $MY_KEY_FILE not found\n";
}
if (!file_exists($MY_CERT_FILE)) {
echo "ERROR: MY_CERT_FILE $MY_CERT_FILE not found\n";
}
if (!file_exists($PAYPAL_CERT_FILE)) {
echo "ERROR: PAYPAL_CERT_FILE $PAYPAL_CERT_FILE not found\n";
}
if (!file_exists($OPENSSL)) {
echo "ERROR: Openssl $OPENSSL not found\n";
}
//Assign Build Notation for PayPal Support
$hash['bn']= 'StellarWebSolutions.PHP_EWP2';
$data = "";
foreach ($hash as $key => $value) {
if ($value != "") {
//echo "Adding to blob: $key=$value\n";
$data .= "$key=$value\n";
}
}
echo $data;
$openssl_cmd = "($OPENSSL smime -sign -signer $MY_CERT_FILE -inkey $MY_KEY_FILE " .
"-outform der -nodetach -binary <<_EOF_\n$data\n_EOF_\n) | " .
"$OPENSSL smime -encrypt -des3 -binary -outform pem $PAYPAL_CERT_FILE";
exec($openssl_cmd, $output, $error);
if (!$error) {
return implode("\n",$output);
} else {
return $error."ERROR: encryption failed";
}
}
I have successfully done it with couple of hours of trying and searching. Finally found this very helpful article
Simplified Code Below
function paypal_ewp_encrypt_data( $hash, $certs ){
$temp_files_dir_path = ''; // a directory php have write access where we will write temporary files and delete afterwards.
$data = 'cert_id=' . $certs->paypal_cert_id;
foreach ($hash as $key => $value) {
if ($value != "") {
$data .= "\n$key=$value";
}
}
$unique_id = uniqid(time());
$data_file_in = $temp_files_dir_path . DIRECTORY_SEPARATOR . $unique_id . "-data-in.txt"; // raw data fie
$data_file_out = $temp_files_dir_path . DIRECTORY_SEPARATOR . $unique_id . "-data-out.txt";// signed data file
$enc_file_out = $temp_files_dir_path . DIRECTORY_SEPARATOR . $unique_id . "-enc-out.txt"; // encrypted data file
$fp = fopen( $data_in, "w" );
fwrite($fp, $data);
fclose($fp);
if( ! openssl_pkcs7_sign(
$data_file_in, $data_file_out, 'file://' . $certs->public_key,
array( 'file://' . $certs->private_key, ''),
array(),
PKCS7_BINARY)
){
return false;
}
$data_out_data = explode("\n\n", file_get_contents($data_out));
$out = fopen($data_out, 'wb');
fwrite($out, base64_decode($data_out_data[1]));
fclose($out);
if( ! openssl_pkcs7_encrypt(
$data_file_out, $enc_file_out,
'file://' . $certs->paypal_public_key, array(),
PKCS7_BINARY, OPENSSL_CIPHER_3DES )
){
return false;
}
$en_data = explode("\n\n", file_get_contents($enc_file_out) );
$en_data = $en_data[1];
$en_data = "-----BEGIN PKCS7-----" . str_replace("\n", "", $en_data ) . "-----END PKCS7-----";
// delete files
#unlink($data_file_in);
#unlink($data_file_out);
#unlink($enc_file_out);
$paypal_array = array(
'cmd' => '_s-' . $hash['cmd'], // use _s- before the cmd
'encrypted' => $en_data
);
}
function certs(){
$certs = new stdClass();
$certs->public_key = '' // absolute path to your public key file
$certs->private_key = '' // absolute path to your private key file
$certs->paypal_public_key = '' // absolute path to paypal public key file
$certs->paypal_cert_id = '' // given cert id after you upload the public key to paypal website.
}
Implementation
$hash = array(
// key value pair of paypal form variables
);
$certs = certs();
$data = paypal_ewp_encrypt_data($hash, $certs);
Data is a php array of key value pair require to create the form fields. Use key as name and value as field value.
Related
I'm not a security expert but I have to encrypt and decrypt files using openssl in PHP in a way that the user can define a password for the encryption. I'm using aes-256-gcm as the cipher method.
My actual code to generate a secure key is:
$password = '12345'; //Entered by the end user
$salt = openssl_random_pseudo_bytes(16);
$key = openssl_pbkdf2($password, $salt, 32, 10000, 'sha256');
I store the generated salt prefixed to the encrypted file. The password is not stored, it is known only by the user.
The solution works.
My question would be whether this solution is good and secure enough today?
As #Topaco stated the "set salt" option was disabled in PHP/openssl. Fortunately there is another crypto library available in a lot of modern PHP-versions - libsodium (sodium). Here we find the necessary method to hash passwords in a reproducible way as you need it for encryption purposes.
The "sodium_crypto_pwhash" method is described here: https://www.php.net/manual/de/function.sodium-crypto-pwhash.php.
You should check that libsodium is enabled (the easiest way is phpinfo()):
sodium
sodium support => enabled
libsodium headers version => 1.0.17
libsodium library version => 1.0.17
Now you are ready to hash your passwords. Please note that I used a fixed salt (that is totally insecure) just for demonstration purposes. Same to the "salt" the "opslimit" and "memlimit" variables need to get stored to get a reproducible (decryption) password.
<?php
echo 'php version: ' . PHP_VERSION . ' openssl version: '
. OPENSSL_VERSION_TEXT . ' sodium: '
. SODIUM_LIBRARY_VERSION . '<br>';
echo 'key derivation with PHP-libsodium' . '<br>';
echo 'https://stackoverflow.com/questions/62535675/proper-way-to-generate-key-from-password-to-openssl-encrypt-in-php' . '<br>';
$password = '12345'; //Entered by the end user
//$salt = openssl_random_pseudo_bytes(16); // openssl-randombytes
//$salt = random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES); // libsodium randombytes
// ### just for demonstration purpose I'm using a static salt
$salt = "1234567890123456";
$key = sodium_crypto_pwhash(
32,
$password,
$salt,
SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
// or SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE or SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE
SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE,
// or SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE or SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE
SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13
);
echo PHP_EOL . '<br>' . 'key: ' . bin2hex($key) . PHP_EOL . '<br>';
?>
the reproducible result:
php version: 7.4.6 openssl version: OpenSSL 1.1.1g 21 Apr 2020 sodium: 1.0.17
key derivation with PHP-libsodium
https://stackoverflow.com/questions/62535675/proper-way-to-generate-key-from-password-to-openssl-encrypt-in-php
key: 8a2a973c147a18fe2a60f7a2c4513e75141a12be0bdc793243fa12b067a3acbe
Edit: complete file encryption for small files with PHP/libsodium, Argon2ID as KDF and XCHACHA20POLY1305 authenticated encryption
A good source for short examples on sodium/libsodium is https://www.zend.com/blog/libsodium-and-php-encrypt. The "intro-pages" of libsodium do have some examples on how to use it - the following codes are taken from this side
(https://www.php.net/manual/en/intro.sodium.php). There are no checks or error handling, you just need a file "plaintext.txt".
encryption.php:
<?php
// https://www.php.net/manual/de/intro.sodium.php
$password = 'password';
$input_file = 'plaintext.txt';
$encrypted_file = 'encryption1.enc';
$chunk_size = 4096;
$alg = SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13;
$opslimit = SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE;
$memlimit = SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE;
$salt = random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES);
$secret_key = sodium_crypto_pwhash(SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES,
$password, $salt, $opslimit, $memlimit, $alg);
$fd_in = fopen($input_file, 'rb');
$fd_out = fopen($encrypted_file, 'wb');
fwrite($fd_out, pack('C', $alg));
fwrite($fd_out, pack('P', $opslimit));
fwrite($fd_out, pack('P', $memlimit));
fwrite($fd_out, $salt);
list($stream, $header) = sodium_crypto_secretstream_xchacha20poly1305_init_push($secret_key);
fwrite($fd_out, $header);
$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE;
do {
$chunk = fread($fd_in, $chunk_size);
if (stream_get_meta_data($fd_in)['unread_bytes'] <= 0) {
$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL;
}
$encrypted_chunk = sodium_crypto_secretstream_xchacha20poly1305_push($stream, $chunk, '', $tag);
fwrite($fd_out, $encrypted_chunk);
} while ($tag !== SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL);
fclose($fd_out);
fclose($fd_in);
?>
decryption.php
<?php
// https://www.php.net/manual/de/intro.sodium.php
$encrypted_file = 'encryption1.enc';
$decrypted_file = 'decrypt1.txt';
$password = 'password';
$fd_in = fopen($encrypted_file, 'rb');
$fd_out = fopen($decrypted_file, 'wb');
$chunk_size = 4096;
$alg = unpack('C', fread($fd_in, 1))[1];
$opslimit = unpack('P', fread($fd_in, 8))[1];
$memlimit = unpack('P', fread($fd_in, 8))[1];
$salt = fread($fd_in, SODIUM_CRYPTO_PWHASH_SALTBYTES);
$header = fread($fd_in, SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES);
$secret_key = sodium_crypto_pwhash(SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES,
$password, $salt, $opslimit, $memlimit, $alg);
$stream = sodium_crypto_secretstream_xchacha20poly1305_init_pull($header, $secret_key);
$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE;
while (stream_get_meta_data($fd_in)['unread_bytes'] > 0 &&
$tag !== SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL) {
$chunk = fread($fd_in, $chunk_size + SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
$res = sodium_crypto_secretstream_xchacha20poly1305_pull($stream, $chunk);
if ($res === FALSE) {
break;
}
list($decrypted_chunk, $tag) = $res;
fwrite($fd_out, $decrypted_chunk);
}
$ok = stream_get_meta_data($fd_in)['unread_bytes'] <= 0;
fclose($fd_out);
fclose($fd_in);
if (!$ok) {
die('Invalid/corrupted input');
}
?>
I found the following code, which contains a class that encrypts/decrypts securely using libsodium with a password
https://gist.github.com/bcremer/858e4a3c279b276751335dc38fc162c5
I need to send Push notifications through PHP script by using .p8 file and found following code in similar question asked here.
<?php
$keyfile = 'AuthKey_AABBCC1234.p8'; # <- Your AuthKey file
$keyid = 'AABBCC1234'; # <- Your Key ID
$teamid = 'AB12CD34EF'; # <- Your Team ID (see Developer Portal)
$bundleid = 'com.company.YourApp'; # <- Your Bundle ID
$url = 'https://api.development.push.apple.com'; # <- development url, or use http://api.push.apple.com for production environment
$token = 'e2c48ed32ef9b018........'; # <- Device Token
$message = '{"aps":{"alert":"Hi there!","sound":"default"}}';
$key = openssl_pkey_get_private('file://'.$keyfile);
$header = ['alg'=>'ES256','kid'=>$keyid];
$claims = ['iss'=>$teamid,'iat'=>time()];
$header_encoded = base64($header);
$claims_encoded = base64($claims);
$signature = '';
openssl_sign($header_encoded . '.' . $claims_encoded, $signature, $key, 'sha256');
$jwt = $header_encoded . '.' . $claims_encoded . '.' . base64_encode($signature);
// only needed for PHP prior to 5.5.24
if (!defined('CURL_HTTP_VERSION_2_0')) {
define('CURL_HTTP_VERSION_2_0', 3);
}
$http2ch = curl_init();
curl_setopt_array($http2ch, array(
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
CURLOPT_URL => "$url/3/device/$token",
CURLOPT_PORT => 443,
CURLOPT_HTTPHEADER => array(
"apns-topic: {$bundleid}",
"authorization: bearer $jwt"
),
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $message,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_TIMEOUT => 30,
CURLOPT_HEADER => 1
));
$result = curl_exec($http2ch);
if ($result === FALSE) {
throw new Exception("Curl failed: ".curl_error($http2ch));
}
$status = curl_getinfo($http2ch, CURLINFO_HTTP_CODE);
echo $status;
function base64($data) {
return rtrim(strtr(base64_encode(json_encode($data)), '+/', '-_'), '=');
}
?>
However, I found that openssl_pkey_get_private doesn't read the key file and it gives following error when I debug it.
$key = openssl_pkey_get_private('file://'.$keyfile);
if ($key === false) {
var_dump(openssl_error_string());
}
error :
'error:02001002:system library:fopen:No such file or directory'
Please note that there is no issue with curl as HTTP2 was enabled for the curl and I am using PHP7. In testing phase I'm using the script and file on the same folder to avoid any path issues.
Any clue where it went wrong ?
Please refer to next URL if did not read yet.
https://www.php.net/manual/en/function.openssl-pkey-get-private.php
To narrow down your issue, please use same directory for your php file and key file and try this working code.
Working code
$keyfile="file://".__DIR__.DIRECTORY_SEPARATOR."key.p8"; //absolute path
$key = openssl_pkey_get_private($keyfile);
if ($key === false) {
var_dump(openssl_error_string());
}else{
var_dump($key);
}
The following might be an issue.
Path
Following path styles should work.
$keyfile="file:///home/john/php/key.p8"; // unix absoulute path
$keyfile="file://C:\\users\\john\\php\\key.p8"; // windows absoulute path
$keyfile="file://".__DIR__.DIRECTORY_SEPARATOR."key.p8"; //absoulute path for unix, windows
$keyfile="file://key.p8"; // relative path, unix, windows, (php,key files in same directory)
$key = openssl_pkey_get_private($keyfile);
If path does not exist, error will be like
"error:02001002:system library:fopen:No such file or directory"
Web environment
Check your web root and web user access permission to the folder and key file.
To reduce issues, test it on php build-in web server env rather than WAMP env.
>php -S localhost:80
Corrupted key file
saved as certain type which include whitespaces.
This can occur error like next.
"error:0906D06C:PEM routines:PEM_read_bio:no start line"
in my case, key file was saved as UTF-8 with BOM(whitespaces)
DEBUG key file 1 - READ FROM VARIABLE
This code should work. I got key file from
http://micmap.org/php-by-example/en/function/openssl_pkey_get_private
Please replace $str to yours.
$str = <<<EOF
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA0llCeBjy18RylTdBih9GMUSZIC3GzeN0vQ9W8E3nwy2jdeUn
H3GBXWpMo3F43V68zM2Qz5epRNmlLSkY/PJUfJIC8Yc1VEokT52q87hH/XJ5eS8h
eZnjuSlPAGi8oZ3ImVbruzV7XmlD+QsCSxJW7tBv0dqJ71e1gAAisCXK2m7iyf/u
l6rT0Zz0ptYH4IZfwc/hQ9JcMg69uM+3bb4oBFsixMmEQwxKZsXk3YmO/YRjRbay
+6+79bSV/frW+lWhknyGSIJp2CJArYcOdbK1bXx1dRWpbNSExo7dWwuPC0Y7a5AE
eoZofieQPPBhXlp1hPgLYGat71pDqBjKLvF5GwIDAQABAoIBACPItYsSy3UzYT7L
OKYTrfBBuD8GKpTqBfkHvAWDa1MD15P92Mr7l0NaCxGfAy29qSa6LdFy/oPM9tGY
9TxKyV6rxD5sfwEI3+Z/bw6pIe4W5F1eTDaQnHHqehsatkRUQET9yXp+na8w/zRF
0C0PQKS95tfvcpm59RGCdGQ8+aZw+cIy/xez75W8IS/hagMxe7xYPjpkOkSCCEJU
zmbVq6AyWodASV0p4H9p8I+c0vO2hJ/ELJ167w6T+2/GlZg979rlyHoTW8jK2BbG
IRGaPo+c2GANXa686tdpbkPd6oJliXwBSNolxmXShvlveBbPFAJJACzCmbXNj9kH
6/K+SWkCgYEA7FNudcTkRPV8TzKhJ1AzDjw3VcnraYhY8IlNxbk7RVHLdkoUtwk/
mImeBlEfCoz9V+S/gRgeQ+1Vb/BCbS24+bN/+IGoNRFMRcOieFt6lQUpj7a9NeSo
IEclGgUiU7QR3xH73SB4GC3rgSPeHJhJZC5EJq5TzYjXTPGPpBD3zicCgYEA49wz
zfMDYIH8h4L65r/eJYIbLwpvgktgaYvhijO3qfZSWW+Y19jCBn55f65YOhPGQBHA
my0f+tVxFNZ/OupbrAIIzogxlCIYHNBawDhoHN/sB3/lSBAjifySNLyRlA62oA0w
wXvXVLVWMa3aXim3c9AlnLF1fHwcvwpOKSfdye0CgYBb1mBKq+T5V1yjek1d9bCh
i40FbZ5qOG43q2Ppvn3mBk9G/KroJlPsdy5NziB9/SRGj8JL7I92Xjihc4Cc5PPJ
NZQ5gklXtg0p30i39PTCDGuGScFlvCIJyRwF7JDWblezlE2INSH2Y4HtgX7DJfr/
T2t0jLJMYS0p3YWwgFeMaQKBgHUIe/8y6zAdc5QynSX5tGL1gXrW1FFK39k2RICU
cag1YTSYkhuDNJzbRxJifORPlcsAkzngooVWLb+zMCQVjUI6xUU3RKe+Hz5lccc6
8ZarGHL9qMkrqOVNudamZ+tw5zIrtDgcoIvcm8nmbrtgl94/MaJar2ph4O3qoByZ
Ylw9AoGAIdS79s0VKkj4VVXqK47ZcI7jGL4V4C8ujU8YcMNV88xwCoDg9ZIFprWA
P5p/cnvj6aHnqL58XiH0+bE0Lt3J+U6N6JelQQevgBHooMFh4FpDXcVda7xB3rK3
woqbi8fNhr827H2maxIZPtVG95/mvR4k5z1Jrdnr34ZUmtC6U5Q=
-----END RSA PRIVATE KEY-----
EOF;
$key = openssl_pkey_get_private($str);
if ($key === false) {
var_dump(openssl_error_string());
}else{
var_dump($key);
}
OUTPUT
resource(4) of type (OpenSSL key)
DEBUG key file 2 - READ FROM FILE
copy your key strings($str) to key file like "key.p8".
$str = <<<EOF
-----BEGIN RSA PRIVATE KEY-----
...YOUR KEY STINGS HERE...
-----END RSA PRIVATE KEY-----
EOF;
$str2 = file_get_contents("key.p8");
$len1 = strlen ($str);
$len2 = strlen ($str2);
if($len1 !== $len2) echo "File has been corrupted.";
$key = openssl_pkey_get_private($str2);
if ($key === false) {
var_dump(openssl_error_string());
}else{
var_dump($key);
}
This script can be used to send a push to IOS using .p8 certificate.
Make sure the location of the certificate is correct
<?php
$keyfile = 'AuthKey_AABBCC1234.p8'; // Your p8 Key file
$keyid = 'AABBCC1234'; // Your Key ID
$teamid = 'AB12CD34EF'; // Your Team ID (see Developer Portal)
$bundleid = 'com.company.YourApp'; // Your Bundle ID
$url = 'https://api.development.push.apple.com'; // development url, or use http://api.push.apple.com for production environment
$token = 'e2c48ed32ef9b018........'; // Device Token
$message = '{"aps":{"alert":"Hi there!","sound":"default"}}';
$key = openssl_pkey_get_private('file://'.$keyfile);
$header = ['alg'=>'ES256','kid'=>$keyid];
$claims = ['iss'=>$teamid,'iat'=>time()];
$header_encoded = base64($header);
$claims_encoded = base64($claims);
$signature = '';
openssl_sign($header_encoded . '.' . $claims_encoded, $signature, $key, 'sha256');
$jwt = $header_encoded . '.' . $claims_encoded . '.' . base64_encode($signature);
// only needed for PHP prior to 5.5.24
if (!defined('CURL_HTTP_VERSION_2_0')) {
define('CURL_HTTP_VERSION_2_0', 3);
}
$http2ch = curl_init();
curl_setopt_array($http2ch, array(
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
CURLOPT_URL => "$url/3/device/$token",
CURLOPT_PORT => 443,
CURLOPT_HTTPHEADER => array(
"apns-topic: {$bundleid}",
"authorization: bearer $jwt"
),
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $message,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_TIMEOUT => 30,
CURLOPT_HEADER => 1
));
$result = curl_exec($http2ch);
if ($result === FALSE) {
throw new Exception("Curl failed: ".curl_error($http2ch));
}
$status = curl_getinfo($http2ch, CURLINFO_HTTP_CODE);
echo $status;
function base64($data) {
return rtrim(strtr(base64_encode(json_encode($data)), '+/', '-_'), '=');
}
?>```
I have now spent the last couple of days to find documentation about this..
I need to send a XML via SOAP with the WSSE security header, but don't know how to encrypt and store the encrypted keys
Here is an example
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soap:mustUnderstand="1">
<xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="EK-1B758D26C51BFCD86614340101135741">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference>
<wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3">MIIDODCCAiCgAwIBAgIGAU0FlCVCMA0GCSqGSIb3DQEBCwUAMFoxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxCYW5rIENvbm5lY3QxFTATBgNVBAsTDEJhbmsgQ29ubmVjdDEdMBsGA1UEAxMUQmFuayBDb25uZWN0IElBLXRlc3QwHhcNMTUwNDI5MTQyODI0WhcNMTgwNDI5MTQyODI0WjAcMRowGAYDVQQDExFiYW5rIGNvbm5lY3QtdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAI23KdtaRKPTFTe/A1PnsF9dpSlTiXurKmio0OCgTP9wClHwync3JsInRwGTooA20P9zWobUnEFbEiAgRVYCxuYoldRE6NLhSC854/YTjMBeevH1TNa38lpavGiI4UwFhg70U9/JuYs21hoFyzVfaWlVfOkAMm1U/n4wHq6FZW461S5PY4A/UI1Mr8WgeIHU9GqMBtFvjynzq3SLenOPgdmKtyJ3V8EOU+DlgwKmDbxMVMtYNDZtoQvOWnuvlJ6ICDcqcW7OUkmwCKodjxxPvrdaPxyZDhT7h4FgRtrAOS8qR6L7x9D4ZIoxOMPudGvr99OSb4KVtaAEt/R7hKxG3OsCAwEAAaNCMEAwHwYDVR0jBBgwFoAU680YSkZnx1IaJAmI49LlTGiia0wwHQYDVR0OBBYEFMaWOY7Vf/iB3WVA96j5kRtbF8prMA0GCSqGSIb3DQEBCwUAA4IBAQAJ+bssSFWE6KsYT7HSDKag4Eot7yNGMY4Don/MilDnOREdu20QUS131DKrSkpBQiCXbyRUQjUoun4yue0EG+rlG3QUIlNNdJ4KZJB+dTYdLUV7XTYJNPimKAmoZ+PFNvT1eGgWcMT+MbTfpk0mw0V8IprYGa8UPchd6vtSVwpbTcPc/F4bgUTlm/V+FG4bQS61gF0koj0DEZjzat7CBHpozRgfRlXgwu26vnhWGc99uKH4GAKN4JpqPi/6Yz+7iQNJUC3yeezgBxFrIXuLpkBZSP4zunf9VxsICnxkFUXOTuYBdcbhPNzqMknD5ijFcFRZITwdv7x3uJGLkM7iUfBp</wsse:KeyIdentifier>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>af9+FhA91ytLwjeRvTYJsRCkhjHmAQGwqYwMBoNZBn7BZhF/a6EUpM9ByarVhx1SRCpjW5fb8tBVuJO1ZkjfTUZ5EAh/oDLbkmwPdSAAVzmAURHwCq3XQgMZV3lAczlLnPamxjjZBCGqxvAmBo1CvFFPC4AcBedqY92mP8XGyVHpS7JYKOxqXK2vUA1by7371x+Mu0aoS2zJPyPLa1IPwOYgR9qicmWz1RNPiEVA8ZBCN0NRyg7FLJxdUcE81z+1SjButBo2j3qcwkNcecHzZAnweY+LSWp3H5JA3WNzUHUuvFHEaPzT5jd7fUI16xo8NLK8/Rd8Eq/zDD+T3baeVQ==</xenc:CipherValue>
</xenc:CipherData>
<xenc:ReferenceList>
<xenc:DataReference URI="#ED-1B758D26C51BFCD86614340101135852"/>
</xenc:ReferenceList>
</xenc:EncryptedKey>
</wsse:Security>
<technicalAddress xmlns="http://example.com/schema/2014" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#"/>
<activationHeader xmlns="http://example.com/schema/2014" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
<organisationIdentification>
<mainRegistrationNumber>8079</mainRegistrationNumber>
<isoCountryCode>DK</isoCountryCode>
</organisationIdentification>
<functionIdentification>112233445566778899</functionIdentification>
<erpInformation/>
<endToEndMessageId>d28b6a7dad414014a59029ef1a7e84d4</endToEndMessageId>
<createDateTime>2015-06-11T10:08:33.258+02:00</createDateTime>
</activationHeader>
</soap:Header>
<soap:Body>
<xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="ED-1B758D26C51BFCD86614340101135852" Type="http://www.w3.org/2001/04/xmlenc#Content">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" wsse11:TokenType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey">
<wsse:Reference URI="#EK-1B758D26C51BFCD86614340101135741"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>dTSVuEJ90OYguQOsOz2ZtcE2mybwuvVl19pp7/e5yuvNygx3w5v+prpEvbjYLauiIAB3lrVDK2astJeYJGnDbaVJVeU0YqH5ItYVn7Wz36jJM52KB+UNbYo8EdTKYjsZuADzH+tAoA+pwYxGBXMEQctNI+C711HgP2hbpHNYOG7nAMOIrP/0B3FCy+st+9CbYlwAEENreTYunEEA41hciFnWCsIx0el7OeuiA6V51fAmvrF19RPNKwaptvbvmVdKj//RQ/0U1kRny16mDnFfX92bI3HBQm4XJA0nEfSvio7EUAAdhe77GMfu7+JELqXNowPGPLlvrbCFYnQhxGRITHtTIEbtJA6MKtBzHgjtw5pt7oWxKgGUnaJTfOPOSv43RLFGggkT/+gTjnZOagu8hhXp0x5HXJuZzw90aIS3jAfSPDc2ivct4WhWk0wcuQyC2rAh4I7gtiR+LqJJGqvucw4S+NR95FunKHKEW4yasKW1oU31/rRbp4Bmwo6BPsQlxnaSHPtk68IVkYDBslz1A5gOP+M/Iam2WI02y6sE/7aAH1ruN3pZlVuYFc3JDNHOPOvevP110d60lroknGdc9vxcFfj48OCKw/8Ed6tiXtAvk0Qu9Qt4ZyLUoPKIWEqjdLjwVadTDJQFAxRptNgiCos7s0czadUu7FNCRxfndjDxhA7trvys44ufEyK++YzZIgNu3r4dywNI22Nm+JZtLj+rX8ARE6FTPlxGBD0SSdXsfCfY2N1ytBBHQRnPsVaHK1p7KOhwQVbqEupcGyvaRolnymOzDLGFdS06OGYFrYXdgIbuqYtZP8QerXtUl0sWNAvvqHSPCQcpKecpMEecar+FUVwLEA+H1wzOprCMbRR+EgIboeDqQ7GxXqugkuFyvnlLDgxnaWhEhQb/5kAcQmnyUZ57MhDcUJqqQ4Cdmwrcxho1P+YqWY9yn0E86F+hl5976a/gH5KBobB84OWmgcX42eAmqpJf+8c8SuBv+7NctbQOk21aYlFEpkwSme/kG1/edtyoHQH/hF0RB1cT8g+u9S9AK2rs3s2G+Ap0U5oyY8pqJalGdZSBudE0sU4mhOV8trtx0FrN9A7pNkTcGPH25nCtyIz6rzR+DP8Mtgw5385s5ivVlDb+z74Wbh6iu7ZkVAogNTpUYU/1BxDXWJqFMkFmfziNxQ5AQqm1vGlBzXifoQkUFX1riutNphmu0Hs+7KMmMLvtW2cXmQDpkHFKVheeN4w7pBCEZ8KhZ0VTOwRZcdvrNcpYfXM13/QdTHQmCqqwgS/VvlUFz7PDn0/OKo6moUic8W6b1iEvd3kfc7QkunxoOUoJr4RwJ+PqCzN6PxQivAFA2tmDPc8qEa1PAdxTeNFoR/6dNQRojouuJq3C1LrbmGf6lQPvKi3KeKHXyjmDr7Tve+al2tcWJVr+1qEM3/XuthoiZbuTDxYUjZ2nf2fhHrmNcfvrfNxSNHVdQPp2R9Rf3eGxlRJsmRpef66VbYhOpmiH4xmq45EWiyBZmYm+tZtjsP51EDMIvdFbVRSGO/hMqURrDSsJXJeot27Iup2s0P2n/6a9k0c4SVvf/WXNN5x9JNvjU97bQNDQRfonJmo9pRYYHl1tSqNIYBK7KsMH+qr1vmiJuhrXUuL/RtOKvE9KXQ8kGoC9oF5rFn21z40ElxG5XRTASg==</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</soap:Body>
</soap:Envelope>
First of all I have never worked with SOAP before so chances I do things wrong has pretty good odds :)
Have found something here, but I need more details https://www.w3.org/TR/2002/REC-xmlenc-core-20021210/Overview.html#aes256-cbc
How are the iv and the key stored in CipherValue in the header?
When sending the XML request to the webservice I get this error
23-08-2018 12:50:02 General exception:Padding is invalid and cannot be removed.
23-08-2018 12:50:02 Stack trace: at System.Security.Cryptography.CapiSymmetricAlgorithm.DepadBlock(Byte[] block, Int32 offset, Int32 count)
at System.Security.Cryptography.CapiSymmetricAlgorithm.TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
at System.Security.Cryptography.Xml.EncryptedXml.DecryptData(EncryptedData encryptedData, SymmetricAlgorithm symmetricAlgorithm)
at SomeClassCore.XmlSecurity.Decryptor.DecryptData(Byte[] symmetricKey)
at SomeClassCore.SecurityServiceImpl.UnwrapRequest(ServiceRequest serviceRequest)
at BD.BCA.MessageHandler.MessageHandler.ProcessRequest(HttpContext context)
Have searched a bit more.. Maybe the iv must be a part of the stored data. But it's still not working? Same error as above
class Encryption {
const AES256_CBC = 'AES-256-CBC';
public function data_encrypt(string $data, string $cipher): Array{
switch($cipher){
case self::AES256_CBC:
$key_length = 32;
$block_length = 16;
break;
}
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));
$key = openssl_random_pseudo_bytes($key_length);
$encrypted_data = $iv.openssl_encrypt($data, $cipher, $key, OPENSSL_RAW_DATA, $iv);
return [
'data' => base64_encode($this->pkcs7_padding($encrypted_data, $block_length)),
'key' => $key
];
}
public function key_encrypt(string $key): string{
$public_cert = openssl_pkey_get_public('contents of public cert');
openssl_public_encrypt($key, $data, $public_cert, OPENSSL_PKCS1_OAEP_PADDING);
openssl_free_key($public_cert);
return base64_encode($data);
}
private function pkcs7_padding(string $data, int $block_length): string{
$pad = $block_length - (strlen($data) % $block_length);
return $data.str_repeat(chr($pad), $pad);
}
}
$Enc = new Encryption;
$data_encrypted = $Enc->data_encrypt('The message I want to encrypt', Encryption::AES256_CBC);
// This base64 encoded string goes to <EncryptedData>
$data_encrypted['data'];
// This base64 encoded string goes to <EncryptedKey> in the header
$Enc->key_encrypt($data_encrypted['key']);
update
Have been in contact with the maintainer of the webservice and OAEP padding is used with the RSA encryption and PKCS7 padding is used with AES chipher..
As I can see this is also what I do?
*TESTED AND WORKING CODE *
I would suggest that you separate the different parts involved. The most likely cause to your problems is the order of execution (i.e. you should do padding before encryption). I am also surprised that there is no signature, but that might not be required in your case. However, I prepared the suggested code for you to test and also added decrypt/decode functions to make testing easier. Good luck.
<?php
class Encryption {
const AES256_CBC = 'AES-256-CBC';
const IV_BYTES = 16;
protected $binary_security_token = null;
protected $private_key = null;
protected $public_key = null;
public function data_encrypt(string $data, string $password): Array {
$key = hash('sha256', $password, true);
$iv = openssl_random_pseudo_bytes(self::IV_BYTES);
$padding = 16 - (strlen($data) % 16);
$data .= str_repeat(chr($padding), $padding);
$encrypted_data = openssl_encrypt($data, self::AES256_CBC, $key, OPENSSL_RAW_DATA, $iv);
$encoded_data = base64_encode($iv . $encrypted_data);
return [
'data' => $encoded_data,
'key' => $key
];
}
public function data_decrypt(string $data, string $password): Array {
$decoded_data = base64_decode($data);
$key = hash('sha256', $password, true);
$iv = substr($decoded_data, 0, self::IV_BYTES);
$encrypted_data = substr($decoded_data, self::IV_BYTES);
$decrypted_data = openssl_decrypt($encrypted_data, self::AES256_CBC, $key, OPENSSL_RAW_DATA, $iv);
$padding = ord($decrypted_data[strlen($decrypted_data) - 1]);
return [
'data' => substr($decrypted_data, 0, -$padding)
];
}
public function key_encrypt(string $key): ?string {
$encoded_data = null;
if ($this->public_key && openssl_public_encrypt($key, $data, $this->public_key, OPENSSL_PKCS1_OAEP_PADDING)) {
$encoded_data = base64_encode($data);
}
// openssl_free_key($this->public_key);
return $encoded_data;
}
public function key_decrypt(string $data): ?string {
$decrypted_data = null;
$decoded_data = base64_decode($data, true);
if ($this->private_key && openssl_private_decrypt($decoded_data, $decrypted, $this->private_key, OPENSSL_PKCS1_OAEP_PADDING)) {
$decrypted_data = $decrypted;
}
// openssl_free_key($decrypted);
return $decrypted_data;
}
public function generate_keys(): void {
$config = [ "private_key_bits" => 2048, "private_key_type" => OPENSSL_KEYTYPE_RSA ];
$resource = openssl_pkey_new($config);
if (openssl_pkey_export($resource, $this->private_key)) {
echo "private_key:\n" . $this->private_key . "\n";
$private_key_file = "private_key.pem";
file_put_contents("private_key.pem" , $this->private_key);
}
$this->public_key = openssl_pkey_get_details($resource);
$this->public_key = $this->public_key["key"];
$this->binary_security_token = preg_replace("#-.+-|[\r\n]| #", "", $this->public_key);
echo "public_key:\n" . $this->public_key . "\n";
file_put_contents("public_key.pem", $this->public_key);
}
public function load_keys(): void {
$private_key_path = realpath(dirname(__FILE__) . "/private_key.pem");
if (!$private_key_path) {
$this->generate_keys();
return;
}
$private_key_contents = file_get_contents($private_key_path);
if (!$private_key_contents) {
$this->generate_keys();
return;
}
$public_key_path = realpath(dirname(__FILE__) . "/public_key.pem");
if (!$public_key_path) {
$this->generate_keys();
return;
}
$public_key_contents = file_get_contents($public_key_path);
if (!$public_key_contents) {
$this->generate_keys();
return;
}
// Signature to see that data is not manipulated, could be performed on an encrypted body. The spec says you only make a signature for what you can see.
// Is it important to "hide data", "detect manipulated data" or both ...
$this->binary_security_token = preg_replace("#-.+-|[\r\n]| #", "", $public_key_contents); // BinarySecurityToken for securityToken in Security header
// ValueType: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"
// EncodingType: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
if (openssl_pkey_export($private_key_contents, $this->private_key)) {
echo "private_key:\n" . $this->private_key . "\n";
}
$public_resource = openssl_pkey_get_public($public_key_contents);
if ($public_resource) {
$this->public_key = openssl_pkey_get_details($public_resource);
$this->public_key = $this->public_key["key"];
echo "public_key:\n" . $this->public_key . "\n";
}
}
}
$enc = new Encryption();
$encrypted = $enc->data_encrypt("The message I want to encrypt", "password");
// This base64 encoded string goes to <EncryptedData>
// $encrypted['data']
// Test that data_encrypt / data_decrypt works (from a terminal)
echo "encrypted data:\n" . $encrypted["data"] . "\n";
$decrypted = $enc->data_decrypt($encrypted["data"], "password");
echo "decrypted data:\n" . $decrypted["data"] . "\n";
// This base64 encoded string goes to <EncryptedKey> in the header
// $enc->key_encrypt($encrypted['key']);
if (version_compare(phpversion(), "7.1.0", ">=")) {
$enc->load_keys();
$pwd_hash_pre = bin2hex($encrypted["key"]);
echo "hex key:" . $pwd_hash_pre . "\n";
$encrypted_key = $enc->key_encrypt($encrypted["key"]);
echo "\nencrypted and base64encoded key:" . $encrypted_key . "\n";
$decrypted_key = $enc->key_decrypt($encrypted_key);
$pwd_hash_post = bin2hex($decrypted_key);
echo "\ndecrypted and decoded key:" . $pwd_hash_post . "\n";
$equal_hashes = $pwd_hash_pre === $pwd_hash_post ? 'true' : 'false';
echo "password hashes equal:" . $equal_hashes . "\n";
}
Your error suggest that the API failed to decrypt the openssl AES-256-CBC data.
I think the reason is because in your class you are routing the encryption through your pkcs7_padding() function. I believe that by default, as long as you don't specify OPENSSL_ZERO_PADDING in your openssl_encrypt() function that the padding is pkcs7. The block size for all AES encryption is 128bits or 16 bytes.
So in essence you are padding your already padded encryption. So basically I just removed your pkcs7_padding() from your class.
I did test your public key encryption. I was able to use a 2048b rsa key from 2048b certificate and generate an encrypted public key using a PEM formatted certificate. Whether or not it's padded correctly I have no idea. But the OPENSSL_PKCS1_OAEP_PADDING is probably correct.
My guess is that the RSA encryption worked if the API made it to the AES portion.
As far as how you are assembling the data into the XML I don't have a clue.
But is seems reasonable that in the <xenc:EncryptedKey> tag in the cipher value would be the RSA encrypted key and for the <xenc:EncryptedData> tag the cipher value would be the AES ecrypted data. You just have to figure out how the API is getting the IV.
Read your API docs for how they are expecting the IV to be delivered. I will keep looking too if this does not work for you.
I will research on that later. But for know try without manually padding your encryption. Hope it helps.
Another thing to consider is that in your case example you don't need to use an IV. In your AES encryption you are generating a new key for every encryption and then encrypting the AES key via the RSA public key obtained by your certificate.
If you were using the same AES key I would see a need to implement an IV but in this case I don't. Regardless... We need to know if the API expects an IV and if it does, how it is expected to be sent?
class Encryption {
const AES256_CBC = 'AES-256-CBC';
public function __construct(){
}
public function data_encrypt($data, $cipher){
switch($cipher){
case self::AES256_CBC:
$key_length = 32;
$block_length = 16;
break;
}
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));
$key = openssl_random_pseudo_bytes($key_length);
$encrypted_data = $iv . openssl_encrypt($data, $cipher, $key, OPENSSL_RAW_DATA, $iv);
return [
'data' => base64_encode($encrypted_data),
'key' => $key //Does this need to be encoded?
];
}
//***Important***
//Make sure you certificate is 1.) a x.509 certificate resource, 2.)A file path that leads to a PEM encoded certificate, or 3.) a PEM formatted key.
public function key_encrypt($text){
$keyResource = openssl_pkey_get_public(file_get_contents('path/to/myCert.pem')); //This returns a resource or FALSE.
if(!$keyResource){
echo 'Something wrong with certificate.';
}
openssl_public_encrypt($text, $cipherText, $keyResource, OPENSSL_PKCS1_OAEP_PADDING);
openssl_free_key($keyResource);
return base64_encode($cipherText);
}
}
$Enc = new Encryption;
$cipherText = $Enc->data_encrypt('The message I want to encrypt', Encryption::AES256_CBC);
// This base64 encoded string goes to <EncryptedData>
echo 'AES Data: ' . $cipherText['data'] . '<br><br>';
echo 'AES Key: ' . $cipherText['key'] . '<br><br>';
// This base64 encoded string goes to <EncryptedKey> in the header
$key = $Enc->key_encrypt($cipherText['key']);
echo 'RSA OAEP Padded Key: ' . $key;
I have some code here that is not functioning.
The critical issue here is the SSL encryption through the exec command in PHP.
The part <<EOF\n$data\n_EOF_\n is causing an issue as it causes the encryption to fail. I have tried the rest of the command without <<EOF\n$data\n_EOF_\n and it worked fine.
Notes:
I am running on Windows 10, XAMPP Control Panel v3.2.2, PHP, and Apache.
This is a personal computer.
It has the full installation of XAMPP.
OpenSSL is enabled.
PHP safe_mode is disabled.
The paths of OpenSSL and the certificate files are correct.
I have done much research into this issue and could not quite find a reliable solution. I would greatly appreciate some help! Thanks!
$types = array('bronze','silver','gold','platinum','diamond');
if(!in_array($_GET['type'],$types)) {
die('<error />');
}
$type = $_GET['type'];
if($type == 'bronze') {
$amount = '15.00';
} elseif ($type == 'silver') {
$amount = '25.00';
} elseif ($type == 'gold') {
$amount = '50.00';
} elseif ($type == 'platinum') {
$amount = '75.00';
} elseif ($type == 'diamond') {
$amount = '100.00';
}
#Discount Rate
$discount_rate = '0';
$IPN_URL = 'https://www.example.net/paypal/ipn';
$PAYPAL_CERT_FILE = 'C:\\xampp\\example.net\\paypal\\paypal_cert.pem';
$MY_KEY_FILE = 'C:\\xampp\\example.net\\paypal\\prvkey.pem';
$MY_CERT_FILE = 'C:\\xampp\\example.net\\paypal\\pubcert.pem';
$OPENSSL = 'C:\\xampp\\apache\\bin\\openssl.exe';
$form = array(
'cmd' => '_xclick',
'amount' => $amount,
'item_number' => explode('"',$userinfo['external_auth'])[3],
'discount_rate' => $discount_rate,
'item_name' => ucfirst($type).' EXAMPLE :: TEST',
'notify_url' => $IPN_URL,
'business' => 'example#live.ca',
'cert_id' => 'SOME_ID_HERE',
'currency_code' => 'USD',
'no_shipping' => '1'
);
function paypal_encrypt($hash) {
global $MY_KEY_FILE;
global $MY_CERT_FILE;
global $PAYPAL_CERT_FILE;
global $OPENSSL;
if (!file_exists($MY_KEY_FILE)) {
echo "ERROR: MY_KEY_FILE $MY_KEY_FILE not found\n";
}
if (!file_exists($MY_CERT_FILE)) {
echo "ERROR: MY_CERT_FILE $MY_CERT_FILE not found\n";
}
if (!file_exists($PAYPAL_CERT_FILE)) {
echo "ERROR: PAYPAL_CERT_FILE $PAYPAL_CERT_FILE not found\n";
}
//Assign Build Notation for PayPal Support
$hash['bn']= 'domain.PHP_EWP2';
$data = "";
foreach ($hash as $key => $value) {
if ($value != "") {
$data .= "$key=".escapeshellcmd($value)."\n";
}
}
$openssl_cmd = "($OPENSSL smime -sign -signer $MY_CERT_FILE -inkey $MY_KEY_FILE " .
"-outform der -nodetach -binary <<_EOF_\n$data\n_EOF_\n) | " .
"$OPENSSL smime -encrypt -des3 -binary -outform pem $PAYPAL_CERT_FILE";
exec($openssl_cmd, $output, $error);
if (!$error) {
return implode("\n",$output);
} else {
return "ERROR: encryption failed";
}
};
$encrypted = paypal_encrypt($form);
die('<success />'.$encrypted);
EDIT:
I am using https://www.stellarwebsolutions.com/en/articles/paypal_button_encryption_php.php as a guide.
If you are using xampp and your notify url has "localhost" in it, you are on another level. If you have a domain masking your xampp server, u can't use that url. Your notify url has to be ip address. ;) Just know I came back to tell you that. Just figured it out myself. If it helped, donate to foziazzubaidi#gmail.com through paypal haha. If it didn't, I've been making websites for a decade now. Send me an email, I'd be happy to help.
PEACE!!!
I am working on below things:
Generate CSR(Certificate Signing Request)
Upload SSL Certificates
To generate SSL certificate I am using something like:
$privkey = openssl_pkey_new();
$csr = openssl_csr_new($dn, $privkey);
$sscert = openssl_csr_sign($csr, null, $privkey, $days);
openssl_csr_export($csr, $csrout);
openssl_pkey_export($privkey, $pkeyout, $_POST['password']);
openssl_pkey_export_to_file($privkey, "<path/to/store/server.key>");
openssl_csr_export_to_file($csr, "/tmp/".<domain-name>.".csr");
Now using that CSR request, I am able to generate(domain-name.cer),(DigitalCert.cer).
Now once I upload this(.cer) certificates, I need to verify those certificates.
Reason: Someone generated these certificates on say "a.com" and tries to upload on "b.com". this should not happen, so I want to validate the uploaded SSL certificates.
In PHP, we have
$ok = openssl_verify($data, $signature, $pubkeyid);
but i am not able to get what things would be treated as $data, $signature and $pubkeyid based on the above certificate generation process.
Check this out:
Verify SMTP in PHP
<?php
$server = "smtp.gmail.com"; // Who I connect to
$myself = "my_server.example.com"; // Who I am
$cabundle = '/etc/ssl/cacert.pem'; // Where my root certificates are
// Verify server. There's not much we can do, if we suppose that an attacker
// has taken control of the DNS. The most we can hope for is that there will
// be discrepancies between the expected responses to the following code and
// the answers from the subverted DNS server.
// To detect these discrepancies though, implies we knew the proper response
// and saved it in the code. At that point we might as well save the IP, and
// decouple from the DNS altogether.
$match1 = false;
$addrs = gethostbynamel($server);
foreach($addrs as $addr)
{
$name = gethostbyaddr($addr);
if ($name == $server)
{
$match1 = true;
break;
}
}
// Here we must decide what to do if $match1 is false.
// Which may happen often and for legitimate reasons.
print "Test 1: " . ($match1 ? "PASSED" : "FAILED") . "\n";
$match2 = false;
$domain = explode('.', $server);
array_shift($domain);
$domain = implode('.', $domain);
getmxrr($domain, $mxhosts);
foreach($mxhosts as $mxhost)
{
$tests = gethostbynamel($mxhost);
if (0 != count(array_intersect($addrs, $tests)))
{
// One of the instances of $server is a MX for its domain
$match2 = true;
break;
}
}
// Again here we must decide what to do if $match2 is false.
// Most small ISP pass test 2; very large ISPs and Google fail.
print "Test 2: " . ($match2 ? "PASSED" : "FAILED") . "\n";
// On the other hand, if you have a PASS on a server you use,
// it's unlikely to become a FAIL anytime soon.
// End of maybe-they-help-maybe-they-don't checks.
// Establish the connection
$smtp = fsockopen( "tcp://$server", 25, $errno, $errstr );
fread( $smtp, 512 );
// Here you can check the usual banner from $server (or in general,
// check whether it contains $server's domain name, or whether the
// domain it advertises has $server among its MX's.
// But yet again, Google fails both these tests.
fwrite($smtp,"HELO $myself\r\n");
fread($smtp, 512);
// Switch to TLS
fwrite($smtp,"STARTTLS\r\n");
fread($smtp, 512);
stream_set_blocking($smtp, true);
stream_context_set_option($smtp, 'ssl', 'verify_peer', true);
stream_context_set_option($smtp, 'ssl', 'allow_self_signed', false);
stream_context_set_option($smtp, 'ssl', 'capture_peer_cert', true);
stream_context_set_option($smtp, 'ssl', 'cafile', $cabundle);
$secure = stream_socket_enable_crypto($smtp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
stream_set_blocking($smtp, false);
$opts = stream_context_get_options($smtp);
if (!isset($opts["ssl"]["peer_certificate"]))
$secure = false;
else
{
$cert = openssl_x509_parse($opts["ssl"]["peer_certificate"]);
$names = '';
if ('' != $cert)
{
if (isset($cert['extensions']))
$names = $cert['extensions']['subjectAltName'];
elseif (isset($cert['subject']))
{
if (isset($cert['subject']['CN']))
$names = 'DNS:' . $cert['subject']['CN'];
else
$secure = false; // No exts, subject without CN
}
else
$secure = false; // No exts, no subject
}
$checks = explode(',', $names);
// At least one $check must match $server
$tmp = explode('.', $server);
$fles = array_reverse($tmp);
$okay = false;
foreach($checks as $check)
{
$tmp = explode(':', $check);
if ('DNS' != $tmp[0]) continue; // candidates must start with DNS:
if (!isset($tmp[1])) continue; // and have something afterwards
$tmp = explode('.', $tmp[1]);
if (count($tmp) < 3) continue; // "*.com" is not a valid match
$cand = array_reverse($tmp);
$okay = true;
foreach($cand as $i => $item)
{
if (!isset($fles[$i]))
{
// We connected to www.example.com and certificate is for *.www.example.com -- bad.
$okay = false;
break;
}
if ($fles[$i] == $item)
continue;
if ($item == '*')
break;
}
if ($okay)
break;
}
if (!$okay)
$secure = false; // No hosts matched our server.
}
if (!$secure)
die("failed to connect securely\n");
print "Success!\n";
// Continue with connection...
?>
This works for me
$crt_md5=exec('openssl x509 -noout -modulus -in /path/to/domain.crt/ | openssl md5 | sed "s/^.* //"');
$key_md5=exec('openssl rsa -noout -modulus -in /path/to/server.key | openssl md5 | sed "s/^.* //"');
if($crt_md5 != $key_md5){
echo 'BAD';
}
else{
echo "GOOD";
}
sed "s/^.* //" - will remove (stdin)= thing from the output, so that
you get exact md5 string
this is how i do it...
system('openssl x509 -noout -modulus -in '.$crt.' | openssl md5', $crt_md5);
system('openssl rsa -noout -modulus -in '.$key.' | openssl md5', $key_md5);
if($crt_md5 != $key_md5){
echo 'BAD';
}
Try openssl_x509_check_private_key( $crt, $key ) it returns boolean
ref http://php.net/manual/en/function.openssl-x509-check-private-key.php
WARNING: openssl_x509_check_private_key will not work for some case.
Example:
SSL certificate like this:
-----BEGIN CERTIFICATE-----
xxxx
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
xxxx
xxxx
This certificate does not end with -----END CERTIFICATE----- , but it can still pass the check of this function. It will return true to tell you that it is correct, but it is not actually. If you upload this certificate to your application, such as Nginx , Nginx will tell you an error.
This doesn't seem to be an error that only appears in PHP. If you check with the openssl function on the command line, it will tell you the same result.
So I think the best way is that you need to check whether the paragraphs of the certificate are complete.
After confirming that the format is correct, use this function to verify the certificate and private key.