JavaScript >> Javascript 文檔 >  >> jQuery

使用 JavaScript 創建文件加密應用程序

安全和隱私是當下的熱門話題。這是我們對我們處理安全的方式進行反省的機會。這完全是一個妥協的問題——方便與完全鎖定。今天的教程試圖將兩者結合起來。

我們今天要構建的應用程序是一個實驗,它允許人們從他們的計算機中選擇文件並在客戶端使用密碼對其進行加密。不需要服務器端代碼,客戶端和服務器之間也不會傳輸任何信息。為此,我們將使用 HTML5 FileReader API 和 JavaScript 加密庫 - CryptoJS。

請注意,該應用程序不會加密實際文件,而是對其副本進行加密,因此您不會丟失原始文件。但在我們開始之前,這裡有一些問題和限制:

問題和限制

1MB 限制

如果你玩這個演示,你會注意到它不允許你加密大於 1mb 的文件。我設置了限制,因為 HTML5 download 我用來提供加密文件以供下載的屬性不能很好地處理大量數據。否則會導致 Chrome 中的 tab 崩潰,使用 Firefox 時整個瀏覽器崩潰。解決這個問題的方法是使用文件系統 API 並在那裡寫入實際的二進制數據,但目前僅在 Chrome 中支持。這不是加密速度(相當快)的問題,而是提供下載文件的問題。

HTTPS 呢?

當談到加密數據和保護信息時,人們自然希望通過 HTTPS 加載頁面。在這種情況下,我認為沒有必要,因為除了 HTML 和資產的初始下載之外,您和服務器之間不會傳輸任何數據 - 一切都是在客戶端使用 JavaScript 完成的。如果這讓您感到困擾,您可以下載演示並直接從您的計算機上打開它。

它有多安全?

我使用的庫——CryptoJS——是開源的,所以我相信它是值得信賴的。我使用集合中的 AES 算法,該算法已知是安全的。為獲得最佳效果,請使用難以猜測的長密碼短語。

HTML

應用程序的標記由一個常規的 HTML5 文檔和一些 div 組成,這些 div 將應用程序分成幾個單獨的屏幕。您將在本教程的 JavaScript 和 CSS 部分看到它們如何交互。

index.html

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8"/>
        <title>JavaScript File Encryption App</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link href="http://fonts.googleapis.com/css?family=Raleway:400,700" rel="stylesheet" />
        <link href="assets/css/style.css" rel="stylesheet" />
    </head>

    <body>

        <a class="back"></a>

        <div id="stage">

            <div id="step1">
                <div class="content">
                    <h1>What do you want to do?</h1>
                    <a class="button encrypt green">Encrypt a file</a>
                    <a class="button decrypt magenta">Decrypt a file</a>
                </div>
            </div>

            <div id="step2">

                <div class="content if-encrypt">
                    <h1>Choose which file to encrypt</h1>
                    <h2>An encrypted copy of the file will be generated. No data is sent to our server.</h2>
                    <a class="button browse blue">Browse</a>

                    <input type="file" id="encrypt-input" />
                </div>

                <div class="content if-decrypt">
                    <h1>Choose which file to decrypt</h1>
                    <h2>Only files encrypted by this tool are accepted.</h2>
                    <a class="button browse blue">Browse</a>

                    <input type="file" id="decrypt-input" />
                </div>

            </div>

            <div id="step3">

                <div class="content if-encrypt">
                    <h1>Enter a pass phrase</h1>
                    <h2>This phrase will be used as an encryption key. Write it down or remember it; you won't be able to restore the file without it. </h2>

                    <input type="password" />
                    <a class="button process red">Encrypt!</a>
                </div>

                <div class="content if-decrypt">
                    <h1>Enter the pass phrase</h1>
                    <h2>Enter the pass phrase that was used to encrypt this file. It is not possible to decrypt it without it.</h2>

                    <input type="password" />
                    <a class="button process red">Decrypt!</a>
                </div>

            </div>

            <div id="step4">

                <div class="content">
                    <h1>Your file is ready!</h1>
                    <a class="button download green">Download</a>
                </div>

            </div>
        </div>

    </body>

    <script src="assets/js/aes.js"></script>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script src="assets/js/script.js"></script>

</html>

一次只能看到一個 step div。根據用戶的選擇——加密或解密——在 body 元素上設置一個類名。使用 CSS,此類名稱隱藏帶有 if-encrypt 的元素 或 if-decrypt 類。這個簡單的門控讓我們能夠編寫出與 UI 相關的更簡潔的 JavaScript。

JavaScript 代碼

正如我在介紹中提到的,我們將一起使用 HTML5 FileReader API(支持)和 CryptoJS 庫。 FileReader 對象允許我們使用 JavaScript 讀取本地文件的內容,但只能讀取用戶通過文件輸入的瀏覽對話框明確選擇的文件。您可以在下面的代碼中看到這是如何完成的。請注意,大部分代碼處理應用程序不同屏幕之間的轉換,文件的實際讀取發生在第 85 行。

assets/js/script.js

$(function(){

    var body = $('body'),
        stage = $('#stage'),
        back = $('a.back');

    /* Step 1 */

    $('#step1 .encrypt').click(function(){
        body.attr('class', 'encrypt');

        // Go to step 2
        step(2);
    });

    $('#step1 .decrypt').click(function(){
        body.attr('class', 'decrypt');
        step(2);
    });

    /* Step 2 */

    $('#step2 .button').click(function(){
        // Trigger the file browser dialog
        $(this).parent().find('input').click();
    });

    // Set up events for the file inputs

    var file = null;

    $('#step2').on('change', '#encrypt-input', function(e){

        // Has a file been selected?

        if(e.target.files.length!=1){
            alert('Please select a file to encrypt!');
            return false;
        }

        file = e.target.files[0];

        if(file.size > 1024*1024){
            alert('Please choose files smaller than 1mb, otherwise you may crash your browser. \nThis is a known issue. See the tutorial.');
            return;
        }

        step(3);
    });

    $('#step2').on('change', '#decrypt-input', function(e){

        if(e.target.files.length!=1){
            alert('Please select a file to decrypt!');
            return false;
        }

        file = e.target.files[0];
        step(3);
    });

    /* Step 3 */

    $('a.button.process').click(function(){

        var input = $(this).parent().find('input[type=password]'),
            a = $('#step4 a.download'),
            password = input.val();

        input.val('');

        if(password.length<5){
            alert('Please choose a longer password!');
            return;
        }

        // The HTML5 FileReader object will allow us to read the 
        // contents of the  selected file.

        var reader = new FileReader();

        if(body.hasClass('encrypt')){

            // Encrypt the file!

            reader.onload = function(e){

                // Use the CryptoJS library and the AES cypher to encrypt the 
                // contents of the file, held in e.target.result, with the password

                var encrypted = CryptoJS.AES.encrypt(e.target.result, password);

                // The download attribute will cause the contents of the href
                // attribute to be downloaded when clicked. The download attribute
                // also holds the name of the file that is offered for download.

                a.attr('href', 'data:application/octet-stream,' + encrypted);
                a.attr('download', file.name + '.encrypted');

                step(4);
            };

            // This will encode the contents of the file into a data-uri.
            // It will trigger the onload handler above, with the result

            reader.readAsDataURL(file);
        }
        else {

            // Decrypt it!

            reader.onload = function(e){

                var decrypted = CryptoJS.AES.decrypt(e.target.result, password)
                                        .toString(CryptoJS.enc.Latin1);

                if(!/^data:/.test(decrypted)){
                    alert("Invalid pass phrase or file! Please try again.");
                    return false;
                }

                a.attr('href', decrypted);
                a.attr('download', file.name.replace('.encrypted',''));

                step(4);
            };

            reader.readAsText(file);
        }
    });

    /* The back button */

    back.click(function(){

        // Reinitialize the hidden file inputs,
        // so that they don't hold the selection 
        // from last time

        $('#step2 input[type=file]').replaceWith(function(){
            return $(this).clone();
        });

        step(1);
    });

    // Helper function that moves the viewport to the correct step div

    function step(i){

        if(i == 1){
            back.fadeOut();
        }
        else{
            back.fadeIn();
        }

        // Move the #stage div. Changing the top property will trigger
        // a css transition on the element. i-1 because we want the
        // steps to start from 1:

        stage.css('top',(-(i-1)*100)+'%');
    }

});

我以數據 uri 字符串(支持)的形式獲取文件的內容。瀏覽器允許您在常規 URL 的任何地方使用這些 URI。好處是它們讓您可以將資源的內容直接存儲在 URI 中,因此我們可以例如將文件的內容放置為 href 的鏈接,並添加 download 屬性(閱讀更多),以強制它在單擊時作為文件下載。

我使用 AES 算法來加密 data uri 使用選擇的密碼,並將其作為下載提供。解密時會發生相反的情況。沒有數據到達服務器。您甚至不需要服務器,您可以直接從計算機上的文件夾中打開 HTML,並按原樣使用。

CSS

我將在這裡只展示 CSS 中更有趣的部分,您可以從可下載的 zip 中查看樣式表中的其餘部分。首先要介紹的是創建佈局的樣式以及通過更改 top 在屏幕之間平滑滾動的能力 #stage 的屬性 元素。

assets/css/styles.css

body{
    font:15px/1.3 'Raleway', sans-serif;
    color: #fff;
    width:100%;
    height:100%;
    position:absolute;
    overflow:hidden;
}

#stage{
    width:100%;
    height:100%;
    position:absolute;
    top:0;
    left:0;

    transition:top 0.4s;
}

#stage > div{  /* The step divs */
    height:100%;
    position:relative;
}

#stage h1{
    font-weight:normal;
    font-size:48px;
    text-align:center;
    color:#fff;
    margin-bottom:60px;
}

#stage h2{
    font-weight: normal;
    font-size: 14px;
    font-family: Arial, Helvetica, sans-serif;
    margin: -40px 0 45px;
    font-style: italic;
}

.content{
    position:absolute;
    text-align:center;
    left:0;
    top:50%;
    width:100%;
}

因為 step div 被設置為 100% 的寬度和高度,所以它們會自動佔據瀏覽器窗口的完整尺寸,而無需調整大小。

另一段有趣的代碼,是大大簡化了我們的 JavaScript 的條件類:

[class*="if-"]{
    display:none;
}

body.encrypt .if-encrypt{
    display:block;
}

body.decrypt .if-decrypt{
    display:block;
}

這樣,主體的加密和解密類控制具有相應 if-* 的元素的可見性 類。

我們完成了!

有了這個,我們的 JavaScript 加密應用程序就準備好了!您可以使用它與朋友分享圖片和文檔,方法是向他們發送使用預先同意的密碼短語加密的版本。或者您可以將應用程序的 HTML 連同您的加密文件一起放在閃存驅動器上,然後直接打開 index.html 進行解密。


Tutorial JavaScript 教程
  1. 使用 yarn start 打開我的應用程序:錯誤命令失敗,退出代碼為 1

  2. 沒有標準方法時 Express REST API 的項目結構

  3. nuxt 2.14 generate 不更新 asyncData

  4. 使用 Vue-Router 構建 SPA

  5. Axios 中帶有正文的 HTTP DELETE 請求

  6. Firebase 函數中的空 functions.config()

  7. 如何使用 Kendo UI 實現豐富的 Angular 通知(又名 Toast、Snackbar)

  1. TypeError:無法讀取未定義的屬性(讀取“等於”)[關閉]

  2. 無法讀取未定義的屬性“長度” - 按下“Enter”鍵後,值變為未定義

  3. React 2021 中的狀態管理之戰:Hooks、Redux 和 Recoil

  4. 如何修復 Vuejs 中的 CORS 問題

  5. 所有關於.... Z-索引?

  6. 使用 Vue 構建 RSS 查看器:第 2 部分

  7. 學習 JavaScript 的簡單方法:JavaScript 語法

  1. 在不到 10 分鐘的時間內設置一個 Node/ExpressJS API 項目

  2. React 的惰性函數是如何實現的?懸念......它會很短😉

  3. 介紹 Authorizer.dev

  4. 鍊錶 JavaScript:使用 ES6 理解和實現 JavaScript 中的鍊錶 - 實現