- PVSM.RU - https://www.pvsm.ru -
Здравствуй, %USERNAME%!
Однажды от моего начальника, (который, помимо того, что является владельцем компании, в которой я работаю еще и практикующий адвокат), поступило задание создать ресурс для анонимной переписки.
В своей работе для разных «серых» переговоров он использовал Telegram Павла Дурова, но смущало его, что в этом и подобных ресурсах предполагается регистрация. Пусть никто и не верифицирует пользователя, но в итоге переписка всегда сводится к тому, что общаются субъект с никнеймом Х и субъект в никнеймом Y и какими-то сохраненными при регистрации данными.
Для того, чтобы, в случае копирования переписки было проблематично ее соединять по календарным сессиям между одними и теми же пользователями, каждый раз необходимо заново проходить регистрацию и искать своего собеседника, либо светить имена пользователей в оффлайне.
ТЗ от моего шефа звучало примерно так: «Идем самым простым путем. Мне нужен чат. Каждый раз, заходя в него, я получаю номер анонимного чата и высылаю его вне Интернета своему собеседнику. Он заходит на соответствующий ресурс, вводит номер и присоединяется к моему чату. Переписка нигде не сохраняется и уничтожается, как только мы прекращаем общение. Номера каждый раз должны быть уникальными, чтобы, даже в случае копирования экранов невозможно было пришить переписку в качестве доказательств в уголовном деле.
Чат должен быть работоспособен в TOR браузере».
Для начала были определены некоторые цели и требования:
Если быть честным, то по первому пункту я попал на все три ситуации. Канал общий на всех сотрудников (dl: 4 mb/s, ul: 0.3mb/s) и раздается одним роутером, что дает маленький канал и большие пинги. А
От данных параметров я и начал проводить исследование в своих JS-скриптах. Выяснилась любопытная вещь: если делать два запроса, средствами AJAX, одновременно то сервер медленно умирает очень долго обрабатывает запросы и в итоге выдает «Aborted».
Решение пришло быстро и заключалось всего в нескольких строках:
if(chat_function.load.status_load) {
chat_function.load.status_load = false;
$.ajax({
type: 'POST',
url: 'hello/world.php',
data: 'chat=000000000',
timeout: 5000,
error: function() {
chat_function.load.status_load = true;
},
success: function (data) {
...
chat_function.load.status_load = true;
}
});
}
chat_vars.intervals.load_messages = setInterval(chat_function.load_message, rand(1000, 2000));
А на тот момент, когда сервер просто выдавал ошибки в PHP вместо конкретных страниц, пришлось убить все выводимые ошибки.
error_reporting(0);
На этом я остановился, ибо больше ничего не надо, думаю.
Не люблю решать за пользователей, но в этом контексте все было решено заранее:
$user_id = rand(1000000000, 9999999999);
А вот на счет номеров чата, надо было подумать. Формат я взял цифро-буквенный, ибо обычное число 8 * 10^9 брутфорсом можно и обойти, с сегодняшними мощностями ПК. Это было мало вероятно, но все же.
Процесс самой регистрации пользователя проходит в единственный этап. В БД делается две записи вида:
chat_id | chat_time_start | ch_code
"28BF195420" | "1409297213" | "e02aeb13ad760933a5f88eb4589ebcbd"
id | user_id | chat_id | user_time_registered | user_time_last_seen
"1" | "1163272808" | "28BF195420" | "1409297213" | "1409297213"
Последнее свойство первой записи — md5-hash псевдослучайной фразы. Служит подобием публичного идентификатора.
/index.php?chat=e02aeb13ad760933a5f88eb4589ebcbd
А для уверенности, что в чат просто так не попадут по данному URL, при заходе делается проверка по второй записи, отвечающую за связь пользователя с чатом.
В качестве алгоритма шифрования я выбрал Anubis, т. к. его реализации доступны на PHP и JS. С последней реализацией, пришлось повозиться лично.
По сути, реализация на JS, была просто портирована из PHP, на что указывают несколько функций.
function microtime (get_as_float) {
var now = new Date().getTime() / 1000;
var s = parseInt(now, 10);
return (get_as_float) ? now : (Math.round((now - s) * 1000) / 1000) + ' ' + s;
}
function ord(str) {
var ch = str.charCodeAt(0);
if (ch>0xFF) ch-=0x350;
return ch;
}
Проблема была в том, что какой бы ключ шифрования не вводи, все расшифровывается. Тоесть этот алгоритм, по своей сути, совсем не шифровал данные.
Путем долгих изысканий и вставки console.log(), я пришел к выводу, что функция String.fromCharCode() выдает значения больше 0xFF.
var crypt = function (block, roundKey) {
...
//map cipher state to byte array block (mu^{-1}):
for (i = 0, pos = 0; i < 4; i++) {
w = inter[i];
block[pos++] = String.fromCharCode(w >> 24);
block[pos++] = String.fromCharCode(w >> 16);
block[pos++] = String.fromCharCode(w >> 8);
block[pos++] = String.fromCharCode(w);
}
return block;
}
Данная часть кода в принципе ничего не делала с данными, т. к. block[pos++] работала логически не правильно. А так же String.fromCharCode() возвращала данные больше 0xFF, что приводило к неправильным вычислениям блоков.
Решением проблемы стало такое преобразование:
var crypt = function (block, roundKey) {
...
function fixedFromCharCode (codePt) {
if (codePt > 0x00FF || codePt < 0x0) {
codePt = codePt % 0x100;
if(codePt < 0x0) {
codePt = codePt - 0xFF00;
}
return String.fromCharCode(codePt);
} else {
return String.fromCharCode(codePt);
}
}
block = "";
//map cipher state to byte array block (mu^{-1}):
for (i = 0, pos = 0; i < 4; i++) {
w = inter[i];
block += fixedFromCharCode(w >> 24);
block += fixedFromCharCode(w >> 16);
block += fixedFromCharCode(w >> 8);
block += fixedFromCharCode(w);
}
return block;
}
Еще одна проблема заключалась в функции hash_hmac, которая использует библиотеку CryptoJS.HmacSHA256. Данная функция выдавала данные в шестнадцатеричном формате, когда было необходимо в двоичном.
function hash_hmac(algo, data, key) {
var hash = CryptoJS.HmacSHA256(data, key);
return hash.toString(CryptoJS.enc.Hex);
}
И вот, что бы все это дело работало правильно, я привел ее к такому виду:
function hash_hmac(algo, data, key) {
var hash = CryptoJS.HmacSHA256(data, key);
hash = hash.toString(CryptoJS.enc.Hex);
for(var i = 0, output = ''; i < hash.length; i+=2) {
output += String.fromCharCode(parseInt(hash.substr(i, 2), 16));
}
return output;
}
А последняя проблема была чисто наша, чисто русская — кирилические и др. символы. Все решилось простым преобразованием в Base64.
encrypt: function(data, hex, b64) {
if(b64 != undefined && b64) var blocks = dataPrepare(Base64.encode(data));
...
},
decrypt : function (data, hex, b64) {
...
if(b64 != undefined && b64) return Base64.decode(decrypted);
return decrypted;
}
Шифрование реализовано таки образом:
У каждого пользователя есть свой открытый ключ, который генерируется при регистрации. Все данные, которыми обменивается браузер и сервер, зашифрованы и расшифровываются только на стороне клиента, то есть с помощью JS. А вот когда серверные скрипты принимают данные и записывают их в БД, они шифруются уже собственным закрытым ключом.
Это простое решения для анонимной переписки, которое не требует от пользователя ничего. Даже придумавыть имена, ники и т.д. Даже если сервер положить на лопатки и угнать базу сообщений, мало вероятно, что она кому-нибудь чем-нибудь поможет, ведь привязать случайный 10-и значный номер к реальному человеку не удастся.
SHSHCHAT [2]
Проект будет развиваться, совершенствоваться, поэтому конструктивную критику я выслушаю в Ваших комментариях.
Автор: GRIDark
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/68562
Ссылки в тексте:
[1] хостинга: https://www.reg.ru/?rlink=reflink-717
[2] SHSHCHAT: http://shshchat.com
[3] Источник: http://habrahabr.ru/post/229399/
Нажмите здесь для печати.