• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

GML Buffer Encryption / Decryption

D

Deleted member 45063

Guest
Hello GMC,

Looking at the documentation for GM:S 2 I can find functions to calculate checksums of buffers or even compress them. But there doesn't seem to be any native support for buffer encryption and decryption. Looking at the Marketplace there seems to be a few extensions for encryption but they either don't support buffers directly or the ones that do don't implement standard encryption algorithms but some custom ones.

I was wondering if there is some resource for a standard encryption algorithm cross-platform implementation that works directly with buffers available that I just missed, or if it is even intended to be incorporated natively into GM:S 2 at all.

And I am aware that I could just change one of the extensions that I found or roll out my own port of a standard encryption algorithm, but I wanted to check what the status quo is before attempting either of those options.

Thanks in advance,
Rui Rosário
 

FrostyCat

Redemption Seeker
Is RC4 standard enough for you?
Code:
///@func rc4(@buffer, key, offset, length)
///@param buffer
///@param key
///@param offset
///@param length
/**
Encrypt the buffer in-place using RC4.
*/

var i, j, s, temp, keyLength, pos;
s = array_create(256);
keyLength = string_byte_length(argument1);
for (var i = 255; i >= 0; --i) {
    s[i] = i;
}
j = 0;
for (var i = 0; i <= 255; ++i) {
    j = (j + s[i] + string_byte_at(argument1, i mod keyLength)) mod 256;
    temp = s[i];
    s[i] = s[j];
    s[j] = temp;
}
i = 0;
j = 0;
pos = 0;
buffer_seek(argument0, buffer_seek_start, argument2);
repeat (argument3) {
    i = (i+1) mod 256;
    j = (j+s[i]) mod 256;
    temp = s[i];
    s[i] = s[j];
    s[j] = temp;
    var currentByte = buffer_peek(argument0, pos++, buffer_u8);
    buffer_write(argument0, buffer_u8, s[(s[i]+s[j]) mod 256] ^ currentByte);
}
 
D

Deleted member 45063

Guest
Is RC4 standard enough for you?
Code:
///@func rc4(@buffer, key, offset, length)
///@param buffer
///@param key
///@param offset
///@param length
/**
Encrypt the buffer in-place using RC4.
*/

var i, j, s, temp, keyLength, pos;
s = array_create(256);
keyLength = string_byte_length(argument1);
for (var i = 255; i >= 0; --i) {
    s[i] = i;
}
j = 0;
for (var i = 0; i <= 255; ++i) {
    j = (j + s[i] + string_byte_at(argument1, i mod keyLength)) mod 256;
    temp = s[i];
    s[i] = s[j];
    s[j] = temp;
}
i = 0;
j = 0;
pos = 0;
buffer_seek(argument0, buffer_seek_start, argument2);
repeat (argument3) {
    i = (i+1) mod 256;
    j = (j+s[i]) mod 256;
    temp = s[i];
    s[i] = s[j];
    s[j] = temp;
    var currentByte = buffer_peek(argument0, pos++, buffer_u8);
    buffer_write(argument0, buffer_u8, s[(s[i]+s[j]) mod 256] ^ currentByte);
}
RC4 can be useful in some applications however it's usage is discouraged as several vulnerabilities have been discovered. This thread is a inquiry on the state of cryptography capabilities for GameMaker, a software whose audience is most likely not security experts (for example, I am not anywhere near a security expert myself). Hence I would prefer if there were resources for safer encryption algorithms available (ideally even in GameMaker itself) so that users don't secure their data with vulnerable algorithms unaware of the consequences. That aside thanks for the example implementation @FrostyCat, I'm sure someone will find it useful.

And I would like to reiterate that I am aware that such algorithms exist in the Marketplace (for example, there are at least two AES implementations) but again they don't seem updated to the latest functionality of GameMaker since buffer support is missing. Also, reimplementing security-related algorithms is always a risk since basing it on the wrong implementation (or doing a naive one) can leave the software open for several known vulnerabilities - that's why a GameMaker supplied version would obviously be ideal as it could reuse a reputable security library that takes these things into account. And I know I could also do this myself by incorporating such libraries in an extension - but this thread is mostly to find the status quo of the GameMaker ecosystem, which so far seems like it is mostly relying on someone else's port of such algorithms and update them accordingly or implementing them yourself.
 

Anixias

Member
Preface: Using GMS 2.3

I started doing an implementation of RSA for encrypting my networking system, and I almost finished it, except that I had to create a BigInteger struct first. I did so, and it supports super huge numbers (haven't tested above 128 bits). Right now, it supports addition, subtraction, multiplication, exponentiation (with normal integers. it's not hard to expand this, except that I'm not entirely sure how to handle past the first "digit", as you'll read later in this comment), bitwise shifting, comparison, and a crude and extremely (could take YEARS to calculate) modulo function. The last thing I was doing was implementing sign, as it was previously restricted to unsigned values. I then have to learn how to divide a BigInteger by another BigInteger. I tried reading the Java implementation of BigInteger, but it's enormous and complex. I also found a video and started following their algorithm, but it requires negative numbers, which I hadn't started supporting yet as of writing the divide function.

The way my BigIntegers work is actually very simple. I have a chunk size, defined currently as 8 bits (could easily swap to 16 bits at the change of a macro, but not sure if larger calculations resulting from this would be accurate given that GM numbers are all doubles). They have an array of numbers, each number having the defined chunk size in bits (so in my case, every chunk AKA element in the array stores a number from 0-255). As for what I say in the next paragraph, whether a BigInt is positive or negative is a simple variable called signed that is -1 or +1. It just changes what the operation functions do (for example, if this bigint is signed -1 and we are subtracting a signed -1 big int, that's just an addition and you keep being signed -1). Chunks are stored in big-endian. The way addition works is just like how you would have learned in school, except each "digit" is actually a chunk. So, you can think of my BigIntegers as being base-256 numbers, where each chunk is a "digit" (except, big-endian so reverse the digits. Big-endian is just because it's easier to manage).

The only reason I need BigInteger is because RSA keys are meant to be 128 bits, and my mod_power (modulus exponentiation) function can't support BigIntegers yet. I converted it to work with BigInts, but I need a proper modulo function first. My idea was a series of repeated divisions instead of repeated subtractions (which is how my modulo function works right now), which would be many many times faster. The issue is that my division algorithm requires the use of negative BigInts, so I'm stuck trying to get that working first. So far, it works with subtraction (as explained above. I haven't fully finished subtraction, as when A - B and B > A (ignoring sign), it's better to do B - A and swap the sign, so you never actually deal with negative numbers) and multiplication (super simple. if the two signs are the same, make it positive. if they are different, make it negative), but not yet addition (should be easy. if we are signed -1 and we are adding a signed -1, addition and keep the sign. if we are signed +1 and we are adding a signed -1, subtraction). Subtraction is another case of what you learned in school, just borrowing from the next chunk if the subtracting chunk is larger than ours.

Btw, my RSA code can generate prime numbers, but to generate prime numbers large enough to be cryptographically secure, it's really slow, because it has to run a Fermat primality test and then repeated Rabin-Miller primality tests, to generate a number that is probably prime. And it would need to work with BigIntegers, to make the primes arbitrarily large, which just makes them even slower. Instead of making the user wait for that, I just have a global array of every large prime number from 800k to 1m. That's not very secure though, as I should use the whole 32-bit space, or even the whole 64-bit space.

@FrostyCat
Do you have anything to say regarding what I've written above? I've seen you on the forums for years from time to time, back to when I was first learning GM (first time I ever used it was GM8.1 back in 2007-2008 ish). I was wondering if you knew anything about arbitrary-precision algorithms, or how to do division with them.
 

FrostyCat

Redemption Seeker
Do you have anything to say regarding what I've written above? I've seen you on the forums for years from time to time, back to when I was first learning GM (first time I ever used it was GM8.1 back in 2007-2008 ish). I was wondering if you knew anything about arbitrary-precision algorithms, or how to do division with them.
It's not my specialization, but if I were to handle it in GML, I wouldn't have handled it much differently than you did. Just groups of bit blocks in an array to emulate Bigint behaviour, using the implementation from another language's standard library as reference.

For multiplication, usually the algorithm alternates between standard long multiplication for smaller values and asymptotically faster alternatives like Karatsuba for larger values. But for division, I'm not personally aware of any "smart" algorithms that's faster than long division and also appropriate for general use (many seem to demand that you know certain facts about the numbers being divided). So starting slow with long division would be a way to make some progress on this front (i.e. if you can do multiplication you should be able to division), and if you learn better ways going forward, just switch out the implementation.

But to be frank, I am not keen on the idea of pure GML implementations of cryptography. The sheer variety of modern cryptography algorithms, coupled with poor fixed-point support in GML, makes this mostly a fool's errand. As much as I enjoy coding challenges, for something like this, I would rather see an extension wrapper for something like libgcrypt than a reinvention of the wheel.
 

Anixias

Member
I agree. An extension would be my go-to as well, if there were one. I'd write it myself, but I've never touched extensions aside from messing around with some from the marketplace. I've never actually relied on one for anything, and don't know where to start on creating one of my own. I might search for a guide and see if I can create a wrapper for an encryption library, preferably RSA in my case.
 

Tornado

Member
We have been using this extension for a long time now and are very happy with it:

Actually we are using it in combination with
for encrypting files.

Never had problems with it!
The only problem is it is not fast, so we choose carefully what we encrypt with AES and when. (on mobiles it is a performance killer)
For us it works on Windows, Android and iOS.

Check if buffers are supported there.
 
Last edited:

Anixias

Member
For me specifically, I'd rather use a public-key encryption algorithm. AES is symmetric, which isn't super useful for me. Plus, I need it to be very very fast, as I will use it to encrypt networking (30+ packets per second that need to be encrypted and decrypted. Very fast with RSA, just RSA uses numbers that are much too big without a BigInteger class, which I've had a hard time implementing division/modulo for).
 
D

Deleted member 16767

Guest
Is RC4 standard enough for you?
Code:
///@func rc4(@buffer, key, offset, length)
///@param buffer
///@param key
///@param offset
///@param length
/**
Encrypt the buffer in-place using RC4.
*/

var i, j, s, temp, keyLength, pos;
s = array_create(256);
keyLength = string_byte_length(argument1);
for (var i = 255; i >= 0; --i) {
    s[i] = i;
}
j = 0;
for (var i = 0; i <= 255; ++i) {
    j = (j + s[i] + string_byte_at(argument1, i mod keyLength)) mod 256;
    temp = s[i];
    s[i] = s[j];
    s[j] = temp;
}
i = 0;
j = 0;
pos = 0;
buffer_seek(argument0, buffer_seek_start, argument2);
repeat (argument3) {
    i = (i+1) mod 256;
    j = (j+s[i]) mod 256;
    temp = s[i];
    s[i] = s[j];
    s[j] = temp;
    var currentByte = buffer_peek(argument0, pos++, buffer_u8);
    buffer_write(argument0, buffer_u8, s[(s[i]+s[j]) mod 256] ^ currentByte);
}
Hi @FrostyCat or anyone else. I don't mean to disturb you, but since this thread was in the Google search algorithms, could we maybe get an explaination on how to decrypt the json? I've tested the RC4 and it works as long as you use string_byte_length(_saveString)+1 on it. But what I'm not getting, and this is really not my field, is how to make it read the RC4 encryption to load everything that was put in json?

Example given:

GML:
function mcli_save(_filename) {
    var _saveStruct = {
        saveArmour : armour,
        saveInventory : inventory
    };
    var _saveString = json_stringify(_saveStruct);

    var _buffer = buffer_create(string_byte_length(_saveString)+1,buffer_fixed,1);
    buffer_write(_buffer,buffer_string,_saveString);

    rc4(_buffer, _saveString, 0, string_byte_length(_saveString)+1)
    buffer_save(_buffer,_filename);
    buffer_delete(_buffer);
    return "Armour and inventory saved to file "+working_directory+_filename;
}
/// @function mcli_load();
/// @description Load the inventory from a file using JSON
/// @param {string} filename The filename for the save file
function mcli_load(_filename) {
    //save file does not exist
    if (!file_exists(_filename)) {
        return "Save file not found";
    }
    //save file exists
    var _buffer = buffer_load(_filename);
    var _loadString = buffer_read(_buffer,buffer_string);
    buffer_delete(_buffer);
    var _loadStruct = json_parse(_loadString);

    armour = _loadStruct.saveArmour;
    inventory = _loadStruct.saveInventory;
    return "Loaded armour/inventory from save file!";
 
Last edited by a moderator:

chamaeleon

Member
But what I'm not getting, and this is really not my field, is how to make it read the RC4 encryption to load everything that was put in json?
Rc4 is symmetrical which means decryption is identical to encryption. So in theory you should only need an identical rc4() function call after buffer_load().

You need to have a fixed key available for the process that both encryption and description uses, though. You can't use the data being encrypted as the key because then you would need the know what the plaintext data is already in order to decrypt the buffer. If you can't come up with the same key at both save and load you can't decrypt what is encrypted.

Maybe it is a single string compiled into your program, maybe it is a string that uses the name of a player character as part of the key (and the name is available outside the save file itself). Maybe you store some random value in a file that is used for all saves. Whatever you do it needs to be predictable and identical for both saving and loading.
 
Last edited:

Food Eater

Member
I know I'm too late for this interesting topic but I have just used RC4 for a test and tried to use the wrong key for decryption. It works fine, but if I modified the last character of the key (in this case, it's "thisIsMyKey07" instead of "thisIsMyKey08"), the message content is still readable, while editing the other characters will mess the content up. Why does this happen? Is it on purpose? Please tell me anything that you know, I'm curious.

By the way, this is my code:
GML:
//create
buffer = buffer_create(128,buffer_fixed,1);
buffer_write(buffer,buffer_string,"foodEater09");

//encrypt and save
function encrypt()
{
    rc4(buffer,"thisIsMyKey08",0,buffer_tell(buffer));
    buffer_save(buffer,"buffer.sav");
}

//decrypt
function decrypt()
{
    var dBuff = buffer_load("buffer.sav");
    rc4(dBuff, "thisIsMyKey07",0,buffer_get_size(dBuff));

    buffer_seek(dBuff,buffer_seek_start,0);
    message = buffer_read(dBuff,buffer_string);
    show_message(message);
}
 
Top