-
+ 'init-vault-form', 'action' => ['vault/init'], 'method' => 'post']); ?>
= $form->field($model, 'input_vault_secret')->label('保险箱密码(建议不要与登陆密码相同)')->passwordInput(['autofocus' => true]) ?>
= Html::submitButton('初始化保险箱', ['class' => 'btn btn-primary']) ?>
diff --git a/views/vault/index.php b/views/vault/index.php
index 79d1a30..76acbf2 100644
--- a/views/vault/index.php
+++ b/views/vault/index.php
@@ -120,7 +120,10 @@ $this->registerCssFile('@web/css/home_style.css');
= Html::tag('i', '', ['class' => $item['type'] . ' file_icon']) ?>
- = Html::a($item['name'], ['vault/download', 'relativePath' => $relativePath], ['class' => 'file_name']) ?>
+ $relativePath], ['class' => 'file_name']) ?>
+ = Html::beginTag('span', ['class' => 'file_name']) ?>
+ = $item['name'] ?>
+ = Html::endTag('span') ?>
|
= date('Y-m-d H:i:s', $item['lastModified']) ?>
@@ -134,7 +137,8 @@ $this->registerCssFile('@web/css/home_style.css');
'class' => 'btn btn-outline-primary download-btn',
'data-bs-toggle' => 'tooltip',
'data-bs-placement' => 'top',
- 'data-bs-title' => '下载'
+ 'data-bs-title' => '下载',
+ 'data-filename' => $item['name'],
]) ?>
= Html::button(Html::tag('i', '', ['class' => 'fa-regular fa-trash-can']), ['value' => $relativePath, 'class' => 'btn btn-outline-danger delete-btn', 'data-bs-toggle' => 'tooltip', 'data-bs-placement' => 'top', 'data-bs-title' => '删除']) ?>
|
@@ -160,5 +164,6 @@ echo Html::endForm();
Modal::end();
$this->registerJsFile('@web/js/vault_script.js', ['depends' => [JqueryAsset::class], 'position' => View::POS_END]);
+$this->registerJsFile('@web/js/vault_core.js', ['position' => View::POS_END]);
?>
diff --git a/web/js/vault_core.js b/web/js/vault_core.js
new file mode 100644
index 0000000..f5ecc56
--- /dev/null
+++ b/web/js/vault_core.js
@@ -0,0 +1,121 @@
+const vaultRawKey = sessionStorage.getItem('vaultRawKey');
+async function generateEncryptionKeyFromPassword(password) {
+ const passwordBuffer = new TextEncoder().encode(password);
+ const key = await window.crypto.subtle.importKey(
+ 'raw',
+ passwordBuffer,
+ {name: 'PBKDF2'},
+ false,
+ ['deriveKey']
+ );
+ return await window.crypto.subtle.deriveKey(
+ {
+ name: 'PBKDF2',
+ salt: new Uint8Array([]),
+ iterations: 100000,
+ hash: 'SHA-256'
+ },
+ key,
+ {name: 'AES-GCM', length: 256},
+ false,
+ ['encrypt', 'decrypt']
+ );
+}
+// 加密文件
+async function encryptFile(file, password) {
+ const iv = window.crypto.getRandomValues(new Uint8Array(12)); // 生成随机 IV
+ const passwordBuffer = new TextEncoder().encode(password);
+ const key = await window.crypto.subtle.importKey(
+ 'raw',
+ passwordBuffer,
+ {name: 'PBKDF2'},
+ false,
+ ['deriveKey']
+ );
+ const derivedKey = await window.crypto.subtle.deriveKey(
+ {
+ name: 'PBKDF2',
+ salt: new Uint8Array([]),
+ iterations: 100000,
+ hash: 'SHA-256'
+ },
+ key,
+ {name: 'AES-GCM', length: 256},
+ false,
+ ['encrypt', 'decrypt']
+ );
+
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = function(event) {
+ const plaintextData = event.target.result;
+ window.crypto.subtle.encrypt(
+ { name: 'AES-GCM', iv: iv }, // 使用随机生成的 IV
+ derivedKey,
+ plaintextData
+ ).then(encryptedData => {
+ const encryptedBlob = new Blob([iv, encryptedData], {type: file.type});
+ resolve(encryptedBlob);
+ }).catch(error => {
+ reject(error);
+ });
+ };
+ reader.readAsArrayBuffer(file);
+ });
+}
+
+// 解密文件
+async function decryptFile(encryptedFile, password) {
+ return new Promise((resolve, reject) => {
+ const fileReader = new FileReader();
+ fileReader.onload = async function(event) {
+ try {
+ const encryptedData = new Uint8Array(event.target.result);
+ const iv = encryptedData.slice(0, 12); // 从密文中提取 IV
+ const ciphertext = encryptedData.slice(12);
+ const passwordBuffer = new TextEncoder().encode(password);
+ const key = await window.crypto.subtle.importKey(
+ 'raw',
+ passwordBuffer,
+ {name: 'PBKDF2'},
+ false,
+ ['deriveKey']
+ );
+ const derivedKey = await window.crypto.subtle.deriveKey(
+ {
+ name: 'PBKDF2',
+ salt: new Uint8Array([]),
+ iterations: 100000,
+ hash: 'SHA-256'
+ },
+ key,
+ {name: 'AES-GCM', length: 256},
+ false,
+ ['encrypt', 'decrypt']
+ );
+ const decryptedData = await window.crypto.subtle.decrypt(
+ { name: 'AES-GCM', iv: iv }, // 使用从密文中提取的 IV
+ derivedKey,
+ ciphertext
+ );
+ const decryptedFile = new Blob([decryptedData], { type: encryptedFile.type });
+ resolve(decryptedFile);
+ } catch (error) {
+ reject(error);
+ }
+ };
+ fileReader.readAsArrayBuffer(encryptedFile);
+ });
+}
+async function downloadAndDecryptFile(url, password, filename) {
+ const response = await fetch(url);
+ const encryptedFile = await response.blob();
+ const decryptedFile = await decryptFile(encryptedFile, password);
+ const blob = new Blob([decryptedFile], {type: decryptedFile.type});
+ const blobURL = window.URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = blobURL;
+ link.download = filename;
+ link.click();
+ window.URL.revokeObjectURL(blobURL);
+}
\ No newline at end of file
diff --git a/web/js/vault_gateway_hook.js b/web/js/vault_gateway_hook.js
new file mode 100644
index 0000000..99e4c91
--- /dev/null
+++ b/web/js/vault_gateway_hook.js
@@ -0,0 +1,61 @@
+document.getElementById('gateway-vault-form').addEventListener('submit', function (event) {
+ event.preventDefault();
+ var password = document.getElementById('password').value;
+ sessionStorage.setItem('vaultRawKey', password);
+ this.submit();
+});
+document.addEventListener('DOMContentLoaded', function () {
+ if (!(window.crypto && window.crypto.subtle)) {
+ console.log('浏览器不支持 Crypto API');
+ alert('您的浏览器不支持加密功能,故无法使用文件保险箱功能,请使用现代浏览器。');
+ window.location.href = 'index.php?r=site%2Findex';
+ }
+});
+
+// async function generateEncryptionKeyFromPassword(password) {
+// const passwordBuffer = new TextEncoder().encode(password);
+// const key = await window.crypto.subtle.importKey(
+// 'raw',
+// passwordBuffer,
+// {name: 'PBKDF2'},
+// false,
+// ['deriveKey']
+// );
+// const encryptionKey = await window.crypto.subtle.deriveKey(
+// {
+// name: 'PBKDF2',
+// salt: new Uint8Array([]),
+// iterations: 100000,
+// hash: 'SHA-256'
+// },
+// key,
+// {name: 'AES-GCM', length: 256},
+// false,
+// ['encrypt', 'decrypt']
+// );
+//
+// return encryptionKey;
+// }
+//
+// function cryptoKeyToString(cryptoKey) {
+// return window.crypto.subtle.exportKey('raw', cryptoKey).then(function (keyData) {
+// return String.fromCharCode.apply(null, new Uint8Array(keyData));
+// });
+// }
+//
+// function stringToCryptoKey(keyString) {
+// // 将字符串转换为 Uint8Array
+// var keyData = new Uint8Array(keyString.length);
+// for (var i = 0; i < keyString.length; ++i) {
+// keyData[i] = keyString.charCodeAt(i);
+// }
+//
+// // 使用 importKey 方法导入 CryptoKey 对象
+// return window.crypto.subtle.importKey(
+// 'raw',
+// keyData,
+// {name: 'PBKDF2'},
+// false,
+// ['deriveKey']
+// );
+// }
\ No newline at end of file
diff --git a/web/js/vault_script.js b/web/js/vault_script.js
index a40b4d0..d881d09 100644
--- a/web/js/vault_script.js
+++ b/web/js/vault_script.js
@@ -2,8 +2,15 @@ var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggl
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
});
-$(document).on('click', '.download-btn', function () {
- window.location.href = $(this).attr('value');
+$(document).on('click', '.download-btn', async function() {
+ const downloadUrl = $(this).attr('value');
+ const filename = $(this).data('filename');
+
+ try {
+ await downloadAndDecryptFile(downloadUrl, vaultRawKey, filename);
+ } catch (error) {
+ console.error('Error downloading or decrypting the file:', error);
+ }
});
$(document).on('click', '.delete-btn', function () {
var relativePath = $(this).attr('value');
@@ -16,26 +23,34 @@ $(document).on('click', '.file-upload-btn', function () {
$('#file-input').on('change', function () {
uploadFiles(this.files);
});
-$('#folder-input').on('change', function () {
- uploadFiles(this.files);
-});
$(document).on('click', '.refresh-btn', function () {
window.location.reload();
});
$(document).on('click', '.single-download-btn', function () {
- var relativePath = $('.select-item:checked').first().data('relativePath');
- window.location.href = 'index.php?r=vault%2Fdownload&relativePath=' + encodeURIComponent(relativePath);
+ var downloadBtn = $('.select-item:checked').closest('tr').find('.download-btn');
+ if (downloadBtn.length > 0) {
+ downloadBtn.trigger('click');
+ } else {
+ console.error('No file selected for download.');
+ }
});
-function uploadFiles(files) {
+async function uploadFiles(files) {
+ // 这里问gpt的,加密方面实在不会
$('#progress-bar').show();
var formData = new FormData();
- for (var i = 0; i < files.length; i++) {
- formData.append('files[]', files[i]);
- }
+ var encryptionPromises = Array.from(files).map(file => encryptFile(file, vaultRawKey));
+ var encryptedFiles = await Promise.all(encryptionPromises);
+
+ encryptedFiles.forEach(function (encryptedFile, index) {
+ formData.append('files[]', new File([encryptedFile], files[index].name, {type: files[index].type}));
+ });
+
+ // 添加其他数据到 FormData 中
formData.append('targetDir', $('#target-dir').val());
formData.append('_csrf', $('meta[name="csrf-token"]').attr('content'));
+ // 创建 XMLHttpRequest 对象并发送 FormData
var xhr = new XMLHttpRequest();
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) {