Библиотека симметричного шифрования на основе xor-алгоритма для Starcraft 2 готова :)
Зачем это нужно?
Насколько мне известно, данные банков без подписи в Starcraft II не шифруются и доступны для изменения. Единственный выход - шифровка данных, наподобие save/load системы: xgm.ru/p/wc3/creating_loading_code
Отдельная благодарность NETRAT'у за помощь с алгоритмами.
Кроме этого, есть ещё очень много применений ScEncrypt, даже для той же генерации сейвлоад (загрузочных) кодов, да для чего угодно, где нужно "запаролить данные и засунуть их в строку", включая обратную операцию.
Отдельная благодарность NETRAT'у за помощь с алгоритмами.
Кроме этого, есть ещё очень много применений ScEncrypt, даже для той же генерации сейвлоад (загрузочных) кодов, да для чего угодно, где нужно "запаролить данные и засунуть их в строку", включая обратную операцию.
Как это использовать?
Сначала вам нужно каким-либо образом сделать так, чтобы код библиотеки ScEncryptLibrary оказался внутри кода карты. Например, так: xgm.ru/forum/showthread.php?t=35589 (т.е. просто копируем его в свободную область)
Библиотека содержит три пользовательские функции:
//==== Encrypt portion of data:
string scEncryptString(string source, string key)
//==== Decrypt portion of data:
string scDecryptString(string coded, string key)
//==== On Initialize event:
bool scEncryptInit()
Последнюю надо запустить из триггера инициализации карты.
Первая функция – scEncryptString(string source, string key) – принимает:
- исходную строку. Это может быть любая строка, туда можно записать уровни, ресурсы, статы и прочие параметры вашей игры, которые надо зашифровать. Главное - чтобы ваш алгоритм знал, как их укомплектовать в string и достать оттуда в обратном порядке.
- секретный ключ. Вот тут всё посложнее. Был бы это Warcraft 3, мы бы смогли просто использовать имя пользователя + ещё какие-либо данные, чтобы ключ стал уникальным, но в SC2 такой возможности нам не предоставили. Я думаю, что в вашей карте вы должны предложить игроку "залогиниться", т.е. ввести его постоянный пароль. После этого мы каким-то образом преобразовываем наш виртуальный пароль (допустим, у нас ещё имеется "имя текущей игры") и генерируем секретный ключ, с помощью которого потом можно будет расшифровать данные.
Функция возвращает код из символов алфавита scEncryptAlphabet, который уже можно будет заталкивать в банк или показывать пользователю на запись (открытый ключ). Код этот в (4/3) раза (+3 символа) больше входных данных, т.к. алфавит ascii больше алфавита кода :)
Вторая функция – scDecryptString(string coded, string key) – принимает:
- кодированную строку. Это тот самый код, который вернула scEncryptString, называемый открытым ключом.
- секретный ключ. Ключ, описанный выше. Он-то нам и нужен для расшифровки.
Функция возвращает ваши расшифрованные данные.
При ошибках ввода, вывода, переполнениях и т.п. вызывается функция scassert (в подбиблиотеке ScDebug) и устанавливается флаг scisassert на значение 1, а в scassertnotes записана причина ассерта.
Далее вы уже можете проверять, какой игрок сфейлил свой код, т.е. по очереди для игроков вызывать шифрование/дешифрование и после ввода проверять флаг, затем устанавливать на 0 для следующего игрока.
Далее вы уже можете проверять, какой игрок сфейлил свой код, т.е. по очереди для игроков вызывать шифрование/дешифрование и после ввода проверять флаг, затем устанавливать на 0 для следующего игрока.
Ограничения
- длина входных данных по умолчанию - от 4 до 4096 символов
- длина ключа по умолчанию - от 2 до 4096 символов
- входные данные могут быть записаны только в ASCII (я не думаю, что кому-то вдруг надо будет шифровать сообщения пользователей или Войну и Мир)
- однопоточность
- не рекомендуется использовать оба символа - 0 "zero" и O "ou" - в scEncryptAlphabet, если вы хотите показывать код игроку на экране, т.к. эти символы в шрифте SC2 абсолютно одинаковые
Исходный код
Смотреть "User Functions" внизу.
ScEncryptLibrary:
//========================================================================================================
// ScEncryptLibrary
// (c) ScorpioT1000 2010
//========================================================================================================
//====================================================
// name: ScMath
// author: ScorpioT1000
// note: my libs uses it
//====================================================
int ceil(fixed f) {
int x = FixedToInt(f);
if(f > IntToFixed(x)) { return x+1; }
return x;
}
// little endian, from 0
int int2byte0(int source) { return (source & 0xFF); }
int int2byte(int source, int index) { return ((source >> (index*8)) & 0xFF); }
int int2short0(int source) { return (source & 0x0000FFFF); }
int int2short1(int source) { return (source << 8); }
//====================================================
// name: ScString
// author: ScorpioT1000
// note: '\n' and '\r' chars don't work
//====================================================
const string scASCIITable="\x1\x2\x3\x4\x5\x6\x7\x8\x9\x1\xB\x1\xD\xE\xF\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x20!\x22#$%&'()*+,-./0123456789:;\x3C=\x3E?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\x5C]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7F";
// conversion
int string2char(string s) {
return (StringFind(scASCIITable,s,true));
}
string char2string(int ch) {
if(ch==0) { return "\x0"; }
return StringSub(scASCIITable,ch,ch);
}
// add one char to the tail
string stringAddChar(string s, int ch) {
return s+char2string(ch);
}
// get/set one char in specific position (from 0)
int stringGetChar(string s, int position) {
return string2char(StringSub(s,position+1,position+1));
}
int stringGetCharPos(string s, string ch) {
return (StringFind(s,ch,true)-1);
}
string stringSetChar(string s, int position, int ch) {
int len = StringLength(s);
position = position + 1; //from 0
if(len <= 1) { return char2string(ch); }
if(position == 1) {
return char2string(ch)+StringSub(s,2,len);
} else if(position >= (len-1)) {
return StringSub(s,1,(len-2))+char2string(ch);
}
return StringSub(s,1,position-1)+char2string(ch)+StringSub(s,position+1,(len-1));
}
string substr(string src, int start, int end) { return StringSub(src,start+1,end+1); }
string strchar(string src, int index) { return StringSub(src,index+1,index+1); }
//====================================================
// name: ScDebug
// author: ScorpioT1000
// note: my libs uses it
//====================================================
bool scDebugEnable = true;
void scdebug(string msg) {
UIDisplayMessage(PlayerGroupAll(),c_messageAreaDebug,StringToText("[debug] "+msg));
}
void scassertmsg(string msg) {
UIDisplayMessage(PlayerGroupAll(),c_messageAreaDebug,StringToText("[assert] "+msg));
}
int sc__isassert=0;
string sc__assertnotes="";
void scassert(bool cond, string notes) {
if((! cond) && scDebugEnable) {
scassertmsg("\""+notes+"\" - assertion failed!");
sc__isassert=1;
sc__assertnotes = notes;
TriggerStop(TriggerGetCurrent());
}
}
//====================================================
// name: ScEncrypt v0.01
// author: ScorpioT1000
// special thanks: NETRAT
// source: www.xgm.ru
// note: it's not recommended to use both "zero"(0) and "ou"(O) symbols in the alphabet
//====================================================
const int scEncryptMaxDataSize = 4096;
const int scEncryptMinDataSize = 4;
const string scEncryptAlphabet = "$#@123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // 64 !
int sce_a; int sce_b; int sce_c; int sce_w; int sce_x; int sce_y; int sce_z;
//====================================================
//===== Private Functions ============================
//====================================================
//===== data coding
// code 3 8-bit-int (a,b,c) to 4 6-bit-int (w,x,y,z)
void sceCodeData(int a, int b, int c) {
sce_w = a >> 2;
sce_x = ((a & 3)<<4) | (b>>4);
sce_y = ((b & 15)<<2) | (c>>6);
sce_z = c & 63;
}
// code 4 6-bit-int (w,x,y,z) to 3 8-bit-int (a,b,c)
void sceDecodeData(int w, int x, int y, int z) {
sce_a = (w<<2) | (x>>4);
sce_b = ((x & 15)<<4) | (y>>2);
sce_c = ((y & 3)<<6) | z;
}
//
int [scEncryptMaxDataSize] sce__DataBuffer0;
int sce__DataBuffer0Size =0;
bool sce__inited0 =false;
void sce__DataBufferClear() {
int i=0;
while(i < scEncryptMaxDataSize) {
sce__DataBuffer0[i]=0;
i=i+1;
}
}
string sce__CodeS() {
string s;
int i=0;
// integer math, check 4 chars in 2 bytes
scassert(sce__DataBuffer0Size == ((sce__DataBuffer0Size/3)*3), "sce__CodeS: wrong size");
while(i < sce__DataBuffer0Size) {
sceCodeData(sce__DataBuffer0[i],sce__DataBuffer0[i+1],sce__DataBuffer0[i+2]);
s = s + strchar(scEncryptAlphabet,sce_w);
s = s + strchar(scEncryptAlphabet,sce_x);
s = s + strchar(scEncryptAlphabet,sce_y);
s = s + strchar(scEncryptAlphabet,sce_z);
i = i+3;
}
return s;
}
void sce__DecodeS(string coded) {
int len = StringLength(coded);
int i=0;
int j=0;
len = (len/4)*4; // integer math, check 3 bytes in 4 chars
sce__DataBuffer0Size = ceil(len * 0.75); // 3/4
scassert(sce__DataBuffer0Size > scEncryptMinDataSize, "sce__DecodeS: buffer size is too small");
scassert(sce__DataBuffer0Size < (scEncryptMaxDataSize+scEncryptMinDataSize), "sce__DecodeS: buffer overflow");
while(i<len) {
sce_w = stringGetCharPos(scEncryptAlphabet,strchar(coded,i));
sce_x = stringGetCharPos(scEncryptAlphabet,strchar(coded,i+1));
sce_y = stringGetCharPos(scEncryptAlphabet,strchar(coded,i+2));
sce_z = stringGetCharPos(scEncryptAlphabet,strchar(coded,i+3));
sceDecodeData(sce_w,sce_x,sce_y,sce_z);
sce__DataBuffer0[j] = sce_a;
sce__DataBuffer0[j+1] = sce_b;
sce__DataBuffer0[j+2] = sce_c;
j=j+3;
i=i+4;
}
}
//===== encryption
string sce__EncryptS(string src, string key) {
string rk;
string result;
int N = StringLength(src);
int Nk = StringLength(key);
int i=0;
int j=0;
scassert(N < (scEncryptMaxDataSize+scEncryptMinDataSize), "sce__EncryptS: input overflow");
if(N > Nk) { // rep. key
while(i < N) {
if(j >= Nk) {
j=0;
}
rk = rk + strchar(key,j);
j=j+1;
i=i+1;
}
} else {
rk = substr(key,0,N-1);
}
sce__DataBuffer0[1] = int2byte0(N) ^ string2char(strchar(rk,0)); // c1 = N0 xor rk0
sce__DataBuffer0[0] = int2byte(N,1) ^ sce__DataBuffer0[1]; // c0 = N1 xor c1
i=0;
while(i<N) { // encrypt
sce__DataBuffer0[i+2] = (string2char(strchar(src,i)) ^ (string2char(strchar(rk,i)) ^ i));
i=i+1;
}
sce__DataBuffer0Size = (i+2);
j = ((sce__DataBuffer0Size/3) *3);
if(j != sce__DataBuffer0Size) { sce__DataBuffer0Size = sce__DataBuffer0Size + (j+3-sce__DataBuffer0Size); }
result = sce__CodeS();
sce__DataBufferClear();
return result;
}
string sce__DecryptS(string coded, string key) {
string result;
string rk;
int N =0; // size
int i=0;
int j=0;
int Nk = StringLength(key);
sce__DecodeS(coded); // decode
N = (sce__DataBuffer0[1] ^ string2char(strchar(key,0))); // d1 = c1 xor rk0
N = (((sce__DataBuffer0[0] ^ sce__DataBuffer0[1])<<8) | N); // d0 = c0 xor d1
scassert(N >= (sce__DataBuffer0Size-scEncryptMinDataSize), "sce__DecryptS: wrong size(-)");
scassert(N < sce__DataBuffer0Size, "sce__DecryptS: wrong size(+)");
if(N > Nk) { // rep. key
while(i < N) {
if(j >= Nk) {
j=0;
}
rk = rk + strchar(key,j);
j=j+1;
i=i+1;
}
} else {
rk = substr(key,0,N-1);
}
i=0;
while(i < N) { // decrypt
result = result + char2string(sce__DataBuffer0[i+2] ^ (string2char(strchar(rk,i)) ^ i));
i=i+1;
}
sce__DataBufferClear();
return result;
}
bool sce__Init0() {
sce__inited0 = true;
sce__DataBufferClear();
return true;
}
//====================================================
//===== User Functions ===============================
// Encrypt and decrypt any data in string by private key.
// note: it's not recommended to use some identical symbols successively
// source size: from (scEncryptMinDataSize) to (scEncryptMaxDataSize)
// key size: from 2 to (scEncryptMaxDataSize)
//====================================================
// Encrypt portion of data
string scEncryptString(string source, string key) { return sce__EncryptS(source,key); }
// Decrypt portion of data
string scDecryptString(string coded, string key) { return sce__DecryptS(coded,key); }
// Initialize encryption system
bool scEncryptInit() { return sce__Init0(); }
//====================================================
//====================================================
void scInit() {
scEncryptInit();
}
А что если взломают?
Особенность системы xor-шифрования в разделении исходных данных на конечный код и секретный ключ. Получаются "две половинки", по которым можно восстановить данные. Если взломщик не будет иметь ключ, то восстановить данные этой системой будет трудно даже полностью зная весь её алгоритм.
Для большей надёжности я бы посоветовал следующее:
- не заполняйте данные идущими подряд нулями или одинаковыми символами, чтобы не рассекретить ключ
- используйте как можно большую длину ключа (например, введите ограничение на ключ от 6 символов)
English Description
ScEncrypt in english
Introduction
As far as I know, the data of Starcraft II banks isn't encrypted, therefore they are accessible for modifying. The only solution is to encrypt data like save/load systems. Special thanks to NETRAT for helping with algorithms.
How to use it?
How to use it?
First include the ScEncryptLibrary source to the map code. For example, so xgm.ru/forum/attachment.php?attachmentid=65696
The library contains three user functions:
==== Encrypt portion of data:
string scEncryptString(string source, string key)
string scEncryptString(string source, string key)
==== Decrypt portion of data:
string scDecryptString(string coded, string key)
string scDecryptString(string coded, string key)
==== On Initialize event:
bool scEncryptInit()
bool scEncryptInit()
The last one should be launched from the initialization trigger.
The first function – scEncryptString(string source, string key) – takes:
the source string. It can be any string, where it is possible to write down any levels, resources, stats and other parameters of your game which should be encrypted. It's important to ensure, that your algorithm is properly serializing data and also getting it back in reverse order.
the secret key. There are some complexities. We'd simply used <user name> + <any data> to make the unique key in WC3, but there's no such possibility in SC2. I think that you should offer the player to "log in" in your map, i.e. enter his permanent password. After that we somehow transform our virtual password (say, we still have a "name of the current game") and generate the secret key, which is used later do data decryption.
the secret key. There are some complexities. We'd simply used <user name> + <any data> to make the unique key in WC3, but there's no such possibility in SC2. I think that you should offer the player to "log in" in your map, i.e. enter his permanent password. After that we somehow transform our virtual password (say, we still have a "name of the current game") and generate the secret key, which is used later do data decryption.
Function returns the code from scEncryptAlphabet characters which can be pushed in bank or shown to the user (public key). Remember, the O 'ou' and 0 'zero' characters in SC2 font are absolutely identical.
The second function – scDecryptString(string source, string key) – takes:
the coded string. It is the code returned by scEncryptString, named public key.
the secret key, described above.
the secret key, described above.
Function returns your decrypted data.
Function scassert is called on I/O errors, overflows, etc., where scisassert == 1 and scassertnotes == reason
Restrictions
Restrictions
source size: from (scEncryptMinDataSize =4) to (scEncryptMaxDataSize=4096)
key size: from 2 to (scEncryptMaxDataSize=4096)
input data can be written only in ASCII
single-threading
key size: from 2 to (scEncryptMaxDataSize=4096)
input data can be written only in ASCII
single-threading
Note: it's not recommended to use some identical symbols successively, it reduces privacy of the key
Give me some examples!
Give me some examples!
Examples and source files are in the attachment.
Если я правильно понял, тут 128-кодирование и 64-шифрование поверх. Можно и попроще, для простой карты такой уровень ни к чему.
Ред. ScorpioT1000