IT Knowledge Base

~ Without sacrifice, there can be no victory ~

發佈日期:

分類:

, ,

如何在PHP中‧利用OpenSSL (aes-256-cbc)對大容量檔案作出加密/解密動作

01. 一直有個想法,檔案放在網上,要如何加密才能感覺安全些呢?今次會試一下PHP OpenSSL的加密/解密方法。參考『Antoine Lamé Blog』,很快就做到第一個成品。

02. 700KB相片利用OpenSSL的加密/解密,PHP記憶體卻用了差不多4倍相片檔案用量。

<?php
$starttime = microtime(true);
define('FILE_ENCRYPTION_BLOCKS', 100000);
define('FILE_DECRYPTION_BLOCKS', 100016);

function encrypt($source, $key) {
$cipher = 'aes-256-cbc';
$ivLenght = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivLenght);
$output = $iv;
$i = 0;
while ($i < strlen($source)) {
$plaintext = substr($source, $i, FILE_ENCRYPTION_BLOCKS);
$i = $i + FILE_ENCRYPTION_BLOCKS;
$ciphertext = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_RAW_DATA, $iv);
$output .= $ciphertext;
}
return $output;
}

function decrypt($source, $key) {
$cipher = 'aes-256-cbc';
$ivLenght = openssl_cipher_iv_length($cipher);
$iv = substr($source, 0, $ivLenght);
$i = strlen($iv);
$output = '';
while ($i < strlen($source)) {
$ciphertext = substr($source, $i, FILE_DECRYPTION_BLOCKS);
$i = $i + FILE_DECRYPTION_BLOCKS;
$plaintext = openssl_decrypt($ciphertext, $cipher, $key, OPENSSL_RAW_DATA, $iv);
$output .= $plaintext;
}
return $output;
}

$image = 'gao.png';
$password = 'password';
$data = file_get_contents($image);
echo 'Orignal photo img size: '.round(strlen($data) / 1024 ,2).'KB<br><br>';;
$encrypted = encrypt($data, $password);
$decrypted = decrypt($encrypted, $password);
echo 'Encrypted photo img size: '.round(strlen($encrypted) / 1024 ,2).'KB and decrypted photo img size: '.round(strlen($decrypted) / 1024, 2).'KB<br><br>';
$endtime = microtime(true);
echo 'Memory usage: ' . round(memory_get_usage() / 1048576, 2).'MB and Time used: '.round($endtime - $starttime, 5).'sec<br><br>';
echo 'Showing the result:<br>';
$imageData = base64_encode($decrypted);
$src = 'data: '.mime_content_type($image).';base64,'.$imageData;
echo '<img src="'.$src.'" style="max-width: 400px;">';
?>

02. 根據『Antoine Lamé Blog』說法,需要減少PHP記憶體用量,以便處理大容量檔案時,不會拖垮PHP程式。更改一下程式,在加密時(encryption)時,直接將檔案名稱掉去函数(function)才作處理。會看到結果,因為不再需要在主程式利用『file_get_contents』存取數據再傳遞給函數,所以PHP記憶體用量,只為原來的2/3左右。

<?php
$starttime = microtime(true);
define('FILE_ENCRYPTION_BLOCKS', 100000);
define('FILE_DECRYPTION_BLOCKS', 100016);

function encrypt($data, $key) {
$source = file_get_contents($data);
$cipher = 'aes-256-cbc';
$ivLenght = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivLenght);
$output = $iv;
$i = 0;
while ($i < strlen($source)) {
$plaintext = substr($source, $i, FILE_ENCRYPTION_BLOCKS);
$i = $i + FILE_ENCRYPTION_BLOCKS;
$ciphertext = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_RAW_DATA, $iv);
$output .= $ciphertext;
}
return $output;
}

function decrypt($source, $key) {
$cipher = 'aes-256-cbc';
$ivLenght = openssl_cipher_iv_length($cipher);
$iv = substr($source, 0, $ivLenght);
$i = strlen($iv);
$output = '';
while ($i < strlen($source)) {
$ciphertext = substr($source, $i, FILE_DECRYPTION_BLOCKS);
$i = $i + FILE_DECRYPTION_BLOCKS;
$plaintext = openssl_decrypt($ciphertext, $cipher, $key, OPENSSL_RAW_DATA, $iv);
$output .= $plaintext;
}
return $output;
}

$data = 'gao.png';
$password = 'password';
$encrypted = encrypt($data, $password);
$decrypted = decrypt($encrypted, $password);
$endtime = microtime(true);
echo 'Memory usage: ' . round(memory_get_usage() / 1048576, 2).'MB and Time used: '.round($endtime - $starttime, 5).'sec<br><br>';
echo 'Showing the result:<br>';
$imageData = base64_encode($decrypted);
$src = 'data: image/png;base64,'.$imageData;
echo '<img src="'.$src.'" style="max-width: 400px;">';
?>

03. 第三個想法,就如『Antoine Lamé Blog』的做法,以檔案方式去處理。PHP記憶體用量只用了0.38MB,對比第一個方法,記憶體只用了1/6左右。對比第二個方法,記憶體只用了1/5左右。

<?php
$starttime = microtime(true);
define('FILE_ENCRYPTION_BLOCKS', 10000);
define('FILE_DECRYPTION_BLOCKS', 10016);

function encrypt($source, $dest, $key) {
$cipher = 'aes-256-cbc';
$ivLenght = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivLenght);
$fpSource = fopen($source, 'rb');
$fpDest = fopen($dest, 'w');
fwrite($fpDest, $iv);
while (! feof($fpSource)) {
$plaintext = fread($fpSource, FILE_ENCRYPTION_BLOCKS);
$ciphertext = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_RAW_DATA, $iv);
fwrite($fpDest, $ciphertext);
}
fclose($fpSource);
fclose($fpDest);
}

function decrypt($source, $dest, $key) {
$cipher = 'aes-256-cbc';
$ivLenght = openssl_cipher_iv_length($cipher);
$fpSource = fopen($source, 'rb');
$fpDest = fopen($dest, 'w');
$iv = fread($fpSource, $ivLenght);
while (! feof($fpSource)) {
$ciphertext = fread($fpSource, FILE_DECRYPTION_BLOCKS);
$plaintext = openssl_decrypt($ciphertext, $cipher, $key, OPENSSL_RAW_DATA, $iv);
fwrite($fpDest, $plaintext);
}
fclose($fpSource);
fclose($fpDest);
}

$image = 'gao.png';
$password = 'password';
encrypt($image, 'encrypted.png', $password);
decrypt('encrypted.png', 'output.png', $password);
$endtime = microtime(true);
echo 'Memory usage: ' . round(memory_get_usage() / 1048576, 2).'MB and Time used: '.round($endtime - $starttime, 5).'sec<br><br>';
echo 'Showing the result:<br>';
echo '<img src="output.png" style="max-width: 400px;">';
?>

04. 我想到的問題,是用了第三個方法,某程度上,原來未經加密的檔案,便會出現在網上伺服器的硬碟上,這似乎已違反原來的想法。檔案可以利用第三個方法先加密,再利用第二個方法作出解密。所以以下例子,只針對已加密的檔案作出處理。今次只作解密,只用了PHP記憶體用量1.09MB,即是和原來一次性讀取檔案差不多。對於大容量檔案,始終不是解決辦法。

<?php
$starttime = microtime(true);
define('FILE_ENCRYPTION_BLOCKS', 10000);
define('FILE_DECRYPTION_BLOCKS', 10016);

function decrypt($source, $key) {
$cipher = 'aes-256-cbc';
$ivLenght = openssl_cipher_iv_length($cipher);
$fpSource = fopen($source, 'rb');
$iv = fread($fpSource, $ivLenght);
$output = '';
while (! feof($fpSource)) {
$ciphertext = fread($fpSource, FILE_DECRYPTION_BLOCKS);
$plaintext = openssl_decrypt($ciphertext, $cipher, $key, OPENSSL_RAW_DATA, $iv);
$output .= $plaintext;
}
fclose($fpSource);
return $output;
}

$image = 'gao.png';
$password = '123456';
$decrypted = decrypt('encrypted.png', $password);
$endtime = microtime(true);
echo 'Memory usage: ' . round(memory_get_usage() / 1048576, 2).'MB and Time used: '.round($endtime - $starttime, 5).'sec<br><br>';
echo 'Showing the result:<br>';
$imageData = base64_encode($decrypted);
$src = 'data: image/png;base64,'.$imageData;
echo '<img src="'.$src.'" style="max-width: 400px;">';
?>

05. 總括而言,為減少PHP記憶體用量,就只能借用檔案存儲的方式作為橋樑,以避免PHP記憶體被佔用,但就會引伸被解壓檔案,會有可能被第三者存取。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *