發佈日期:
分類:
如何在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記憶體被佔用,但就會引伸被解壓檔案,會有可能被第三者存取。
發佈留言