В наши дни всё больше программ переводятся в так называемый «web-ориентированный» вид, то есть используется принцип клиент-сервер, что позволяет хранить данные удалённо и получать к ним доступ через тонкий клиент (браузер).
Одновременно с удобством использования остро встаёт вопрос о защищённости этих данных. Конфиденциальная информация может стать доступна другим людям несколькими путями. Во-первых, к пользователю могут быть применены физические меры для выпытывания. Во-вторых, при передаче данные могут быть перехвачены различными снифферами. И, в-третьих, на сервер могут быть произведены хакерские атаки, что позволит злоумышленникам похитить информацию, либо недобросовестный администратор сервера воспользуется ею в личных целях.
Некоторое время назад у меня возникла задача разработать прототип программы шифрования/дешифрования данных на стороне клиента в web-ориентированных системах.
То есть, было необходимо разработать программу, которая позволит не просто хранить данные на сервере, но и предоставит возможность работы с ними через web-интерфейс и при этом обеспечит их бесполезность для злоумышленников в случае кражи, что достигается шифрованием/дешифрованием исключительно на стороне клиента.
Для типичного сценария использования возможна работа с тремя типами данных:
Тот факт, что обработка данных должна производиться исключительно на стороне клиента, ограничивал выбор средств для реализации. На начальной стадии разработки была опробована связка «Java-апплет – Java-сервлет», но через какое-то время пришлось искать другой способ, потому что были трудности в отладке и передаче данных между апплетом и сервлетом.
Я остановился на использовании возможностей HTML5 и JavaScript-объекта «XmlHttpRequest Level 2» в частности, потому что они позволили с меньшими усилиями реализовать необходимый функционал.
Алгоритм шифрования:
Обратный процесс:
Процесс шифрования/дешифрования файлов происходит немного другим образом.
Алгоритм шифрования:
Обратный процесс:
Алгоритм шифрования:
Обратный процесс:
Немного ключевого исходного кода для работы с файлами:
Исходный код для работы с изображениями:
Я не стал здесь приводить реализацию функций XOREncrypt, XORDecrypt и класса Base64, чтобы не загромождать и без того длинный листинг. Их можно посмотреть в прилагаемом архиве с исходным кодом.
Код Java-апплета для вывода диалога сохранения файла.
Надеюсь, полученные мной результаты сэкономят немного времени тем, кто столкнётся с задачей шифрования/дешифрования данных на стороне клиента в web-ориентированных системах. Исходный код — в прикреплённом архиве.
Одновременно с удобством использования остро встаёт вопрос о защищённости этих данных. Конфиденциальная информация может стать доступна другим людям несколькими путями. Во-первых, к пользователю могут быть применены физические меры для выпытывания. Во-вторых, при передаче данные могут быть перехвачены различными снифферами. И, в-третьих, на сервер могут быть произведены хакерские атаки, что позволит злоумышленникам похитить информацию, либо недобросовестный администратор сервера воспользуется ею в личных целях.
Задача
Некоторое время назад у меня возникла задача разработать прототип программы шифрования/дешифрования данных на стороне клиента в web-ориентированных системах.
То есть, было необходимо разработать программу, которая позволит не просто хранить данные на сервере, но и предоставит возможность работы с ними через web-интерфейс и при этом обеспечит их бесполезность для злоумышленников в случае кражи, что достигается шифрованием/дешифрованием исключительно на стороне клиента.
Для типичного сценария использования возможна работа с тремя типами данных:
- обычный текст, вводимый в поля ввода формы и хранящийся на сервере в базе данных в зашифрованном виде
- файлы, которые хранятся на сервере в зашифрованном виде, и при необходимости пользователь может их скачать
- изображения, хранящиеся на сервере как зашифрованные файлы, но при необходимости они расшифровываются на стороне клиента и вставляются на web-страницу как обычные картинки.
Реализация
Тот факт, что обработка данных должна производиться исключительно на стороне клиента, ограничивал выбор средств для реализации. На начальной стадии разработки была опробована связка «Java-апплет – Java-сервлет», но через какое-то время пришлось искать другой способ, потому что были трудности в отладке и передаче данных между апплетом и сервлетом.
Я остановился на использовании возможностей HTML5 и JavaScript-объекта «XmlHttpRequest Level 2» в частности, потому что они позволили с меньшими усилиями реализовать необходимый функционал.
Работа с текстом
Алгоритм шифрования:
- вносим текст в поле формы на web-странице
- шифруем текст с помощью функций Java Script
- отправляем зашифрованный текст на сервер, где сохраняем в базу данных.
Обратный процесс:
- получаем зашифрованные данные из базы данных с сервера
- дешифруем их с помощью функций Java Script
- выводим расшифрованный текст в нужное место на web-странице.
Работа с файлами
Процесс шифрования/дешифрования файлов происходит немного другим образом.
Алгоритм шифрования:
- выбираем файл с компьютера пользователя
- получаем содержимое файла в объект Java Script, используя XmlHttpRequest Level 2 и возможности HTML 5
- шифруем его с помощью функций Java Script
- отправляем зашифрованные данные на сервер, где сохраняем как файл.
Обратный процесс:
- получаем содержимое зашифрованного файла с сервера
- записываем его в объект Java Script, используя XmlHttpRequest Level 2 и возможности HTML 5
- дешифруем с помощью функций Java Script
- передаём расшифрованные данные в Java-апплет, чтобы дать пользователю возможность указать путь и имя для сохраняемого файла, т. к. на данный момент развития технологий в браузерах нельзя штатно вызывать диалог сохранения файла в произвольное место на компьютере пользователя, только в ограниченную «песочницу», что нам не подходит. Если по каким-либо причинам использование Java-апплета не подходит, эту часть можно заменить на Flash с аналогичным функционалом.
Работа с изображениями
Алгоритм шифрования:
- выбираем файл с изображением с компьютера пользователя
- записываем его содержимое в объект Java Script, используя XmlHttpRequest Level 2 и возможности HTML 5
- кодируем в формат Base64
- шифруем с помощью функций Java Script
- отправляем зашифрованные данные на сервер, где сохраняем в файл.
Обратный процесс:
- получаем содержимое зашифрованного изображения с сервера
- записываем его в объект Java Script, используя XmlHttpRequest Level 2 и возможности HTML 5
- дешифруем с помощью функций Java Script. На этом этапе получаем изображение, закодированное в формате Base64
- вставляем содержимое в тег на web-странице (браузеры по умолчанию поддерживают вставку изображений в формате Base64).
Немного ключевого исходного кода для работы с файлами:
<script type="text/javascript"> /** * Функция загрузки файла на сервер с использованием * XMLHttpRequest level 2 */ function upload(blobOrFile) { var xhr = new XMLHttpRequest(); // открываем соединение методом POST, вказываем URL и true=асинхронный запрос xhr.open('POST', '/File/UploadFile/', true); // тип ответа - набор байт xhr.responseType = "arraybuffer"; // устанавливаем заголовок ответа xhr.setRequestHeader("Content-type", "multipart/form-data"); xhr.onload = function(e) { // ... }; xhr.send(blobOrFile); // отправляем запрос } // добавляем слушателя события выбора файла document.querySelector('input[type="file"]').addEventListener('change', function(e) { var file = this.files[0]; // первый выбранный в диалоге файл var reader = new FileReader(); reader.onloadend = function(e) { var result = this.result; // считанный поток байт var arr = new Int8Array(result); var i; // счётчик байт var newResult = new ArrayBuffer(arr.byteLength); var newRes = new Int8Array(newResult); var keyForEncrypt = $('#keyForEncrypt').val(); for(i = 0; i < arr.byteLength; i++) { // шифруем данные побайтово newRes[i] = arr[i] + parseInt(keyForEncrypt, 10); } // отправка данных upload(newRes.buffer); }; // читаем файл как массив байт // работает в Chrome 11.0.696.68, не работает в FireFox 4.0.1 reader.readAsArrayBuffer(file); }, false); /*** * Функция получает файл в виде массива байт с сервера, * расшифровывает эти данные и передаёт их в Java апплет * для сохранения указанный пользователем файл */ function download() { var xhr = new XMLHttpRequest(); xhr.open('POST', '/File/DownloadFile/', false); xhr.responseType = 'arraybuffer'; xhr.onload = function(e) { var arr = new Int8Array(this.response); // this.response == arr.buffer // передаём данные в апплет upload(arr.buffer); var result = new Array(arr.byteLength); var keyForDecrypt = $('#keyForDecrypt').val(); for(var i = 0; i < arr.byteLength; i++) { result[i] = arr[i] - parseInt(keyForDecrypt, 10); } saveFileByApplet(result); }; xhr.send(); } function saveFileByApplet(data) { // посылаем данные в апплет var cryptApplet = document.CryptApplet; cryptApplet.saveFile(data); } $(document).ready(function() { $('#saveFileButton').click(function() { download(); }); }); </script>
Исходный код для работы с изображениями:
<script type="text/javascript"> var selectedFile = null; //добавляем слушателя события выбора файла document.querySelector('input[type="file"]').addEventListener('change', function(e) { selectedFile = this.files[0];// первый выбранный в диалоге файл }, false); $(document).ready(function() { $('#uploadPictureButton').click(function() { // если мы выбрали какой-нибудь файл if(selectedFile != null) { var reader = new FileReader(); reader.onload = function(e) { // считанная бинарная строка var result = this.result; // переводим в Base64 var base64Result = Base64.encode(result); // шифруем строку XOR с ключом var keyForEncrypt = $('#keyForEncrypt').val(); var encryptedData = XOREncrypt(base64Result, keyForEncrypt); // отправка данных uploadPicture(encryptedData); }; // читаем файл как бинарную строку reader.readAsBinaryString(selectedFile); } }); $('#downloadPictureButton').click(function() { downloadPicture(); }); }); /** * Функция возвращает расширение файла с дописанными справа до 5 символов пробелами */ function buildExtension() { if(selectedFile != null) { var ext = ""; var fullName = selectedFile.name; for(var i = fullName.length - 1; i >= 0; i--) { if(fullName[i] == '.') break; else ext += fullName[i]; } ext = ext.split('').reverse().join(''); if(ext.length < 5) { for(var i = 0; i <= 5-ext.length; i++) { ext += " "; } } return ext; } } /** * Функция загрузки изображения на сервер с использованием * XMLHttpRequest level 2 */ function uploadPicture(picture) { var xhr = new XMLHttpRequest(); // открываем соединение методом POST, вказываем URL и true=асинхронный запрос xhr.open('POST', '/Picture/UploadPicture/', true); xhr.onload = function(e) { // ... }; var sentData = "jpg " + picture; // отправляем запрос xhr.send(sentData); } /*** * Функция получает файл в виде строки, закодированной XOR, с сервера, * расшифровывает эти данные и вставляет в аттрибут SRC тега IMG */ function downloadPicture() { var xhr = new XMLHttpRequest(); xhr.open('POST', '/Picture/DownloadPicture/', true); xhr.onload = function(e) { // ответ - строка Base64, закодированная XOR var result = this.response; // первые 5 символов - расширение var ext = rtrim(result.substr(0, 5)); // строка в Base64 var base64Data = result.substr(5); var keyForDecrypt = $('#keyForDecrypt').val(); var decryptedData = XORDecrypt(base64Data, keyForDecrypt); // устанавливаем MIME тип в зависимости от расширения var mime = ""; switch (ext) { case "jpeg" : case "jpg" : case "jpe" : mime = "image/jpeg"; break; case "gif" : mime = "image/gif"; break; case "png" : mime = "image/png"; break; default: mime = "image/jpeg"; break; } $('#pict').attr('src', "data:" + mime + ";base64," + decryptedData); }; xhr.send(); } /** * Аналог PHP-функции rtrim - удаление пробелов справа */ function rtrim ( str, charlist ) { charlist = !charlist ? ' \\s\u00A0' : (charlist + '').replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g, '\\$1'); var re = new RegExp('[' + charlist + ']+$', 'g'); return (str + '').replace(re, ''); } </script>
Я не стал здесь приводить реализацию функций XOREncrypt, XORDecrypt и класса Base64, чтобы не загромождать и без того длинный листинг. Их можно посмотреть в прилагаемом архиве с исходным кодом.
Код Java-апплета для вывода диалога сохранения файла.
package ExtPackage; import java.applet.*; import java.io.*; import java.io.FileOutputStream; import javax.swing.JFileChooser; import javax.swing.*; public class CryptApplet extends Applet{ public void saveFile(byte[] data) throws FileNotFoundException, IOException { // вызываем диалог сохранения файла final JFileChooser fc = new JFileChooser(); fc.showSaveDialog(CryptApplet.this); // путь и имя файла, указанные пользователем File file = fc.getSelectedFile(); OutputStream out = new FileOutputStream(file); // записываем пришедшие данные в файл out.write(data); out.flush(); out.close(); } }
Резюме
Надеюсь, полученные мной результаты сэкономят немного времени тем, кто столкнётся с задачей шифрования/дешифрования данных на стороне клиента в web-ориентированных системах. Исходный код — в прикреплённом архиве.