Шифрование строк во flash играх
Написал недавно на базе flex-овских классов Base64Encoder и Base64Decoder (кодирвание Base64 строки в ByteArray) шифрование строк as3, с помощью алгоритма XOR cipher. Классы подверглись чудовищному надругательству, в итоге родился один единственный класс ageCrypt. Естественно flex-а он уже не требует, так что его можно использовать во flash-e )
Класс можно применять везде, где необходимо шифрование данных: при передаче данных на сервер, при записи shared objects etc.
Пример использования класса:
//Кодирование строки var strEncode:String=ageCrypt.encode(orig.text); //Декодирование строки var strDecode:String=ageCrypt.decode(strEncode);
Всё очень просто, правда?
Хочу обратить внимание что ключ сейчас задаётся прямо в классе. В целях безопасности его стоит запрятать получше. Также есть ограничение на длину строки – 32767 символа. Если надо больше – бейте текст на части. Вот вроде и всё.
Пример работы:
Код шифровальщика:
//Oleg Antipov //http://www.blog.anegmetex.com package main { public class ageCrypt { import flash.utils.ByteArray; //!!!Максимальный размер кодируемого буфера!!! private static const MAX_BUFFER_SIZE:uint = 32767; //Ключ для кодирования\декодирования - в целях безопасности лучше //конечно хранить в другом месте.... public static var KEY:String = "eVBHOulunx8A6spikeRQ9UEgyaXINTyzpn3SJ7FSzmwSlewTWI3"; public static function encode(source:String):String { resetEncoder(); encodeBase64(xor(source)); return flushEncoder(); } public static function decode(source:String):String { resetDecoder(); decodeBase64(source); return xor(flushDecoder().toString()); } //------------XOR Cipher------------- private static function xor(source:String):String { var key:String = KEY; var result:String = new String(); for(var i:Number = 0; i < source.length; i++) { if(i > (key.length - 1)) { key += key; } result += String.fromCharCode(source.charCodeAt(i) ^ key.charCodeAt(i)); } return result; } //------------DECODER------------- //переменные декодера private static var count:int=0; private static var data:ByteArray; private static var filled:int=0; private static var work:Array=[0, 0, 0, 0]; private static const ESCAPE_CHAR_CODE:Number = 61; // The '=' char private static const inverse:Array = [ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 ]; //дальше методы декодера //Декодирование строки формата Base64 и добавление результата //во внутренний буфер private static function decodeBase64(encoded:String):void { for (var i:uint = 0; i < encoded.length; ++i) { var c:Number = encoded.charCodeAt(i); if (c == ESCAPE_CHAR_CODE) work[count++] = -1; else if (inverse[c] != 64) work[count++] = inverse[c]; else continue; if (count == 4) { count = 0; data.writeByte((work[0] << 2) | ((work[1] & 0xFF) >> 4)); filled++; if (work[2] == -1) break; data.writeByte((work[1] << 4) | ((work[2] & 0xFF) >> 2)); filled++; if (work[3] == -1) break; data.writeByte((work[2] << 6) | work[3]); filled++; } } } private static function drainDecoder():ByteArray { var result:ByteArray = new ByteArray(); copyByteArray(data, result, filled); filled = 0; return result; } private static function flushDecoder():ByteArray { if (count > 0) { trace("Error in flushDecoder(): partialBlockDropped!"); } return drainDecoder(); } //Очистка всех буфферов декодера и приведение его переменных //в начальное состояние private static function resetDecoder():void { data = new ByteArray(); count = 0; filled = 0; work[0] = 0; work[1] = 0; work[2] = 0; work[3] = 0; } //Возвращает текущий буфер как массив байт. private static function toByteArray():ByteArray { var result:ByteArray = flushDecoder(); resetDecoder(); return result; } private static function copyByteArray(source:ByteArray,destination:ByteArray,length:uint = 0):void { var oldPosition:int = source.position; source.position = 0; destination.position = 0; var i:uint = 0; while (source.bytesAvailable > 0 && i < length) { destination.writeByte(source.readByte()); i++; } source.position = oldPosition; destination.position = 0; } //----------ENCODER------------ //переменные кодера private static const CHARSET_UTF_8:String = "UTF-8"; private static var newLine:int = 10; private static var insertNewLines:Boolean = true; private static var _buffers:Array; private static var _count:uint=0; private static var _line:uint=0; private static var _work:Array = [ 0, 0, 0 ]; private static const ALPHABET_CHAR_CODES:Array = [ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47 ]; //Дальше методы кодера private static function drainEncoder():String { var result:String = ""; for (var i:uint = 0; i < _buffers.length; i++) { var buffer:Array = _buffers[i] as Array; result += String.fromCharCode.apply(null, buffer); } _buffers = []; _buffers.push([]); return result; } //Кодирование строки формата Base64 и добавление результата //во внешний буфер private static function encodeBase64(data:String,offset:uint=0,length:uint=0):void { if (length == 0) length = data.length; var currentIndex:uint = offset; var endIndex:uint = offset + length; if (endIndex > data.length) endIndex = data.length; while (currentIndex < endIndex) { _work[_count] = data.charCodeAt(currentIndex); _count++; if (_count == _work.length || endIndex - currentIndex == 1) { encodeBlock(); _count = 0; _work[0] = 0; _work[1] = 0; _work[2] = 0; } currentIndex++; } } //Кодирует UTF-8 байты строки в Base64 и добавляет //результат во внутренний буфер private static function encodeUTFBytes(data:String):void { var bytes:ByteArray = new ByteArray(); bytes.writeUTFBytes(data); bytes.position = 0; encodeBytes(bytes); } //Кодирует байты в формат Base64 private static function encodeBytes(data:ByteArray,offset:uint=0,length:uint=0):void { if (length == 0) length = data.length; var oldPosition:uint = data.position; data.position = offset; var currentIndex:uint = offset; var endIndex:uint = offset + length; if (endIndex > data.length) endIndex = data.length; while (currentIndex < endIndex) { _work[_count] = data[currentIndex]; _count++; if (_count == _work.length || endIndex - currentIndex == 1) { encodeBlock(); _count = 0; _work[0] = 0; _work[1] = 0; _work[2] = 0; } currentIndex++; } data.position = oldPosition; } private static function flushEncoder():String { if (_count > 0) encodeBlock(); var result:String = drainEncoder(); resetEncoder(); return result; } //Очистка всех буфферов кодера и приведение его переменных //в начальное состояние private static function resetEncoder():void { _buffers = []; _buffers.push([]); _count = 0; _line = 0; _work[0] = 0; _work[1] = 0; _work[2] = 0; } //Возвращает текущий закодирванный буфер как строку Base64 private static function toString():String { return flushEncoder(); } private static function encodeBlock():void { var currentBuffer:Array = _buffers[_buffers.length - 1] as Array; if (currentBuffer.length >= MAX_BUFFER_SIZE) { currentBuffer = []; _buffers.push(currentBuffer); } currentBuffer.push(ALPHABET_CHAR_CODES[(_work[0] & 0xFF) >> 2]); currentBuffer.push(ALPHABET_CHAR_CODES[((_work[0] & 0x03) << 4) | ((_work[1] & 0xF0) >> 4)]); if (_count > 1) currentBuffer.push(ALPHABET_CHAR_CODES[((_work[1] & 0x0F) << 2) | ((_work[2] & 0xC0) >> 6) ]); else currentBuffer.push(ESCAPE_CHAR_CODE); if (_count > 2) currentBuffer.push(ALPHABET_CHAR_CODES[_work[2] & 0x3F]); else currentBuffer.push(ESCAPE_CHAR_CODE); if (insertNewLines) { if ((_line += 4) == 76) { currentBuffer.push(newLine); _line = 0; } } } } }
Качнуть пример и сам класс можно отсюда: http://www.anegmetex.com/devblog/files/crypt.rar
Категория: Полезные классы

Апрель 21st, 2010 at 11:02
А где же хранить ключ шифрования? на внешнем сервере? Все равно в рантайм режиме есть способ посмотреть текущие переменные = уже после получения ключа.
Может, есть более надежные способы?
Апрель 21st, 2010 at 12:07
Ну во первых стоит переименовать переменные key в какие нибудь tmpVal. Во вторых стоит задавать ключ гденибуть в неприметном месте таймлайна:
import main.*;
ageCrypt.KEY=”eVBHOulunx8A6spikeRQ9UEgyaXINTyzpn3SJ7FSzmwSlewTWI3″;
далее можно задавать ключ и поле KEY последовательностью ascii кодов типа:
ageCrypt[String.fromCharCode(...) + String.fromCharCode(...) + String.fromCharCode(...)] = String.fromCharCode(…) + String.fromCharCode(…) + String.fromCharCode(…) + String.fromCharCode(…) + String.fromCharCode(…) + String.fromCharCode(…);
где … – соответствующий ascii код. Это затруднит поиск ключа в программе (особенно если обфускатором пройти по ней).
Потом можно пойти ещё дальше и не хранить ключ в памяти, а генерировать его на лету с помощью очень хитрой функции (а то и не одной):
function tmpVal():String
{
var i:int=Math.round(sin(180)/5643*4767);
var g:int=(i-45)*3;
var tmp1:String=String.fromCharCode(i)+String.fromCharCode(g);
//Тут ещё много хитрых вычислений остальных ascii символов
return tmp1+tmp2+tmp3+tmp4…+tmp50;
}
Это первое что в голову приходит. Вообще говоря идеальной защиты не существует – профессионалы всё равно смогут сломать. Но наша задача – отсеять любителей и дилетантов )
А их как известно 95%…
Апрель 21st, 2010 at 12:18
Спасибо за коммент, про аски-коды и разнесение ключа – хорошая мысль.
Апрель 21st, 2010 at 12:29
И еще один теоретически занятный – повесить зашифрованный ключ в какой-либо мувик, в динамик тексте, и при необходимости вызывать его, расшифровывать и использовать
. Обфускатором такое не найти.
Апрель 22nd, 2010 at 20:08
Отлично!
Почерпнул пару идей и комментариев.
Еще можно составлять ключ из элементов массива – тоже в памяти непросто найти.
Если генерировать ключ на лету – то как его передать?
ПС Похоже автор темы для сайта везде понаставлял своих ссылок – посмотрите/наведите на двоеточия
Категория:
Апрель 21st, 2010 автор:
Апрель 23rd, 2010 at 0:00
>Если генерировать ключ на лету – то как его передать?
Например заводим себе глобальный класс который виден отовсюду – mainGlobal. Подписываем туда нашу функцию генерации ключа.
Ну а теперь в самом алгоритме шифрования вместо переменной KEY вызываем mainGlobal.getKEY()
Вот както так
>Похоже автор темы для сайта
>везде понаставлял своих ссылок
Хех, вот жук. Надо будет удалить
Апрель 23rd, 2010 at 10:56
Про класс это понятно, передали мы куда-то зашифрованное – а как на месте расшифровать без ключа?
Апрель 23rd, 2010 at 11:23
Не очень понимаю о чём речь. getKEY возвращает нужный нам ключ.
В каком месте нам надо расшифровывать без ключа?
Апрель 23rd, 2010 at 12:48
Мы же шифруем данные не просто так. Зашифровали – передали на сервер и на сервере надо что-то с ними сделать.
Как извлечь теперь на сервере то, что было зашифровано.
Или если мы делаем динамический ключ при перезапуске флешки будет новый ключ и мы в ней не сможем расшифровать то, что закодировали
Апрель 24th, 2010 at 0:58
>Как извлечь теперь на сервере то,
>что было зашифровано.
Ну для сервера в таком случае придётся конечно переписать декодер на PHP…
>Или если мы делаем динамический ключ
>при перезапуске флешки будет новый ключ
Да не, посыл был такой чтобы в памяти не хранить ключ, а генерировать его функцией. Но ключ то будет один и тот же.
Июль 25th, 2010 at 19:56
спасибо Олег, то что надо!
Замечательно работает
Июль 31st, 2010 at 9:45
Это конечно всё красиво, но как быть с русскими буквами ?
Июль 31st, 2010 at 11:04
Русский к сожалению не поддерживается