IT Knowledge Base

~ Without sacrifice, there can be no victory ~

發佈日期:

分類:

, ,

如何在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. 最後,加密程式在將檔案加密後可以刪除,以便沒有人知道加密方式。但解密程式因為需要每次檔案解密時也需要用到,所以最好是將解密程式同時以加密處理,在需要時先作解密,就可以避免有心偷取檔案,便知道加密原理而作逆向破解。

發佈留言

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