В наши дни всё больше программ переводятся в так называемый «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-ориентированных системах. Исходный код — в прикреплённом архиве.