I use this code for encrypt file in php:
<?php
// The password
$passphrase = 'My secret';
// Turn a human readable passphrase
// into a reproducible iv/key pair
$iv = substr(md5("\x1B\x3C\x58".$passphrase, true), 0, 8);
$key = substr(md5("\x2D\xFC\xD8".$passphrase, true) .
md5("\x2D\xFC\xD9".$passphrase, true), 0, 24);
$opts = array('iv' => $iv, 'key' => $key, 'mode' => 'stream');
// Open the file
$fp = fopen('panel.css', 'wb');
// Add the Mcrypt stream filter
// We use Triple DES here, but you
// can use other encryption algorithm here
stream_filter_append($fp, 'mcrypt.tripledes', STREAM_FILTER_WRITE, $opts);
// Wrote some contents to the file
fwrite($fp, 'Secret secret secret data');
// Close the file
fclose($fp);
but when run above code this error happen:
stream_filter_append(): Could not open encryption module
I check for mcrypt extension with this code:
if (extension_loaded('mcrypt') === true)
{
echo "Mcrypt exist !";
}
and Mcrypt exist ! displayed.also I check php.ini for Mcrypt and this line is about Mcrypt:
[mcrypt]
; For more information about mcrypt settings see http://php.net/mcrypt-module-open
; Directory where to load mcrypt algorithms
; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt)
;mcrypt.algorithms_dir=
; Directory where to load mcrypt modes
; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt)
;mcrypt.modes_dir=
any one can help me ?
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 use these two functions to encrypt / decrypt files :
private function encrypt_file($source,$destination,$passphrase,$stream=NULL) {
// $source can be a local file...
if($stream) {
$contents = $source;
// OR $source can be a stream if the third argument ($stream flag) exists.
}else{
$handle = fopen($source, "rb");
$contents = #fread($handle, filesize($source));
fclose($handle);
}
$iv = substr(md5("\x1B\x3C\x58".$passphrase, true), 0, 8);
$key = substr(md5("\x2D\xFC\xD8".$passphrase, true) . md5("\x2D\xFC\xD9".$passphrase, true), 0, 24);
$opts = array('iv'=>$iv, 'key'=>$key);
$fp = fopen($destination, 'wb') or die("Could not open file for writing.");
stream_filter_append($fp, 'mcrypt.tripledes', STREAM_FILTER_WRITE, $opts);
fwrite($fp, $contents) or die("Could not write to file.");
fclose($fp);
}
private function decrypt_file($file,$passphrase) {
$iv = substr(md5("\x1B\x3C\x58".$passphrase, true), 0, 8);
$key = substr(md5("\x2D\xFC\xD8".$passphrase, true) .
md5("\x2D\xFC\xD9".$passphrase, true), 0, 24);
$opts = array('iv'=>$iv, 'key'=>$key);
$fp = fopen($file, 'rb');
stream_filter_append($fp, 'mdecrypt.tripledes', STREAM_FILTER_READ, $opts);
return $fp;
}
It works perfectly for most files. But there is a problem with SVG or XML files in general. Decryption of an SVG file for example gives characters "NUL NUL ..." in the last line. As you can see in this picture:
You may have copied the code straight from the PHP documentation. But: As it says on the same page, there are several issues with this code. Basically using md5 for key derivation is far from optimal. See http://www.cryptofails.com/post/70059608390/php-documentation-woes for full description. This and encryption filters are deprecated (see same link), I would recommend abandoning this style of cryptography.
I would also recommend using some tested PHP crypto library like libsodium-php. This will also be integrated into php7 itself. (Source)
Back to topic: What you are seeing is the encryption padding. For the block cipher (in your case DES) to work, each chunk has to have the size given by the algorithm. Since most data doesn't care about chunk size, the algorithm has to apply some kind of padding.
When decrypting, you also receive the padded value. To get to your output value, you need to remove the padding afterwards. In your case this would be to trim the tailing NUL charachters. Its already in the documentation (thanks to #James for pointing this out)
$data = rtrim(stream_get_contents($fp)); //trims off null padding
Disclaimer: English isn't my mother tongue so feel free to ask if something isn't clear.
Hi there,
I have to encrypt files using AES as soon as they are uploaded on the server and send the key needed to decrypt them via mail to the client (not storing it anywhere server side). Files can be as big as 2GB and are deleted 7 days after their upload.
Here is what I'm using to encrypt/decrypt files :
function encrypt_file($source, $destination, $key) {
$iv = md5("\x1B\x3C\x58".$key, true);
$ivsize = openssl_cipher_iv_length('aes-256-cbc');
$fp = fopen($destination, 'wb') or die("Could not open file for writing.");
$handle = fopen($source, "rb");
while (!feof($handle)) {
$e = 0;
$contents = fread($handle, 4 * 1024 * 1024);
$ciphertext = openssl_encrypt($contents, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
$iv = substr($ciphertext, -$ivsize);
while (!fwrite($fp, $ciphertext)) {
$e++;
if ($e == 5) {
die("Couldn't write to file.");
break 2;
}
}
}
fclose($handle);
fclose($fp);
}
function streamdecrypt_file($source, $key) {
$iv = md5("\x1B\x3C\x58".$key, true);
$ivsize = openssl_cipher_iv_length('aes-256-cbc');
$handle = fopen($source, "rb");
while (!feof($handle)) {
$contents = fread($handle, 4 * 1024 * 1024);
$raw = openssl_decrypt($contents, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
$iv = substr($contents, -$ivsize);
print $raw; // Printing because it's directly sent to the user to download
}
fclose($handle);
}
If you're wondering why 4 * 1024 * 1024 it's just that this is the buffer size with which I got the fastest encryptions. My implementation uses the schema proposed here https://stackoverflow.com/a/30742644/3857024
I also made those 2 little functions to encrypt a string to a file using a passphrase :
function encrypt_string($source, $destination, $passphrase) {
$iv = md5("\x1B\x3C\x58".$passphrase, true);
$key = md5("\x2D\xFC\xD8".$passphrase, true);
$ciphertext = openssl_encrypt($source, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
$fp = fopen($destination, 'wb') or die("Could not open file for writing.");
fwrite($fp, $ciphertext) or die("Could not write to file.");
fclose($fp);
}
function decrypt_string($source, $passphrase) {
$iv = md5("\x1B\x3C\x58".$passphrase, true);
$key = md5("\x2D\xFC\xD8".$passphrase, true);
$contents = file_get_contents($source);
return openssl_decrypt($contents, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
}
And here is what I'm finally doing when an upload is complete :
$skey = /* 32 chars random generated string [a-Z0-9]*/
$ukey = /* 10 chars random generated string [a-Z0-9]*/
encrypt_file($originalFile, $encryptedFile, $skey);
encrypt_string($skey, $encryptedKey, $ukey);
I then delete the original file and send a link containing the $ukey to the user via mail.
When they want to decrypt the file to download it, I first decrypt the file containing the $skey using the $ukey, checking if I end up with a 32-chars 256-bits long string made of [a-Z0-9]. If the $skey doesn't match the regexp, I know the $ukey is invalid, so I stop there.
I did this so that I wouldn't have to decrypt the file to check if the key was correct or not.
Now I hope that my questions fit in SO :
Am I doing it right ?
Is there anything that could/should be improved ?
It takes about 60s to encrypt a 2GB file, is that an "ok" result ?
Is it good enough ? The goal is to prevent an attacker gaining access to the server to also gain access to the users files already stored. I know he would then be able to modify the code and access the following uploads, but that should protect the files already stored right ? Am I doing too much for nothing ?
Thank you for your answers !
For an IV, use random bytes.
For password expansion, use PBKDF2 or equivalent; the derivation needs to be slower.
Restricting a key to the characters [a-Z0-9] reduces the 256 key to essentially 36 bytes. That is not very secure. You need at least 128-bits of key material.
You need a better method to authenticate the user's password.
I have a php back-end that previously generated RSA private/public keypairs on its own, encrypting the private part with a given passphrase.
Now I'm using this library: http://travistidwell.com/jsencrypt/ to generate a keypair on client side. But I didn't find how to encrypt the private key with a passphrase using this library. So I tried using this: http://www.movable-type.co.uk/scripts/aes.html but it seems that a key I get doesn't work, I can't encrypt/decrypt using it on my php back-end and different keys management apps don't recognize the key.
What am I doing wrong and how to successfully encrypt the original JSEncrypt'ed private key properly with a passphrase?
This is how the keypair was generated on PHP:
$config = array(
"digest_alg" => "sha256",
"private_key_bits" => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
"encrypt_key" => true
);
$keypair = openssl_pkey_new($config);
$pkey_pass = '123';
openssl_pkey_export($keypair, $privKey, $pkey_pass, $config);
$fp = fopen($keys_folder . '/private.pem', 'w');
fwrite($fp, $privKey);
fclose($fp);
$pubKey = openssl_pkey_get_details($keypair);
$fp = fopen($keys_folder . '/public.pem', 'w');
fwrite($fp, $pubKey);
fclose($fp);
Maybe you could adapt code from phpseclib. Quoting it:
if (!empty($this->password) || is_string($this->password)) {
$iv = Random::string(8);
$symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key
$symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8);
$des = new TripleDES();
$des->setKey($symkey);
$des->setIV($iv);
$iv = strtoupper(bin2hex($iv));
$RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" .
"Proc-Type: 4,ENCRYPTED\r\n" .
"DEK-Info: DES-EDE3-CBC,$iv\r\n" .
"\r\n" .
chunk_split(base64_encode($des->encrypt($RSAPrivateKey)), 64) .
'-----END RSA PRIVATE KEY-----';
} else {
$RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" .
chunk_split(base64_encode($RSAPrivateKey), 64) .
'-----END RSA PRIVATE KEY-----';
}
src: https://raw.githubusercontent.com/phpseclib/phpseclib/master/phpseclib/Crypt/RSA.php
How to encrypt a JS generated RSA private key with a passphrase?
You have one of two choices. First, encrypt the entire key beofre it reaches disk. Then decrypt it before you use it. In this case, you treat the key like a file you want to encrypt.
Second, use PKCS #8, a.k.a. RFC 5208, Public-Key Cryptography Standards (PKCS) #8: Private-Key Information Syntax Specification Version 1.2. In particular, see section 6 of RFC 5208, EncryptedPrivateKeyInfo.
You have a third option, but its not advised. The third option is to use an encrypted PEM encoding. Its not advisable because its been superseded by PKCS #8.
In the future, you will have a fourth option, and that is to use WebCrypto to store your key. In this case, you moved the problem of secure storage to the platform.
Unfortunately, I don't know about the library you are using, so I don't know what it may (or may not offer). But the answers above cover the OpenSSL bits of your question.
I have this PHP function (using PHP 5.3) that I use to decrypt files, it used to work just fine, but now that I moved to Amazon EC2 (based on Amazon Linux Image 2012.3), it seems that mcrypt install is either corrupted or not available at all.
Initial tests suggests that file decryption does work on smaller files, but not on 20MB+ files (which is not a particularly large size).
I tracked the problem down to this line, which is causing an Error 500 (I'm not getting mcrypt_module_open is undefined, just 500 server error)
$td = mcrypt_module_open ('rijndael-128', '', 'cbc', '');
What's strange, I checked /etc/php.ini, I can't see mcrypt at all (assuming I'm looking at the correct php.ini/path of course!)
The PHP code/function is:
function decrypt_file ($inputfile, $outputfile)
{
$key = FILE_KEY; // <-- assign private key
$buffersize = 16384;
// Open $inputfile for reading binary
$input = fopen ($inputfile, 'rb');
// Error opening $inputfile, return false
if (!$input)
return false;
// Open $outputfile for writing binary
$output = fopen ($outputfile, 'wb');
// Error opening $outputfile, return false
if (!$output)
return false;
// Open the cipher module
$td = mcrypt_module_open ('rijndael-128', '', 'cbc', '');
// Read the IV from $inputfile
$iv = fread ($input, 16);
// Compute the SHA512 of the IV (salt) and Key and use 32 bytes (256 bit) of the result as the encryption key
$keyhash = substr (hash ('sha512', $iv . $key, true), 0, 32);
// Intialize encryption
mcrypt_generic_init ($td, $keyhash, $iv);
while (!feof ($input))
{
$buffer = fread ($input, $buffersize);
// Encrypt the data
$buffer = mdecrypt_generic ($td, $buffer);
// Remove padding for last block
if (feof ($input))
{
$padsize = ord ($buffer[strlen ($buffer) - 1]);
$buffer = substr ($buffer, 0, strlen ($buffer) - $padsize);
}
// Write the encrypted data to $output
fwrite ($output, $buffer, strlen ($buffer));
}
fclose ($input);
fclose ($output);
// Deinitialize encryption module
mcrypt_generic_deinit ($td);
// Close encryption module
mcrypt_module_close ($td);
return true;
}
Anyone knows how to fix that? I'm using PHP 5.3 with CodeIgniter 2.1 (thought this is most likely not related to CodeIgniter)
It looks like you don't have mcrypt installed. Try running:
sudo yum install php-mcrypt
...from the command line on your instance.