發佈日期:
分類:
如何在PHP中‧利用OpenSSL (aes-256-gcm)對大容量檔案作出加密/解密動作
01. 之前在PHP中,利用OpenSSL對檔案作出加密/解密動作。因應需要減少PHP記憶體使用,所以利用了檔案儲存方式,將要加密/解密檔案切割為一小份作加密/解密處理,再儲存有關部份。
02. 明白了有關原理,就可以更改其他OpenSSL密碼演算法(cipher algo)。
03. 之前用的是『AES-256-CBC』,今次改用另一種『AES-256-GCM』。
<?php define('FILE_ENCRYPTION_BLOCKS', 10000); function encrypt($source, $dest, $key) { $cipher = 'aes-256-gcm'; $ivLenght = openssl_cipher_iv_length($cipher); $fpSource = fopen($source, 'rb'); $fpDest = fopen($dest, 'w'); while (! feof($fpSource)) { $iv = openssl_random_pseudo_bytes($ivLenght); fwrite($fpDest, $iv); $plaintext = fread($fpSource, FILE_ENCRYPTION_BLOCKS); $ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv, $tag); fwrite($fpDest, $tag); fwrite($fpDest, $ciphertext); } fclose($fpSource); fclose($fpDest); } $video = 'test.mp4'; $password = 'mypassword'; encrypt($video, 'encrypted.tmp', $password); ?>
04. 『AES-256-GCM』相對於『AES-256-CBC』,在加密時,會多了一個『$tag』輸出,每次需要解密時,除了要知道『$key (密碼)』、『$cipher (密鑰)』、還要知道每次加密之後,產生的『$tag (驗證標籤)』,才可以解密相關資料。所以某程度上,『AES-256-GCM』比『AES-256-CBC』相對安全。
PHP (AES-256-CBC)指令: openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
PHP (AES-256-GCM)指令: openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv, $tag);
05. 但對於上面加密程式碼,因為我們將檔案拆細處理時,會先將隨機產生的『$cipher (密鑰)』及加密時產生的『$tag (驗證標籤)』,直接以明文方式寫入加密檔案,所以如果有心人能知道『FILE_ENCRYPTION_BLOCKS』數量,再知道『$key (密碼)』,就有可能解密出原來資料(因為4個密碼中,已有2個出現在加密檔案之內)。另外,如果要加密檔案容量低於『FILE_ENCRYPTION_BLOCKS』數量,檔案根本不會出現拆細情況,有心人更能容易解密出原來資料。
06. 因應上述情況,在處理隨機產生的『$cipher (密鑰)』及加密時產生的『$tag (驗證標籤)』,會再將此資料加密一次,才儲存到加密檔案的標頭(header)。所以有關程式碼會變成以下內容。
<?php define('FILE_ENCRYPTION_BLOCKS', 10000); function encrypt($source, $dest, $key) { $cipher = 'aes-256-gcm'; $ivLenght = openssl_cipher_iv_length($cipher); $fpSource = fopen($source, 'rb'); $fpDest = fopen($dest, 'w'); while (! feof($fpSource)) { $iv = openssl_random_pseudo_bytes($ivLenght); $plaintext = fread($fpSource, FILE_ENCRYPTION_BLOCKS); $ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv, $tag); $prefix = $iv.$tag; $iv1 = openssl_random_pseudo_bytes($ivLenght); $cipherkey = openssl_encrypt($prefix, $cipher, $key, $options=0, $iv1, $tag); $key1 = $iv1.$tag.$cipherkey; fwrite($fpDest, $key1); fwrite($fpDest, $ciphertext); } fclose($fpSource); fclose($fpDest); } $video = 'test.mp4'; $password = 'mypassword'; encrypt($video, 'encrypted.tmp', $password); ?>
07. 將上面程式碼加入相關輸出指令,便可以看到解密時需要的參數。
<?php define('FILE_ENCRYPTION_BLOCKS', 10000); function encrypt($source, $dest, $key) { $cipher = 'aes-256-gcm'; $ivLenght = openssl_cipher_iv_length($cipher); $fpSource = fopen($source, 'rb'); $fpDest = fopen($dest, 'w'); while (! feof($fpSource)) { $iv = openssl_random_pseudo_bytes($ivLenght); $plaintext = fread($fpSource, FILE_ENCRYPTION_BLOCKS); $ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv, $tag); $prefix = $iv.$tag; $iv1 = openssl_random_pseudo_bytes($ivLenght); $cipherkey = openssl_encrypt($prefix, $cipher, $key, $options=0, $iv1, $tag); $key1 = $iv1.$tag.$cipherkey; echo 'ivLenght: '.strlen($iv1).'<br>'; echo 'tag: '.strlen($tag).'<br>'; echo 'cipherkey: '.strlen($cipherkey).'<br>'; echo 'ciphertext: '.strlen($ciphertext).'<br>'; exit(); fwrite($fpDest, $key1); fwrite($fpDest, $ciphertext); } fclose($fpSource); fclose($fpDest); } $video = 'test.mp4'; $password = 'mypassword'; encrypt($video, 'encrypted.tmp', $password); ?>
08. 針對解密程式碼,就是需要逆向加密時的想法,先存取已加密的的標頭(header),解密出『$cipher (密鑰)』及加密時產生的『$tag (驗證標籤)』,再用此解密原來內容。而從上述輸出圖表,就知道為何解密時,『FILE_DECRYPTION_BLOCKS』需要設定為『13336』,以及其他需要的解密長度及參數。
<?php define('FILE_ENCRYPTION_BLOCKS', 10000); define('FILE_DECRYPTION_BLOCKS', 13336); function encrypt($source, $dest, $key) { $cipher = 'aes-256-gcm'; $ivLenght = openssl_cipher_iv_length($cipher); $fpSource = fopen($source, 'rb'); $fpDest = fopen($dest, 'w'); $fpDest1 = fopen('test', 'w'); while (! feof($fpSource)) { $iv = openssl_random_pseudo_bytes($ivLenght); $plaintext = fread($fpSource, FILE_ENCRYPTION_BLOCKS); $ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv, $tag); $prefix = $iv.$tag; $iv1 = openssl_random_pseudo_bytes($ivLenght); $cipherkey = openssl_encrypt($prefix, $cipher, $key, $options=0, $iv1, $tag1); $key1 = $iv1.$tag1.$cipherkey; fwrite($fpDest, $key1); fwrite($fpDest, $ciphertext); } fclose($fpSource); fclose($fpDest); } function decrypt($source, $dest, $key) { $cipher = 'aes-256-gcm'; $ivLenght = openssl_cipher_iv_length($cipher); $fpSource = fopen($source, 'rb'); $fpDest = fopen($dest, 'w'); $fpDest1 = fopen('test1', 'w'); while (! feof($fpSource)) { $key1 = fread($fpSource, 68); $iv1 = substr($key1, 0, 12); $tag1 = substr($key1, 12, 16); $cipherkey = substr($key1, 28, 40); $prefix = openssl_decrypt($cipherkey, $cipher, $key, $options=0, $iv1, $tag1); $iv = substr($prefix, 0, 12); $tag = substr($prefix, 12, 16); $ciphertext = fread($fpSource, FILE_DECRYPTION_BLOCKS); $plaintext = openssl_decrypt($ciphertext, $cipher, $key, $options=0, $iv, $tag); fwrite($fpDest, $plaintext); } fclose($fpSource); fclose($fpDest); } $video = 'test.mp4'; $password = 'mypassword'; encrypt($video, 'encrypted.tmp', $password); decrypt('encrypted.tmp', 'output.mp4', $password); ?>
09. 最後,加密程式在將檔案加密後可以刪除,以便沒有人知道加密方式。但解密程式因為需要每次檔案解密時也需要用到,所以最好是將解密程式同時以加密處理,在需要時先作解密,就可以避免有心偷取檔案,便知道加密原理而作逆向破解。
發佈留言