How to encrypt and decrypt strings longer than 65535 characters with PHP - php

Here is my problem,
I want to encrypt JSON files that may be very long in some cases. (Sometimes containing images in Base64 format).
On the following test servers, everything works:
Raspberry Pi 3
Dell Poweredge T110
IIS on Windows 10
Synology DS1815 +
On the other hand, on the following servers, (Which are intended to be used..) the encryption does not work with more than 65535 characters, the server seems to crash.
Synology RS212
Synology DS112 +
Is there a restriction on the CPU?
Can a parameter of php.ini affect?
I tested exactly the same code on multiple servers, and on both Synology mentioned, it does not work ...
Here is my class of encryption / decryption:
class PHP_AES_Cipher {
private static $OPENSSL_CIPHER_NAME = "AES-256-CBC"; //Name of OpenSSL Cipher
private static $CIPHER_KEY_LEN = 32;
static function encrypt($key, $iv, $data) {
if (strlen($key) < PHP_AES_Cipher::$CIPHER_KEY_LEN) {
$key = str_pad("$key", PHP_AES_Cipher::$CIPHER_KEY_LEN, "0");
} else if (strlen($key) > PHP_AES_Cipher::$CIPHER_KEY_LEN) {
$key = substr($str, 0, PHP_AES_Cipher::$CIPHER_KEY_LEN);
}
$encodedEncryptedData = base64_encode(openssl_encrypt($data, PHP_AES_Cipher::$OPENSSL_CIPHER_NAME, $key, OPENSSL_RAW_DATA, $iv));
$encodedIV = base64_encode($iv);
$encryptedPayload = $encodedEncryptedData.":".$encodedIV;
return $encryptedPayload;
}
static function decrypt($key, $data) {
if (strlen($key) < PHP_AES_Cipher::$CIPHER_KEY_LEN) {
$key = str_pad("$key", PHP_AES_Cipher::$CIPHER_KEY_LEN, "0");
} else if (strlen($key) > PHP_AES_Cipher::$CIPHER_KEY_LEN) {
$key = substr($str, 0, PHP_AES_Cipher::$CIPHER_KEY_LEN);
}
$parts = explode(':', $data); //Separate Encrypted data from iv.
$decryptedData = openssl_decrypt(base64_decode($parts[0]), PHP_AES_Cipher::$OPENSSL_CIPHER_NAME, $key, OPENSSL_RAW_DATA, base64_decode($parts[1]));
return $decryptedData;
}
}
I use it like this:
$data = PHP_AES_Cipher::encrypt($key, $iv, $data);
and
$data = PHP_AES_Cipher::decrypt($key, $iv, $data);
Assuming everything works on some servers, I think the code has no problems. I already checked the Apache and PHP logs, nothing to report.
I have been searching for days without understanding the cause of the problem.
In hope that someone can help me :-)

Chunk it,
This is what I do (Uses PHPSecLib2 )
/**
* AES encrypt large files using streams and chunking
*
* #param resource $stream
* #param resource $outputStream
* #param string $key
* #throws SecExecption
*/
function streamSymEncode($stream, &$outputStream, $key, $chunkSize = 10240){
if(!is_resource($stream)) throw new Execption('Resource expected[input]');
rewind($stream); //make sure the stream is rewound
if(!is_resource($outputStream)) throw new Execption('Resource expected[output]');
$Cipher = new AES(AES::MODE_CBC);
$Cipher->setKey($key);
//create the IV
$iv = Random::string($Cipher->getBlockLength() >> 3);
$Cipher->setIV($iv);
if(strlen($iv_base64 = rtrim(base64_encode($iv), '=')) != 22) throw new Execption('IV lenght check fail');
fwrite($outputStream, $iv_base64.'$'); //add the IV for later use when we decrypt
while(!feof($stream)){
$chunk = fread($stream, $chunkSize);
fwrite($outputStream, rtrim(base64_encode($Cipher->encrypt($chunk)),'=').':');
}
$stat = fstat($outputStream);
ftruncate($outputStream, $stat['size'] - 1); //trim off the last character, hanging ':'
}
/**
* AES decrypt large files that were previously encrypted using streams and chunking
*
* #param resource $stream
* #param resource $outputStream
* #param string $key
* #throws SecExecption
*/
function streamSymDecode($stream, &$outputStream, $key){
if(!is_resource($stream)) throw new Execption('Resource expected[input]');
rewind($stream); //make sure the stream is rewound
if(!is_resource($outputStream)) throw new Execption('Resource expected[output]');
$Cipher = new AES(AES::MODE_CBC);
$Cipher->setKey($key);
$iv = base64_decode(fread($stream, 22) . '==');
$Cipher->setIV($iv);
fread($stream, 1); //advance 1 for the $
$readLine = function(&$stream){
$line = '';
while(false !== ($char = fgetc($stream))){
if($char == ':') break;
$line .= $char;
}
return $line;
};
while(!feof($stream)){
$chunk = $readLine($stream);
$decrypted = $Cipher->decrypt(base64_decode($chunk.'=='));
if(!$decrypted) throw new Execption('Failed to decode!');
fwrite($outputStream, $decrypted);
}
}
It takes two File stream resources like what you get from fopen and a key. Then it uses the same ecryption but chunks the file into $chunkSize separates them with : and when it decodes, it splits it back into chunks and re-assembles everything.
It winds up like this (for example)
IV$firstChunk:secondChunk:thirdChunk
This way you don't run out of memory trying to encrypt large files.
Please Note this was part of a lager class I use so I had to trim some things and make a few changes, that I haven't tested.
https://github.com/phpseclib/phpseclib
Cheers.

Related

Is it Possible to migrate from PEAR Crypt to PHPSecLib RSA?

We migrated to PHPSecLib a while ago, but did not migrate our legacy data that was encrypted by the old PEAR\Crypt_RSA library. We've reached a point where we need to migrate that data into PHPSecLib's RSA format. While investigating this, I came across this old forum thread. I attempted to apply the suggestion in the response, but could not get it to successfully decrypt our data. It's not erroring out or anything, it just appears to still be encrypted or encoded. We're running PHPSecLib 2.0.6 currently, and I suspect the instructions were for 1.x.
Here's a roughed out version of my adapted decryption flow (based off the forum thread):
$rsaDecryptor = new RSA();
// The Private Key is encrypted based off a password
$mc = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($mc), MCRYPT_DEV_URANDOM);
$keySize = mcrypt_enc_get_key_size($mc);
$key = substr($rsaDecryptor->password, 0, $keySize);
mcrypt_generic_init($mc, $key, $iv);
$privateKey = mdecrypt_generic($mc, base64_decode($privateKey));
mcrypt_generic_deinit($mc);
mcrypt_module_close($mc);
list($privateKeyModulus, $privateKeyExponent) = unserialize(base64_decode($privateKey));
$privateKeyExponent = new BigInteger(strrev($privateKeyExponent), 256);
$privateKeyModulus = new BigInteger(strrev($privateKeyModulus), 256);
$rsaDecryptor->modulus = $privateKeyModulus;
$rsaDecryptor->exponent = $privateKeyExponent;
$rsaDecryptor->publicExponent = $privateKeyExponent;
$rsaDecryptor->k = strlen($this->decRSA->modulus->toBytes());
// ciphertext is the raw encrypted string created by PEAR\Crypt_RSA
$value = base64_decode($ciphertext);
$value = new BigInteger($value, 256);
$value = $rsaDecryptor->_exponentiate($value)->toBytes();
$value = substr($value, 1);
Bugs In PEAR's Crypt_RSA
So I was playing around with this. There's a bug in PEAR's Crypt_RSA (latest version) that might prevent this from working at all. The following code demonstrates:
$key_pair = new Crypt_RSA_KeyPair(1024);
$privkey = $key_pair->getPrivateKey();
$pubkey = $key_pair->getPublicKey();
$a = $privkey->toString();
$b = $pubkey->toString();
echo $a == $b ? 'same' : 'different';
You'd expect $a and $b to be different, wouldn't you? Well they're not. This is because RSA/KeyPair.php does this:
$this->_public_key = &$obj;
...
$this->_private_key = &$obj;
If you remove the ampersands it works correctly but they're in the code by default.
It looks like this is an unresolved issue as of https://pear.php.net/bugs/bug.php?id=15900
Maybe it behaves differently on PHP4 but I have no idea.
Decrypting Data
Assuming the above bug isn't an issue for you then the following worked for me (using phpseclib 2.0):
function loadKey($key) // for keys genereated with $key->toString() vs $key->toPEMString()
{
if (!($key = base64_decode($key))) {
return false;
}
if (!($key = unserialize($key))) {
return false;
}
list($modulus, $exponent) = $key;
$modulus = new BigInteger(strrev($modulus), 256);
$exponent = new BigInteger(strrev($exponent), 256);
$rsa = new RSA();
$rsa->loadKey(compact('modulus', 'exponent'));
return $rsa;
}
function decrypt($key, $ciphertext)
{
if (!($ciphertext = base64_decode($ciphertext))) {
return false;
}
$key->setEncryptionMode(RSA::ENCRYPTION_NONE);
$ciphertext = strrev($ciphertext);
$plaintext = $key->decrypt($ciphertext);
$plaintext = strrev($plaintext);
$plaintext = substr($plaintext, 0, strpos($plaintext, "\0"));
return $plaintext[strlen($plaintext) - 1] == "\1" ?
substr($plaintext, 0, -1) : false;
}
$key = loadKey($private_key);
$plaintext = decrypt($key, $ciphertext);
echo $plaintext;
Private Keys Generated with toPEMString()
With PEAR's Crypt_RSA you can generate private keys an alternative way:
$key_pair->toPEMString();
This method works without code changes. If you used this approach to generate your private keys the private key starts off with -----BEGIN RSA PRIVATE KEY-----. If this is the case then you don't need to use the loadKey function I wrote. You can do this instead:
$key = new RSA();
$key->loadKey('...');
$plaintext = decrypt($key, $ciphertext);
echo $plaintext;

PHP - Improve file encryption

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.

PHP Server-Side jCryption without proc_open (AES)?

I'm trying to implement the server-side PHP handling code for jCryption without proc_open (or exec or anything of that sort), so that I can disable those functions entirely, but I'm having difficulty getting AES encryption/decryption to match what jCryption is doing on the client side, though I have gotten the RSA component to work using the OpenSSL functions.
Specifically, I'm having difficulty writing code to replace the proc_open parts of these two functions:
$descriptorSpec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w") // stdout is a pipe that the child will write to
);
function handshake($encryptedAESKey) {
// Decrypt the AES key with the RSA key
$encryptedAESKey = base64_decode($encryptedAESKey);
$privKey = unserialize($_SESSION['priv_key']);
openssl_private_decrypt($encryptedAESKey, $key, $privKey);
// Store the AES key in the session
$_SESSION["AES_Key"] = $key;
// Generate the challenge to be sent back to the client
$challenge = NULL;
$cmd = sprintf("openssl enc -aes-256-cbc -pass pass:" . escapeshellarg($key) . " -a -e");
$process = proc_open($cmd, $descriptorSpec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], $key);
fclose($pipes[0]);
// we have to trim all newlines and whitespaces by ourself
$challenge = trim(str_replace("\n", "", stream_get_contents($pipes[1])));
fclose($pipes[1]);
proc_close($process);
}
return $challenge;
}
// Once the handshake is done, we can receive encrypted data and decrypt it.
function decrypt($encryptedData) {
$key = $_SESSION["AES_Key"];
// Decrypt the client's request and send it to the clients(uncrypted)
$cmd = sprintf("openssl enc -aes-256-cbc -pass pass:" . escapeshellarg($key) . " -d");
$process = proc_open($cmd, $descriptorSpec, $pipes);
$decryptedData = NULL;
if (is_resource($process)) {
fwrite($pipes[0], base64_decode($encryptedData));
fclose($pipes[0]);
$decryptedData = stream_get_contents($pipes[1]);
fclose($pipes[1]);
proc_close($process);
}
return $decryptedData;
}
I've tried both PHP's MCrypt and OpenSSL functions, and neither seemed to match (I don't have what I tried on-hand, but I could try again and post it). Any advice on how to match the openssl commands would be really appreciated.
Reference:
http://php.net/manual/en/function.openssl-decrypt.php#107210
<?php
class sqAES {
/**
* decrypt AES 256
*
* #param string $password
* #param data $edata
* #return dencrypted data
*/
public static function decrypt($password, $edata) {
$data = base64_decode($edata);
$salt = substr($data, 8, 8);
$ct = substr($data, 16);
/**
* From https://github.com/mdp/gibberish-aes
*
* Number of rounds depends on the size of the AES in use
* 3 rounds for 256
* 2 rounds for the key, 1 for the IV
* 2 rounds for 128
* 1 round for the key, 1 round for the IV
* 3 rounds for 192 since it's not evenly divided by 128 bits
*/
$rounds = 3;
$data00 = $password.$salt;
$md5_hash = array();
$md5_hash[0] = md5($data00, true);
$result = $md5_hash[0];
for ($i = 1; $i < $rounds; $i++) {
$md5_hash[$i] = md5($md5_hash[$i - 1].$data00, true);
$result .= $md5_hash[$i];
}
$key = substr($result, 0, 32);
$iv = substr($result, 32,16);
return openssl_decrypt($ct, 'aes-256-cbc', $key, true, $iv);
}
/**
* crypt AES 256
*
* #param string $password
* #param data $data
* #return base64 encrypted data
*/
public static function crypt($password, $data) {
// Set a random salt
$salt = openssl_random_pseudo_bytes(8);
$salted = '';
$dx = '';
// Salt the key(32) and iv(16) = 48
while (strlen($salted) < 48) {
$dx = md5($dx.$password.$salt, true);
$salted .= $dx;
}
$key = substr($salted, 0, 32);
$iv = substr($salted, 32,16);
$encrypted_data = openssl_encrypt($data, 'aes-256-cbc', $key, true, $iv);
return base64_encode('Salted__' . $salt . $encrypted_data);
}
}
?>
Your new code:
require './sqAES.php';
function handshake($encryptedAESKey) {
// Decrypt the AES key with the RSA key
$encryptedAESKey = base64_decode($encryptedAESKey);
$privKey = unserialize($_SESSION['priv_key']);
openssl_private_decrypt($encryptedAESKey, $key, $privKey);
// Store the AES key in the session
$_SESSION["AES_Key"] = $key;
// Generate the challenge to be sent back to the client
$challenge = trim(str_replace("\n", "", sqAES::crypt($key, $key)));
return $challenge;
}
// Once the handshake is done, we can receive encrypted data and decrypt it.
function decrypt($encryptedData) {
$key = $_SESSION["AES_Key"];
// Decrypt the client's request and send it to the clients(uncrypted)
$decryptedData = sqAES::decrypt($key, $encryptedData);
return $decryptedData;
}

creating encrypted passwords in openfire MySQL via PHP

Openfire stores encrypted passwords in a database using blowfish encryption.
http://svn.igniterealtime.org/svn/repos/openfire/trunk/src/java/org/jivesoftware/util/Blowfish.java is the java implementation for how encrypt / decrypt functions work in openfire.
My goal is to create new user entries in the database via PHP and MySQLI. All of the variations I've tried have yielded results that don't match what already exists in the database. For example:
d3f499857b40ac45c41828ccaa5ee1f90b19ca4e0560d1e2dcf4a305f219a4a2342aa7364e9950db is one of the encrypted passwords. clear text, this is stackoverflow
I've tried a few variations:
echo mcrypt_cbc(MCRYPT_BLOWFISH, '1uY40SR771HkdDG', 'stackoverflow', MCRYPT_ENCRYPT, '12345678');
// result: áë*sY¶nŸÉX_33ô
Another based on mcrypt blowfish php slightly different results when compared to java and .net
$key = '1uY40SR771HkdDG';
$pass = 'stackoverflow';
$blocksize = mcrypt_get_block_size('blowfish', 'cbc'); // get block size
$pkcs = $blocksize - (strlen($data) % $blocksize); // get pkcs5 pad length
$data.= str_repeat(chr($pkcs), $pkcs); // append pkcs5 padding to the data
// encrypt and encode
$res = base64_encode(mcrypt_cbc(MCRYPT_BLOWFISH,$key, $pass, MCRYPT_ENCRYPT));
echo $res;
// result: 3WXKASjk35sI1+XJ7htOGw==
Any clever ideas, or any glaring problems? I simply want to implement Blowfish.encryptString() as referenced in the first link in this question.
Here's a class I made, it encrypts and decrypts properly.
Note, you need to save / [pre/app]end the IV in order to reproduce results.
Some test vectors for the java code would be nice.
<?php
/**
* Emulate OpenFire Blowfish Class
*/
class OpenFireBlowfish
{
private $key;
private $cipher;
function __construct($pass)
{
$this->cipher = mcrypt_module_open('blowfish','','cbc','');
$this->key = pack('H*',sha1($pass));
}
function encryptString($plaintext, $iv = '')
{
if ($iv == '') {
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($this->cipher));
}
else {
$iv = pack("H*", $iv);
}
mcrypt_generic_init($this->cipher, $this->key, $iv);
$bs = mcrypt_enc_get_block_size($this->cipher); // get block size
$plaintext = mb_convert_encoding($plaintext,'UTF-16BE'); // set to 2 byte, network order
$pkcs = $bs - (strlen($plaintext) % $bs); // get pkcs5 pad length
$pkcs = str_repeat(chr($pkcs), $pkcs); // create padding string
$plaintext = $plaintext.$pkcs; // append pkcs5 padding to the data
$result = mcrypt_generic($this->cipher, $plaintext);
mcrypt_generic_deinit($this->cipher);
return $iv.$result;
}
function decryptString($ciphertext)
{
$bs = mcrypt_enc_get_block_size($this->cipher); // get block size
$iv_size = mcrypt_enc_get_iv_size($this->cipher);
if ((strlen($ciphertext) % $bs) != 0) { // check string is proper size
return false;
}
$iv = substr($ciphertext, 0, $iv_size); // retrieve IV
$ciphertext = substr($ciphertext, $iv_size);
mcrypt_generic_init($this->cipher, $this->key, $iv);
$result = mdecrypt_generic($this->cipher, $ciphertext); // decrypt
$padding = ord(substr($result,-1)); // retrieve padding
$result = substr($result,0,$padding * -1); // and remove it
mcrypt_generic_deinit($this->cipher);
return $result;
}
function __destruct()
{
mcrypt_module_close($this->cipher);
}
}
$enckey = "1uY40SR771HkdDG";
$enciv = 'd3f499857b40ac45';
$javastring = 'd3f499857b40ac45c41828ccaa5ee1f90b19ca4e0560d1e2dcf4a305f219a4a2342aa7364e9950db';
$a = new OpenFireBlowfish($enckey);
$encstring = bin2hex($a->encryptString('stackoverflow',$enciv));
echo $encstring . "\n";
echo $a->decryptString(pack("H*", $encstring)) . "\n";
$b = new OpenFireBlowfish($enckey);
echo $b->decryptString(pack("H*", $javastring)) . "\n";
There is nothing wrong with your code, however to generate the same code as Openfire, you will need to add in two other items before the encrypted text.
length of ciphertext
CBCIV (initialization variable)
Read "public String decryptString(String sCipherText)" in java code, it's all there. Also check the docs on how to use CBCIV in PHP.
Openfire's code prepends the CBCIV passed with the output string. It also using Unicode as the character set. These together may be the problem area.
I don't know enough about Blowfish's internals to help more, sorry.

Encrypting / Decrypting file with Mcrypt

Trying to write a couple of functions that will encrypt or decrypt a file and am using the class found here to try and accomplish this:
http://www.itnewb.com/v/PHP-Encryption-Decryption-Using-the-MCrypt-Library-libmcrypt
The encryption function below seems to work, in that it appears to encrypt the file and place it in the intended directory. I'm trying to decrypt the file now, and it just dies with the message "Failed to complete decryption" (which is coded in there...) There's nothing in the php error logs, so I'm not sure why it's failing, but as mcrypt is entirely new to me, I'm more than inclined to believe I'm doing something wrong here...
Here are the functions:
//ENCRYPT FILE
function encryptFile() {
global $cryptastic;
$pass = PGPPASS;
$salt = PGPSALT;
$key = $cryptastic->pbkdf2($pass, $salt, 1000, 32) or die("Failed to generate secret key.");
if ($handle = opendir(PATH.'/ftpd')) {
while (false !== ($file = readdir($handle))) {
if ($file != "." && $file != "..") {
$newfile = PATH.'/encrypted/'.$file.'.txt';
$msg = file_get_contents(PATH.'/ftpd/'.$file);
$encrypted = $cryptastic->encrypt($msg, $key) or die("Failed to complete encryption.");
$nfile = fopen($newfile, 'w');
fwrite($nfile, $encrypted);
fclose($nfile);
unlink(PATH.'/ftpd/'.$file);
}
}
closedir($handle);
}
//DECRYPT FILE
function inFTP() {
global $cryptastic;
$pass = PGPPASS;
$salt = PGPSALT;
$key = $cryptastic->pbkdf2($pass, $salt, 1000, 32) or die("Failed to generate secret key.");
if ($handle = opendir(PATH.'/encrypted')) {
while (false !== ($file = readdir($handle))) {
if ($file != "." && $file != "..") {
$newfile = PATH.'/decrypted/'.$file;
$msg = PATH.'/encrypted/'.$file;
$decrypted = $cryptastic->decrypt($msg, $key) or die("Failed to complete decryption.");
$nfile = fopen($newfile, 'w');
fwrite($nfile, $decrypted);
fclose($nfile);
//unlink(PATH.'/encrypted/'.$file);
}
}
closedir($handle);
}
//$crypt->decrypt($file);
}
Since mcrypt is abandonware and no longer recommended to be used, here's an example using openssl.
class AES256Encryption
{
public const BLOCK_SIZE = 8;
public const IV_LENGTH = 16;
public const CIPHER = 'AES256';
public static function generateIv(bool $allowLessSecure = false): string
{
$success = false;
$random = openssl_random_pseudo_bytes(openssl_cipher_iv_length(static::CIPHER));
if (!$success) {
if (function_exists('sodium_randombytes_random16')) {
$random = sodium_randombytes_random16();
} else {
try {
$random = random_bytes(static::IV_LENGTH);
}
catch (Exception $e) {
if ($allowLessSecure) {
$permitted_chars = implode(
'',
array_merge(
range('A', 'z'),
range(0, 9),
str_split('~!##$%&*()-=+{};:"<>,.?/\'')
)
);
$random = '';
for ($i = 0; $i < static::IV_LENGTH; $i++) {
$random .= $permitted_chars[mt_rand(0, (static::IV_LENGTH) - 1)];
}
}
else {
throw new RuntimeException('Unable to generate initialization vector (IV)');
}
}
}
}
return $random;
}
protected static function getPaddedText(string $plainText): string
{
$stringLength = strlen($plainText);
if ($stringLength % static::BLOCK_SIZE) {
$plainText = str_pad($plainText, $stringLength + static::BLOCK_SIZE - $stringLength % static::BLOCK_SIZE, "\0");
}
return $plainText;
}
public static function encrypt(string $plainText, string $key, string $iv): string
{
$plainText = static::getPaddedText($plainText);
return base64_encode(openssl_encrypt($plainText, static::CIPHER, $key, OPENSSL_RAW_DATA, $iv));
}
public static function decrypt(string $encryptedText, string $key, string $iv): string
{
return openssl_decrypt(base64_decode($encryptedText), static::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
}
}
$text = '8SViI0Gz4r-p7A15YxkwjOBFuW*#NTtbm{U]D&E=~6yLM+adX'P;h3$,KJ%/eo>}<Rs:2#gZ.9fqn"Cv_^[(H\c!)?`Ql';
$key = 'secretkey';
$iv = AES256Encryption::generateIv();
$encryptedText = AES256Encryption::encrypt($text, $key, $iv);
$decryptedText = AES256Encryption::decrypt($encryptedText, $key, $iv);
printf('Original Text: %s%s', $text, PHP_EOL);
printf('Encrypted: %s%s', $encryptedText, PHP_EOL);
printf('Decrypted: %s%s', $decryptedText, PHP_EOL);
Output:
// Long string with lots of different characters
Original Text: 8SViI0Gz4r-p7A15YxkwjOBFuW*#NTtbm{U]D&E=~6yLM+adX'P;h3$,KJ%/eo>}<Rs:2#gZ.9fqn"Cv_^[(H\c!)?`Ql
Encrypted : rsiF4PMCMyvAp+CTuJrxJYGoV4BSy8Fy+q+FL8m64+Mt5V3o0HS0elRkWXsy+//hPjzNhjmVktxVvMY55Negt4DyLcf2QpH05wUX+adJDe634J/9fWd+nlEFoDutXuhY+/Kep9zUZFDmLmszJaBHWQ==
Decrypted : 8SViI0Gz4r-p7A15YxkwjOBFuW*#NTtbm{U]D&E=~6yLM+adX'P;h3$,KJ%/eo>}<Rs:2#gZ.9fqn"Cv_^[(H\c!)?`Ql
Old Answer
Try this PHP5 class for encryption using mcrypt. In this case it's using AES encryption. You'll want to change the key for each site you use it on. If you don't use it at least it may guide you on writing your own version of it.
<?php
class Encryption
{
const CIPHER = MCRYPT_RIJNDAEL_128; // Rijndael-128 is AES
const MODE = MCRYPT_MODE_CBC;
/* Cryptographic key of length 16, 24 or 32. NOT a password! */
private $key;
public function __construct($key) {
$this->key = $key;
}
public function encrypt($plaintext) {
$ivSize = mcrypt_get_iv_size(self::CIPHER, self::MODE);
$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
$ciphertext = mcrypt_encrypt(self::CIPHER, $this->key, $plaintext, self::MODE, $iv);
return base64_encode($iv.$ciphertext);
}
public function decrypt($ciphertext) {
$ciphertext = base64_decode($ciphertext);
$ivSize = mcrypt_get_iv_size(self::CIPHER, self::MODE);
if (strlen($ciphertext) < $ivSize) {
throw new Exception('Missing initialization vector');
}
$iv = substr($ciphertext, 0, $ivSize);
$ciphertext = substr($ciphertext, $ivSize);
$plaintext = mcrypt_decrypt(self::CIPHER, $this->key, $ciphertext, self::MODE, $iv);
return rtrim($plaintext, "\0");
}
}
Usage:
$key = /* CRYPTOGRAPHIC!!! key */;
$crypt = new Encryption($key);
$encrypted_string = $crypt->encrypt('this is a test');
$decrypted_string = $crypt->decrypt($encrypted_string); // this is a test
Notes:
This class is not safe for use with binary data (which may end in NUL bytes)
This class does not provide authenticated encryption.
While Johns answer is good, using base64 encoding just to fix the binary safety issue is overkill and will make your encrypted files 33% larger than the original. Here is my PHP Implementation of the AES Crypt file format which solves all the above issues transparently.
https://github.com/philios33/PHP-AES-File-Encryption
It is binary safe and includes authenticated encryption. Since it uses the open source aes crypt file format (.aes) it is fully compatible with other .aes software.
https://www.aescrypt.com/
The interface is pretty simple whether you are encrypting or decrypting. You just give it a source file and password.
You should not be using Mcrypt to encrypt/decrypt data. As shown in your question, and in the accepted answer, the data is not authenticated, which means it will fall victim to chosen ciphertext attacks.
Further, a great deal of effort has been done to make sure that developers put together cryptographic primitives correctly. As such, instead of Mcrypt, you should be using libsodium for your PHP projects. libsodium is a fork of NaCl. NaCl/libsodium is written to remove a lot of the cryptographic pitfalls that developers find themselves in, such as timing attacks with verification of MAC tags.
Mcrypt is deprecated in PHP 7.1, and libsodim is the preferred way to handle cryptography in PHP.
Using libsodium in your PHP project is easy, and secure. Scott Arciszewski has written an extensive ebook on using libsodium with PHP at https://paragonie.com/book/pecl-libsodium. It's worth the read for anyone doing PHP cryptography.
CakePHP has a pretty good implementation of rijndael. I'm not posting code directly here because not sure the legal ramifications.
Here are the api docs for the Security::rijndael() method.
If encoding a file, you will want to base64_encode() before calling this method with 'encrypt', and base64_decode() after calling this method with 'decrypt'

Categories