Создание приложения для шифрования файлов с помощью JavaScript

Безопасность и конфиденциальность — это одни из самых актуальных тем на сегодняшний день. Это заставляет нас еще раз пересмотреть подходы к безопасности. И, как всегда, все зависит от оптимального баланса — удобства и факторов риска. Сегодня мы попытаемся найти формулу их оптимального соотношения.

Приложение, которое мы собираемся сегодня создать — это эксперимент. Оно позволит пользователям выбирать на своих компьютерах файлы и шифровать их на стороне клиента с использованием кода доступа.

ДЕМО СКАЧАТЬ

шифрование файлов

Нет, в серверном коде необходимости не будет, никакая информация между клиентом и сервером передаваться не будет. Чтобы сделать это, мы будем использовать HTML5 FileReader API, и библиотеку шифрования JavaScript — CryptoJS.

Обратите внимание, что приложение будет шифровать не сам файл, а его копию, так что оригинал в любом случае у вас сохранится.

Но прежде чем мы начнем, вот несколько проблем и ограничений, с которыми нам придется столкнуться:

Проблемы и ограничения

Лимит в 1 Мб

Если вы попробуете поработать с демонстрационной версией, то заметите, что она не позволяет шифровать файлы размером более 1 МБ. Я установил такой лимит, потому что атрибут HTML5 download, который я использую для запроса на загрузку файла для шифрования, не слишком хорошо работает с большими объемами данных.

При загрузке больших файлов текущая вкладка в браузере Google Chrome просто зависает, при использовании Firefox вылетает весь процесс браузера.

Способом решить эту проблему могло бы стать использование File System API и запись в нем двоичных данных, но на данный момент это приложение поддерживается только в Google Chrome.

Причем, на скорости шифрования файла размер не сказывается, это проблема только для возможности скачивания файлов.

Как насчет HTTPS?

Когда речь идет о шифровании данных и обеспечении конфиденциальности информации, люди, естественно, ожидают, что страницы должны загружаться через HTTPS. В данном случае я считаю, что необходимости в этом нет.

Так как кроме стартовой загрузки HTML и активных элементов, данные между вами и сервером не передаются — все делается на стороне клиента с помощью JavaScript. Если это все же беспокоит вас, вы можете просто скачать демо-версию и запустить ее прямо на вашем компьютере.

Насколько это безопасно?

Библиотека, которую я использую — CryptoJS — это программное обеспечение с открытым исходным кодом, так что я считаю, что она заслуживает доверия.

Я использовал алгоритм AES из коллекции, которая, насколько мне известно, безопасна. Для большей безопасности используйте длинные пароли, которые трудно подобрать:

CryptoJS

JavaScript File Encryption App

HTML

Разметка приложения состоит из обычного документа HTML5 и нескольких блоков, которые разделяют приложение на несколько отдельных экранов. Вы увидите, как они взаимодействуют в разделах 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>Что вы хотите сделать?</h1>
                    <a class="button encrypt green">Зашифровать файл</a>
                    <a class="button decrypt magenta">Расшифровать файл</a>
                </div>
            </div>

            <div id="step2">

                <div class="content if-encrypt">
                    <h1>Выберите шифруемый файл</h1>
                    <h2>Будет сгенерирована зашифрованная копия файла. Никакие данные на наш сервер не пересылаются.</h2>
                    <a class="button browse blue">Обзор</a>

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

                <div class="content if-decrypt">
                    <h1> Выберите файл для расшифровки</h1>
                    <h2>Только файлы зашифрованные с помощью данного инструмента.</h2>
                    <a class="button browse blue">Обзор</a>

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

            </div>

            <div id="step3">

                <div class="content if-encrypt">
                    <h1>Введите пароль</h1>
                    <h2>Данный пароль будет использоваться в качестве ключа для шифрования. Запишите его или запомните; без него вы не сможете восстановить файл. </h2>

                    <input type="password" />
                    <a class="button process red">Расшифровать!</a>
                </div>

                <div class="content if-decrypt">
                    <h1>Введите пароль.</h1>
                    <h2>Введите пароль, который использовался при шифровке файла. Без него вы не сможете расшифровать файл.</h2>

                    <input type="password" />
                    <a class="button process red">Зашифровать!</a>
                </div>

            </div>

            <div id="step4">

                <div class="content">
                    <h1>Ваш файл готов!</h1>
                    <a class="button download green">Скачать</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>

Во время работы приложения только один из блоков выполнения виден на экране. В зависимости от выбора пользователя — шифрование или расшифровка — для элемента тела устанавливается имя класса.

С помощью CSS это имя класса определяет скрываются или выводятся элементы с классами if-encrypt или if-decrypt. Этот простой способ разделения позволяет обеспечить более чистый код, в котором минимально присутствуют элементы интерфейсов:

index.html

Выбор файла для шифрования

Код JavaScript

Как я уже упоминал, мы собираемся использовать HTML5 FileReader API (поддержка) и библиотеку CryptoJS.

Объект FileReader позволяет нам читать содержимое локальных файлов с помощью JavaScript, но только тех файлов, которые были выбраны пользователем непосредственно через диалоговое окно, предлагающее выбрать файл.

Как это делается, вы можете понять при рассмотрении ниже приведенного кода. Обратите внимание, что большая часть кода обрабатывает переходы между различными экранами приложения, а само считывание файла происходит, начиная со строки 85:

assets/js/script.js
$(function(){

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

    /* Шаг 1 */

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

        // Переход к шагу 2
        step(2);
    });

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

    /* Шаг 2 */

    $('#step2 .button').click(function(){
        // Вызов окна выбора файла
        $(this).parent().find('input').click();
    });

    // Определение действия ввода файла

    var file = null;

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

        // Файл выбран?

        if(e.target.files.length!=1){
            alert('Пожалуйста введите файл для шифрования!');
            return false;
        }

        file = e.target.files[0];

        if(file.size > 1024*1024){
            alert('Пожалуйста выберите файл размером меньше, чем 1 Мбайт, в противном случае ваш браузер может быть перегружен. nЭто распространенная проблема. См. пособие.');
            return;
        }

        step(3);
    });

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

        if(e.target.files.length!=1){
            alert('Пожалуйста выберите файл для расшифровки!');
            return false;
        }

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

    /* Шаг 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('Пожалуйста придумайте более длинный пароль!');
            return;
        }

        // Объект HTML5 FileReader позволяет нам считывать содержимое 
        // выбранного файла.

        var reader = new FileReader();

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

            // Зашифровать файл!

            reader.onload = function(e){

                // Используем библиотеку CryptoJS и шифр AES, чтобы 
                // зашифровать содержимое файла, который содержится в
                //  e.target.result, с паролем

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

                // Скачиваем атрибут, который по ссылке вызывает контент,
                // при  нажатии на ссылку происходит загрузка содержимого.
                // Он также содержит имя файла, который предлагается
                 // скачать.

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

                step(4);
            };

            // Данный фрагмент кодирует содержимое файла в data-uri.
            // Это запускает обработку загруженного файла, с результатом 

            reader.readAsDataURL(file);
        }
        else {

            // Расшифровка файла!

            reader.onload = function(e){

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

                if(!/^data:/.test(decrypted)){
                    alert("Неверный пароль или путь к файлу! Пожалуйста, попробуйте еще раз.");
                    return false;
                }

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

                step(4);
            };

            reader.readAsText(file);
        }
    });

    /* Кнопка возврата */

    back.click(function(){

        // Новая инициализация формы выбора файла,
        // таким образом, что в ней не сохраняется сделанный ранее выбор        

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

        step(1);
    });

    // Вспомогательная функция, которая перемещает окно просмотра в блок, соответствующий текущему шагу

    function step(i){

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

        // Перемещение блока #stage. Смена свойства top вызывает
        // css-преобразование элемента i-1 потому что мы хотим, чтобы
        // очередность начиналась с шага 1:

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

});

Я получил содержимое файлов в виде строки uri-данных. Браузеры позволяют использовать эти URI, везде, где могут использоваться обычные URL-адреса.

Их преимущество заключается в том, что они позволяют хранить исходное содержимое непосредственно в URI, поэтому мы можем, например, записать содержимое файла в виде ссылки href , а также добавить к ней атрибут download, чтобы при нажатии она загружалась, как файл.

Я использую алгоритм AES для шифрования URI-данных с выбранным паролем, и предлагаю его для скачивания. Во время расшифровки происходит обратная процедура.

При этом никакие данные на сервер на самом деле не пересылаются. Если на то пошло, вам даже не нужен сервер, вы можете просто открыть HTML-файл непосредственно из папки на вашем компьютере, и использовать его как есть.

Код JavaScript

Ввод пароля

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{  /* Блоки шагов */
    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%;
}

Поскольку для блоков шагов задаются размеры в 100% по ширине и высоте, они автоматически занимают полный размер окна браузера без необходимости дополнительных преобразований.

Еще одним интересным фрагментом кода являются условные классы, которые значительно упрощают наш JavaScript:

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

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

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

Таким образом, классы шифрования и расшифрования тела приложения управляют видимостью элементов, которые имеют соответствующий класс if-*.

Мы закончили!

На этом наше JavaScript-приложение для шифрования данных готово! Вы можете использовать его, чтобы поделиться фотографиями и документами с друзьями, отправив им зашифрованную версию файла с предварительно оговоренной кодовой фразой.

Или можете записать HTML-код приложения на флешку, вместе с вашими зашифрованными файлами и расшифровать их напрямую, открыв файл index.html.

Перевод статьи «Creating a File Encryption App with JavaScript» был подготовлен дружной командой проекта Сайтостроение от А до Я.