IT Knowledge Base

~ Freedom is the right of all sentient beings ~

發佈日期:

分類:

, ,

如何在PHP中‧利用Sodium對大容量檔案作出加密/解密動作

01. 早幾天終於解決了PHP函數Sodium的問題,今天就來到實戰時候,要如何對大容量檔案作出加密/解密動作。

02. PHP函數Sodium加密比較簡單,只要用『sodium_crypto_secretbox_keygen()』及『random_bytes()』產生的隨機數值『$keygen』及加密安全的隨機位元組(cryptographically secure random bytes)『$nonce』,便可以用『sodium_crypto_secretbox』函數對數據進行加密。

<?php
$keygen = sodium_crypto_secretbox_keygen();
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = sodium_crypto_secretbox('Your message', $nonce, $keygen);
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $keygen);
if ($plaintext === false) {
throw new Exception("Bad ciphertext");
}
echo $plaintext;
?>

03. 對於大檔案的做法,依然是將要原來的檔案以『FILE_ENCRYPTION_BLOCKS』設定來斬細,利用『sodium_crypto_secretbox』函數對數據進行加密。而加密時用到的『$keygen』及『$nonce』,會再用『OpenSSL』函數,以用戶設定的密碼再作一次加密,最後就是將所有資料(除用戶密碼),寫入加密檔案。以下例子,『$source』為要加密檔案,『$dest』為加密後檔案,『$key』為用戶用於OpenSSL加密時密碼。

<?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)) {
$plaintext = fread($fpSource, FILE_ENCRYPTION_BLOCKS);
$keygen = sodium_crypto_secretbox_keygen();
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$iv = openssl_random_pseudo_bytes($ivLenght);
$cipherkey = openssl_encrypt($keygen.$nonce, $cipher, $key, $options=0, $iv, $tag);
$ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $keygen);
fwrite($fpDest, $cipherkey.$iv.$tag.$ciphertext);
}
fclose($fpSource);
fclose($fpDest);
}
?>

04. 解密方面,『$source』為已加密檔案,『$dest』為解密後檔案,『$key』為用戶加密時曾輸入的密碼。首先需要知道OpenSSL加密『$nonce』及『$keygen』時所在位置、長度及排列方式,再讀取加密內容,以『sodium_crypto_secretbox_open』函數作出解密。以上面『FILE_ENCRYPTION_BLOCKS』設定為10000為例,其加密後長度就是10016,所以『FILE_DECRYPTION_BLOCKS』設定便是10016。而其他數值長度,可參考下列例子。

<?php
define('FILE_DECRYPTION_BLOCKS', 10016);
define('CIPHERKEY_LENGTH', 76);
define('IV_LENGTH', 12);
define('TAG_LENGTH', 16);
define('KEYEGN_LENGTH', 32);
define('NONCE_LENGTH', 24);

function decrypt($source, $dest, $key) {
$cipher = 'aes-256-gcm';
$ivLenght = openssl_cipher_iv_length($cipher);
$fpSource = fopen($source, 'rb');
$fpDest = fopen($dest, 'w');
while (! feof($fpSource)) {
$cipherkey = fread($fpSource, CIPHERKEY_LENGTH);
$iv = fread($fpSource, IV_LENGTH);
$tag = fread($fpSource, TAG_LENGTH);
$ciphertext = fread($fpSource, FILE_DECRYPTION_BLOCKS);
$prefix = openssl_decrypt($cipherkey, $cipher, $key, $options=0, $iv, $tag);
$keygen = substr($prefix, 0, KEYEGN_LENGTH);
$nonce = substr($prefix, KEYEGN_LENGTH, NONCE_LENGTH);
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $keygen);
fwrite($fpDest, $plaintext);
}
fclose($fpSource);
fclose($fpDest);
}
?>

05. 最終結果。

<?php
define('FILE_ENCRYPTION_BLOCKS', 10000);
define('FILE_DECRYPTION_BLOCKS', 10016);
define('CIPHERKEY_LENGTH', 76);
define('IV_LENGTH', 12);
define('TAG_LENGTH', 16);
define('KEYEGN_LENGTH', 32);
define('NONCE_LENGTH', 24);

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)) {
$plaintext = fread($fpSource, FILE_ENCRYPTION_BLOCKS);
$keygen = sodium_crypto_secretbox_keygen();
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$iv = openssl_random_pseudo_bytes($ivLenght);
$cipherkey = openssl_encrypt($keygen.$nonce, $cipher, $key, $options=0, $iv, $tag);
$ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $keygen);
fwrite($fpDest, $cipherkey.$iv.$tag.$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');
while (! feof($fpSource)) {
$cipherkey = fread($fpSource, CIPHERKEY_LENGTH);
$iv = fread($fpSource, IV_LENGTH);
$tag = fread($fpSource, TAG_LENGTH);
$ciphertext = fread($fpSource, FILE_DECRYPTION_BLOCKS);
$prefix = openssl_decrypt($cipherkey, $cipher, $key, $options=0, $iv, $tag);
$keygen = substr($prefix, 0, KEYEGN_LENGTH);
$nonce = substr($prefix, KEYEGN_LENGTH, NONCE_LENGTH);
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $keygen);
fwrite($fpDest, $plaintext);
}
fclose($fpSource);
fclose($fpDest);
}

$video = 'file.mp4';
$password = 'password';
encrypt($video, 'encrypted.tmp', $password);
decrypt('encrypted.tmp', 'decrypt.mp4', $password);
?>

發佈留言

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