I'm working on a php program that will verify a digital signature. I'm testing with an example (valid) certificate, and decrypting the digital signature, this gives me the sha256 digest which I am trying to match:
$Cert1 = file_get_contents('CERT1/cert_array.json'); // TEST CERT DATA
$Cert1 = json_decode($Cert1, true);
$PublicKey = file_get_contents('CERT2/public_key_rsa.pem'); // CA CERT PUBLIC KEY
openssl_public_decrypt(hex2bin($Cert1['DigitalSignature']), $DecryptedDigitalSignature, $PublicKey, OPENSSL_PKCS1_PADDING); // DECRYPT SIGNATURE USING CA PUBLIC KEY
print('decrypted digital signature:' . bin2hex($DecryptedDigitalSignature) . "\n\n"); // PRINT RESULTS
This outputs the following:
decrypted digital signature:
3031300d0609608648016503040201050004200bf3dcf2340b972e97fe3c8493e11eeee01f298939734690d0b4e79e1f5701b4
There is some padding on the left, I now split this up to get the sha256 digest:
3031300d060960864801650304020105000420 // PADDING
0bf3dcf2340b972e97fe3c8493e11eeee01f298939734690d0b4e79e1f5701b4 // SHA256 DIGEST
Ok, so now I have my sha 256 digest, so I now wish to use the openssl_x509_fingerprint function on the same cert (CERT1) to create my own sha256 digest. This should match the digest that I already extracted from the cert from above. I use this code:
$Cert1 = file_get_contents('CERT1/cert.crt'); // THE CERT IN VALID x509 FORMAT
print(openssl_x509_fingerprint($Cert1, 'sha256'));
I get the following result:
d74157547fb287694b95b2533588c71f8706b0960e023fc4317f4f9a49ad2721
So, my question is, why am I getting "d74157547fb287694b95b2533588c71f8706b0960e023fc4317f4f9a49ad2721" and not "0bf3dcf2340b972e97fe3c8493e11eeee01f298939734690d0b4e79e1f5701b4"?
Related
I have this application to encrypt a MP3 audio clip using a public key (public.pem) and then decrypt using a private key (private.key). I tried the following method but the encrypted file returns as a 0 byte file. No errors returned.
exec("openssl genrsa -out private.key 2048");
exec("openssl rsa -in private.key -out public.pem -outform PEM -pubout");
$public_key = file_get_contents('./public.pem');
$private_key = file_get_contents('./private.key');
$source = "./sample.mp3";
$dest = "./sample.mp3.enc";
$data = file_get_contents($source);
openssl_public_encrypt($data, $output, $public_key, OPENSSL_NO_PADDING);
file_put_contents($dest, $output);
How can I do this? There are PHP examples to encrypt text but not files using this private/public key way.
As commented by #Peter the datasize using RSA encryption is limited (depends on RSA keysize) to plaintext lengths of about 60 to 400 bytes only.
To solve the problem of encrypting a file (here a MP3-file of about 5 mb) you need to switch to a hybrid encryption as recommended by #Topaco.
Indeed, there are fewer examples for this in public so I wrote a very simple program that does what it promises to do.
The program generates a random encryption key (length 32 bytes for AES-256), a random initialization vector ("iv", 16 bytes long for AES CBC mode) and then encrypts the key with the public key with RSA padding PKCS1_OAEP_PADDING. It writes the encrypted key, the iv and the ciphertext to a file.
For decryption the (encrypted) key is read (256 bytes long) from the file, followed by the iv and the encrypted data. Then the encrypted key is decrypted with the private key and used for the final decryption with AES. In the end the decrypted data is written to a file and voilĂ - the file is playable.
NOTICE: The main problem in my code could be the loading of the complete files (e.g. original mp3-file on encryption and encrypted file on decryption side)
into memory before they get encrypted or decrypted - e.g. having an uncut mp3-file of hundreds of mb size may cause an ""out of memory error" - beware of this.
Security warning: this file comes with absolute NO exception handling and is for educational purpose only!.
The code will load the private and public keys from actual directory as well as the mp3-file processing takes place in this memory.
The output is short:
PHP OpenSSL RSA & AES CBC 256 hybrid encryption
encryption finished
decryption finished
here is the - beware of the limitations and security warning:
<?php
// hybrid encryption
// https://stackoverflow.com/questions/64693606/php-encrypt-a-file-using-public-key-and-decrypt-using-private-key
echo 'PHP OpenSSL RSA & AES CBC 256 hybrid encryption' . PHP_EOL;
// get filenames
$plainfile = "./whateverittakes.mp3";
$cipherfile = "./whateverittakes.mp3.enc";
$decryptedfile = "./whateverittakesdec.mp3";
// attention: it is a RSA PRIVATE KEY !
// key generation:
// openssl genrsa -out private.key 2048
// openssl rsa -in private.key -out public.pem -outform PEM -pubout
// load private & public key
$public_key = openssl_pkey_get_public(file_get_contents('./public.pem'));
$private_key = openssl_pkey_get_private(file_get_contents('./private.key'));
// generate random aes encryption key
$key = openssl_random_pseudo_bytes(32); // 32 bytes = 256 bit aes key
// now encrypt the aes encryption key with the public key
openssl_public_encrypt($key, $encryptedKey, $public_key, OPENSSL_PKCS1_OAEP_PADDING);
// save 256 bytes long encrypted key
file_put_contents($cipherfile, $encryptedKey);
// aes cbc encryption
// generate random iv
$iv = openssl_random_pseudo_bytes(16);
// save 16 bytes long iv
file_put_contents($cipherfile, $iv, FILE_APPEND);
$data = file_get_contents($plainfile);
$cipher = openssl_encrypt($data, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
// save cipher
file_put_contents($cipherfile, $cipher, FILE_APPEND);
echo 'encryption finished' . PHP_EOL;
// decryption
// read the data
$handle = fopen($cipherfile, "rb");
// read 256 bytes long encryptedKey
$decryptionkeyLoad = fread($handle, 256);
// decrypt the encrypted key with private key
openssl_private_decrypt($decryptionkeyLoad, $decryptionkey, $private_key, OPENSSL_PKCS1_OAEP_PADDING);
// read 16 bytes long iv
$ivLoad = fread($handle, 16);
// read ciphertext
$dataLoad = fread($handle, filesize($cipherfile));
fclose($handle);
$decrypt = openssl_decrypt($dataLoad, 'AES-256-CBC', $decryptionkey, OPENSSL_RAW_DATA, $ivLoad);
file_put_contents($decryptedfile, $decrypt);
echo 'decryption finished' . PHP_EOL;
?>
I think good way is convert you'r file to byte after that encrypt byte and where you need it decrypt it.
Try :
How to convert base64 string into an audio mp3 file?
and after that use OpenSSL function of php for encrypt and decrypt string(byte) file with private and public key !
I think this is good resource:
https://www.geeksforgeeks.org/how-to-encrypt-and-decrypt-a-php-string/
I try to send EBICS HPB request, but get always the same error:
EBICS_AUTHENTICATION_FAILED
I think the problem is on my code, below is what the code is doing to get the signature value :
Get signature Certificate from pem file
Remove CR, LF and Ctrl-Z
Convert PEM to DER.
Apply ISO10126
hash sha256
Encryption rsa PKCS #1 v1.5: rsa_encrypt($SignatureValue, $public_key)
Then I put the result as signature value, but I'm not sure to do the right procedure before sending my request.
phpseclib:
php code :
include('Crypt/RSA_XML.php');
$rsa = new Crypt_RSA_XML();
$rsa->loadKeyfromXML($public_key_xml);
$data = "invoice_number=1,100.00&customer_tin=674858994885&serial=ONLYPEMPSERIAL&tin=ONLYPUMPTIN&vat_1_net=2,0000&vat_1_value=3600&vat_2_net=0&vat_2_value=0&vat_3_net=0&vat_3_value=0&vat_4_net=0&vat_4_value=0&vat_5_net=0&vat_5_value=0&vat_6_net=0&vat_6_value=0&vat_7_net=0&vat_7_value=0&vat_8_net=0&vat_8_value=0&payment_mode=3&discount=200&total_gross=9562";
$plaintext = $data;
//define('CRYPT_RSA_PKCS15_COMPAT', true);
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$ciphertext = base64_encode(strrev($rsa->encrypt($plaintext)));
echo "data";
echo $ciphertext; // there is encoded value getting
and
then how to decrypt encrypted data using private key:
echo $rsa->decrypt($ciphertext); // this is giving error , decryption error
actually , i want to encrypt in php and decrypt in c# ,
data encrypted successfully and when c# rsa crypto service decrypt this encrypted data then giving error message : invalid base-64 string length
$public_key_xml and $private_key_xml is defined below...
public key :
<RSAKeyValue>
<Modulus>uidt3bPfWozkIkC6nHnRDbXrvjqplfCslV2zP4hKJ6sVjVnPfjMM0ueCuEDFZ9NK+kCWaPNAVhOKKwL8HmoX/7KcFLWkwSoatnrncHTH5STey+bqR1xTFY+Rubj8BZt7D9JJYyLQC46wn4ySVnLWkCZZ9+aaTriEBzGTpUzeRiUTWVprp3oXsA7ZKyn+lhZfMx1ILhcD8dnX7xFHB57jIKvPBxAdT4K7GxdgENeS76I/zmVmlF//JnmtZ/RM1WmRkx8mFmcK/Ky8gLsmIpPPltoyBWIKIf2NQH9kHqHa2gwoPg34LTutV9AACTWuiVOjqU7Gq2BHQcjovXMF8t3Wiw==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
private key :
<RSAKeyValue>
<Modulus>uidt3bPfWozkIkC6nHnRDbXrvjqplfCslV2zP4hKJ6sVjVnPfjMM0ueCuEDFZ9NK+kCWaPNAVhOKKwL8HmoX/7KcFLWkwSoatnrncHTH5STey+bqR1xTFY+Rubj8BZt7D9JJYyLQC46wn4ySVnLWkCZZ9+aaTriEBzGTpUzeRiUTWVprp3oXsA7ZKyn+lhZfMx1ILhcD8dnX7xFHB57jIKvPBxAdT4K7GxdgENeS76I/zmVmlF//JnmtZ/RM1WmRkx8mFmcK/Ky8gLsmIpPPltoyBWIKIf2NQH9kHqHa2gwoPg34LTutV9AACTWuiVOjqU7Gq2BHQcjovXMF8t3Wiw==</Modulus><Exponent>AQAB</Exponent><P>9NiLuI9TjNvpAPQqD9ySdMX37OmEDCF02isoovt8hwPpiXcZYH4FeasNZoydRrBUOHTTRrW3xdUYGsCZI0H9tSg+gIjo/k/JhmECT7RuSgjEL7mLpusAhi1RFv81TNERGvWP8V9HtB4oZONgOpdTuNqJwhyZ3+aA3zyy7k1mKJc=</P><Q>wqJndWnlZ2i8sW8zhX9SPUddyf8E+wHek3SYynUNQ3T7zJbk+woqyjMuSImXXuZO47uBJlwskYwR+mJr/AuCR7Y0+jtByJF8RoqkY7ttdhS8CpJ9J2o5YMGcGw0JoJ16L0W6QvhY5Zxb5IAG5nuiPLDlgZYNo7+oKRcDyF+fbC0=</Q><DP>hwimhjMFsTnXV19Rk03it+Q2x8JBwS7ycyA6WSi5lPzjX5z/sefOvTtJOLV0R/gXestzehveLo1Hrflqe7d5ZN+9GMZpOVhnnGUEEVFBQjNzf56lFnmk4Fs9zaESlDr5ZBTqPgR+VygKvxlniOjMk7ZzI0sC0ikeCgA/7o1hOiE=</DP><DQ>Fpj2fBpcaSIu7kbj63b53GWBzScPs/jml6Ys6yyl6pQVfA507XSjvTAuCnv3GCyTMjud5g9DmW5y0+hDc1L+wEa2ZeslWx0RGbuVFIM5VUFZB700TLQ3jzVLY5Si7oP6IKzu0EG3SIlb3e7DXlUyY+uB6ga69K0W4BZs+QGpJ80=</DQ><InverseQ>nob08brDfoswDV8JAkGJIg5T9ktMBRzn5djbAfSorOCCVwW+iRz/hkzSs4LaeMuoC3V5AnLeTg1T7J3op67KGerRwwjXSgCKO4crs2pODcZuIMkaE8e/5Ti1O40yKl05mQaxLk/SgSAhy97HhHoiteg/ttLcrvsCcSfyyxzHT3M=</InverseQ><D>bwqYEbh7EjOa3gfIiRBtMIWFExtBD6zZ9dtH0i0FNvZpy8B38iqXirMImcohNxal0fN3BTGc/ft33sJQDABzQlaTnhLgLU1lU9aqeb1fhANjVzPuKhUbhm/2mFeNFfcyCDUDC7y3Zz19hB7BKAomjSQjZKnNAAo2z2e2T9Mzf5kV8uuYsnoum6LEvEfluQ3q3+9Ua64P0E4D2j2iaOnvpBzTCpeaBMDfWZEe19MaS40d/OrZOwlyAVPCW9RkT3948fC5KDvE0KetYDsrVApRSKzvBUQCVNmcO2o+rhMO1qKvS+zkw2VW5OxGDk/QuHuXIkCyipUEMa/DmK74hoxCaQ==</D></RSAKeyValue>
You must be using a really old version of phpseclib since phpseclib has had built in support for the XML format for quite a few years. So my first recommendation would be to upgrade to the latest version. After that you should be able to do something like this:
<?php
include('Crypt/RSA.php');
$plaintext = 'zzz';
$rsa = new Crypt_RSA();
$rsa->loadKey('<RSAKeyValue>
<Modulus>uidt3bPfWozkIkC6nHnRDbXrvjqplfCslV2zP4hKJ6sVjVnPfjMM0ueCuEDFZ9NK+kCWaPNAVhOKKwL8HmoX/7KcFLWkwSoatnrncHTH5STey+bqR1xTFY+Rubj8BZt7D9JJYyLQC46wn4ySVnLWkCZZ9+aaTriEBzGTpUzeRiUTWVprp3oXsA7ZKyn+lhZfMx1ILhcD8dnX7xFHB57jIKvPBxAdT4K7GxdgENeS76I/zmVmlF//JnmtZ/RM1WmRkx8mFmcK/Ky8gLsmIpPPltoyBWIKIf2NQH9kHqHa2gwoPg34LTutV9AACTWuiVOjqU7Gq2BHQcjovXMF8t3Wiw==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>');
define('CRYPT_RSA_PKCS15_COMPAT', true);
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
echo base64_encode($rsa->encrypt($plaintext));
If that doesn't work it'd help to see the .NET code you're using to decrypt. Also, if you could encrypt a string in .NET and post the public key you used to do the encryption and the ciphertext itself that'd be useful. From that I could play around with the phpseclib options until I found some combination of options that let me decrypt the string, which would, presumably, in turn, give us insight in how to encrypt the string.
In PHP, I want to sign some documents with a padding of PSS, and a digest of SHA512.
According to the docs, at http://www.php.net/manual/en/function.openssl-sign.php, I can set the digest however I need, by using a string, such as
openssl_sign($text-to-sign,$binary_signature,$privkey,"sha512");
I don't see any way to set the padding, however.
Can anyone please help me understand how I can sign text, using the RSA-PSS padding style, as seen in version 2.1 of PKCS #1?
I had the same needs as you, but in 2019 (I assume we got better libraries now ;)
As you already discovered, the way is using phpseclib, which is now at version 2.0 in Debian Stretch.
Signature Generation
This is the code I used to sign some binary data, using a 8192 RSA key, with PSS padding and a SHA512 hash function:
require "/usr/share/php/phpseclib/autoload.php";
use phpseclib\Crypt\RSA;
// INPUT DATA
$privkey = "..."; // I used a RSA private key in PEM format, generated by openssl, but phpseclib supports many formats...
$mydata = "..."; // I generated some binary bytes here...
// SIGNING
$rsa = new RSA();
if ($rsa->loadKey($privkey) != TRUE) {
echo "Error loading private key";
return;
}
$rsa->setHash("sha512");
$rsa->setMGFHash("sha512"); // This NEEDS to be set, or the default will be used
$rsa->setSignatureMode(RSA::SIGNATURE_PSS); // This doesn't need to be set, as it is already the default value
$signatureBytes = $rsa->sign($mydata);
$signatureBytes is a binary string (or an array of bytes, as you call it in C/C++/C#). It doesn't have any encoding, and may contain NULL bytes. This is your wanted signature.
Side note: it's nearly required to insall php-gmp, or you'll get painfully slow signing times. Here there are some benchmarks.
Signature Verification with OpenSSL
openssl dgst -sha512 -sigopt rsa_padding_mode:pss -sigopt
rsa_pss_saltlen:-1 -verify pubkey.pem -signature signature.bin
plaintextdata.dat
In detail, the -sigopt rsa_pss_saltlen:-1 tells openssl to use a salt length that matches the hashing algorithm size. (this is what the Microsoft APIs do, and there's no way to change that behavior)
Signature Verification with C++/CLI (.NET)
In order to be able to use the RSA public key in the .NET world, without using any other library, you need first to export it in BLOB format using openssl:
openssl rsa -pubin -inform PEM -in pubkey.pem -outform "MS PUBLICKEYBLOB" -out pubkey.blob
Then, you need .NET 4.6, with the new RSACng CryptoProvider.
And that's the code:
// Import the public key (as BLOBDATA)
RSACryptoServiceProvider^ rsaCsp = gcnew RSACryptoServiceProvider();
rsaCsp->ImportCspBlob(pubkey);
RSAParameters rsaParams = rsaCsp->ExportParameters(false);
RSA^ rsa = gcnew RSACng();
rsa->ImportParameters(rsaParams);
array<Byte>^ dataBytes = ...;
array<Byte>^ signatureBytes = ...;
bool signIsValid = rsa->VerifyData(dataBytes, signatureBytes, HashAlgorithmName::SHA512, RSASignaturePadding::Pss);
In order not to be "That Guy", I thought I'd leave an answer to the question, given how this does show up in Google and all ;)
I am able to get the PSS padding via http://phpseclib.sourceforge.net/
So far, I haven't gotten it to interop with OpenSSL or LibTomCrypt, but..
I'm probably just configuring it wrong.
I'm sure you'll have better luck, future person!
-CPD
How convert RSA public key, from XML to PEM (PHP)?
I'm assuming that by XML format, you mean XML DSig RSAKeyValue, and that by PEM format you mean what OpenSSL exports in between -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY-----.
You need first to extract the modulus and public exponent from the XML.
<RSAKeyValue>
<Modulus>xA7SEU+e0yQH5rm9kbCDN9o3aPIo7HbP7tX6WOocLZAtNfyxSZDU16ksL6W
jubafOqNEpcwR3RdFsT7bCqnXPBe5ELh5u4VEy19MzxkXRgrMvavzyBpVRgBUwUlV
5foK5hhmbktQhyNdy/6LpQRhDUDsTvK+g9Ucj47es9AQJ3U=
</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
You can easily convert these into a bit string using base64_decode.
Once this is done, you need to build the ASN.1 public key structure somehow.
What OpenSSL exports between BEGIN/END PUBLIC KEY is an X.509 SubjectPublicKeyInfo structure.
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
The subjectPublicKey is made of a sequnce is described in the PKCS#1 spec:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER,
publicExponent INTEGER
}
The algorithm (an AlgorithmIdentifier) is also described in the PKCS#1 spec (see section A.1):
rsaEncryption
OBJECT IDENTIFIER ::= { pkcs-1 1 }
This structure needs to be serialized in DER form, then base64-encoded and then placed between the BEGIN/END delimiters.
I don't know of any PHP library to do the ASN.1/DER encoding unfortunately (the rest is relatively easy, but dealing with ASN.1 tends to be tedious).
The PHP/PEAR Crypt_RSA module can construct RSA public keys from modulus and exponent, but its toString() method uses a custom format (just the base64-encoding of the result of PHP serialize on the array structure, which has nothing to do with the ASN.1/DER encoding).
we know
.pem - (Privacy Enhanced Mail) Base64
encoded DER certificate, enclosed
between "-----BEGIN CERTIFICATE-----"
and "-----END CERTIFICATE-----"
X.509
The SignatureValue element contains
the Base64 encoded signature result -
the signature generated with the
parameters specified in the
SignatureMethod element - of the
SignedInfo element after applying the
algorithm specified by the
CanonicalizationMethod.
XML_Signature
so we end up with
$xml = simplexml_load_file($xmlFile); // or simplexml_load_string
$pem = "-----BEGIN CERTIFICATE-----\n";
$pem .= $xml->SignatureValue;
$pem .= "\n-----END CERTIFICATE-----";
// save to file
if your xml-file isn't a XML_Signature
$xml = simplexml_load_file($xmlFile); // or simplexml_load_string
$pem = "-----BEGIN CERTIFICATE-----\n";
$pem .= $xml->nodeWithWantedValue; // use base64_encode if needed
$pem .= "\n-----END CERTIFICATE-----";
Here's an example of how to read XML RSA keys in PHP:
There is no standard for storing RSA public keys in XML. So the manner of conversion will depend on the XML you have.
Just for completeness, here is a working example of creating the PEM from modulus in python. You could call it in a subprocess from PHP if necessary.
The meat of the solution is:
def big_endian(n):
s = '%x' % n
if len(s) & 1:
s = '0' + s
return s.decode('hex')
from M2Crypto import RSA
e = E_PREFIX + big_endian(public_exponent)
n = N_PREFIX + big_endian(modulus)
new = RSA.new_pub_key((e,n))
new.save_key('foo.pub')
Where E_PREFIX and N_PREFIX are constants that (as far as I can tell) depend on the exponent and key length. Here is a quick table I have constructed:
E_PREFIX = '\x00\x00\x00\x01' # 0x3 (3)
E_PREFIX = '\x00\x00\x00\x03' # 0x10001 (65537)
N_PREFIX = '\x00\x00\x00!\x00' # 256-bit
N_PREFIX = '\x00\x00\x00A\x00' # 512-bit (default)
N_PREFIX = '\x00\x00\x00\x81\x00' # 1024-bit
N_PREFIX = '\x00\x00\x01\x01\x00' # 2048-bit
N_PREFIX = '\x00\x00\x02\x01\x00' # 4096-bit
If someone knows a more general way to compute the prefixes, do tell.
Maybe you should have a look here
Extract the two base64-encoded strings, convert and pass to PEAR::Crypt_RSA, then export as text file, then openssl convert?
Check this too