Jump to content

Encryption help


ItHadToComeToThis
 Share

You are about to reply to a thread that has been inactive for 1475 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

So I want to encrypt messages so they quickly encrypt and decrypt. I am using Base 64 with a randomly generated nonce added onto the end. This is kinda where my experience with encryption ends.

From what I have read a nonce is supposed to be a random number to help pad out a message and also prevent MIM / relay attacks. But in terms of what im doing its there just to make the message slightly harder to decrypt.

So my question is this. Besides adding a nonce into the encrypted message, what else could you add to Base64 that would increase its security and also keep the message encryption and decryption as fast as possible?. Is there a way to create a nonce that is only valid for a certain amount of time so even if the message was decrypted it would be useless?. Or am I thinking wrongly about this.

Link to comment
Share on other sites

What are you trying to do? Encrypt, or authenticate?

Base64 is not encryption. It's just a way to get binary numbers into a set of characters that will pass through most text handling systems.

LSL has SHA-1 and MD5 hashes available. They're not secure by modern standards, but they require a fair amount of compute power to come up with a collision, and unless you have a well-funded adversary they're probably good enough. I've used an SHA-1 hash to validate log entries I make to a server. I was originally considering doing a racing game, and wanted to avoid people messing with the results. That's not hard.

The LSL random number generator is not random enough to be used as a key generator. "The random number generator is not a source of entropy." - LL docs.

If you're sending messages to an outside server, you can use HTTPS, and get regular web-grade encryption. You can probably even do object to object HTTPS within the LL world.

Link to comment
Share on other sites

To echo what Animats said: Base64 is not encryption, it's encoding. That means it's a 1:1 translation from one format to another, with absolutely zero security. If anyone catches your base64-string, they can immediately get the real content. You cannot do anything to "make base64 more secure."

The "nonce" used by the MD5 function in LSL can be alternatively read as "n, once." (Probably not intentional.) It's a number, used at a single point in the encryption process.

llMD5String("password", 0);

The above code actually results in:

MD5("password" + ":0")
  
// Output: "5980d6f828464365b63eac19a6984b3f"

You can verify this with any online MD5 generator.

The difference between Base64 and MD5/SHA1 is that if I give you an encrypted string, there's (ideally) no way for you to figure out how that encrypted string was created. MD5 and SHA1 are specifically designed as one-way encryption. It's not possible to create a decrypter for them. (For example, what is this? "d612308c602ba6467964a2d20a91d7d0")

The process of figuring out the input would involve creating plain-text, encrypting it, and checking if the outputs match. The same applies to user accounts. When you sign up, the password you enter is encrypted, either one-way or two-way, and the encrypted string is stored into a database. When you try logging in, the password you enter is encrypted again and the system checks if the two results match.

Edited by Wulfie Reanimator
Link to comment
Share on other sites

if we are talking about encrypting a string then we can use some kind of feistel (block cipher) algorithm. Horst Feistel: https://en.wikipedia.org/wiki/Horst_Feistel

basically the feistel algorithm cycles for some number of rounds. On each round swap high block bytes with bytes in the low block, and use some kind of bit mixer on each round. A bit mixer like XOR or some other reversible bit manipulation method

a LSL example of a fairly quick feistel network block cipher algorithm using llXorBase64: http://wiki.secondlife.com/wiki/LlXorBase64

integer ROUNDS = 4;  // 4 rounds for this example. Change to whichever
integer MIXLEN = 12;  // mixer length is 12 chars for this example. Change to whichever

string encode(string source, string password)
{
    // convert both to base64
    source = llStringToBase64(source);
    password = llStringToBase64(password);
    
    integer i;
     
    for (i = 0; i < ROUNDS; i++)  
    {
        // mix block by moving first MIXLEN chars to end of string 
        source = llDeleteSubString(source, 0, MIXLEN-1) + llGetSubString(source, 0, MIXLEN-1);
        // xor (bit mixer) encode
        source = llXorBase64(source, password);
    }
    return source;
}

string decode(string crypt, string password)
{
    // convert password to base64. crypt is already base64
    password = llStringToBase64(password);
    
    integer i;
    for (i = 0; i < ROUNDS; i++)  // be sure that ROUNDS is the same as encode  
    {
        // xor (bit mixer) decode
        crypt = llXorBase64(crypt, password);
        // unmix block by moving last MIXLEN chars to start of string 
        crypt = llGetSubString(crypt, -MIXLEN, -1) + llDeleteSubString(crypt, -MIXLEN, -1); 
    }
    return llBase64ToString(crypt);
}

default
{
    state_entry()
    {
        string password = "P4s5Wo_rD^LonG3r"; // change to whichever. Longer password is better than shorter 
        
        string data = "I am some very important data.";
        
        // say data
        llOwnerSay("Data: " + data);
                
        // encrypt data and say
        string crypt = encode(data, password);
        llOwnerSay("Crypt: " + crypt);
 
        // decrypt data and say
        string decrypt = decode(crypt, password);
        llOwnerSay("Decrypt: " + decrypt);
    } 
}

 

Link to comment
Share on other sites

What I am wanting to do is encrypt messages sent from one hud object to another worn by multiple people. I know I can bury the message in one of the two billion chan els available to me but the community I develop for are a tenacious lot and have broken past HUD’s by channel scanning for weeks on end. I need a way that gives some security to messages and some kind of authentication so a single message can’t be used more than once

  • Like 1
Link to comment
Share on other sites

a problem with strings is that they are relatively short and being short they lend themselves to be broken by brute force attacks

the only thing i can add to a feistel like algorithm is for each player to have their own password, which has to be derived in whole or part from some constant info unique to each owner/player  that the decoder just knows. So that should a encoded message be intercepted by a man-in-middle attack (by a channel scanner) then presented by the attacker when decoded it doesn't match the owner

the only constant info that both the encoder and decoder can know independently is the owner's key. From which a unique password can be independently constructed

Edited by Mollymews
from which
Link to comment
Share on other sites

5 minutes ago, Mollymews said:

a problem with strings is that they are relatively short and being short they lend themselves to be broken by brute force attacks

the only thing i can add to a feistel like algorithm is for each player to have their own password, which has to be derived in whole or part from some constant info unique to each owner/player  that the decoder just knows. So that should a encoded message be intercepted by a man-in-middle attack (by a channel scanner) then presented by the attacker when decoded it doesn't match the owner

the only constant info that both the encoder and decoder can know independently is the owner's key. From which a unique password can be independently constructed

That’s not a bad idea. Combined with what you linked above. Thank you for that btw. One other idea I had a few mins ago was creating a hash from a number generated using server time. Do you think that would be a good addition to the key or would a value taken from the key be okay on its own?

  • Like 1
  • Haha 1
Link to comment
Share on other sites

50 minutes ago, ItHadToComeToThis said:

One other idea I had a few mins ago was creating a hash from a number generated using server time. Do you think that would be a good addition to the key or would a value taken from the key be okay on its own?

yes it can help. With time then the password can be some kind of hash of the player's key and some fixed time in the future. Say for example the next hour time from Now.  Say the time Now is 1145. The next hour is 1200.  The encoder uses owner key hash + "1200". The decoder on receiving at say 1146 takes this to the next hour: 1200 then owner key hash + "1200"

a issue with this is when it is close to the hour. Say 1159 and is not received until say 1201. 1201 takes the decoder to 1300 and the message will decode to garbage. So we have to compensate for this in some way

a way is for the decoder to try twice.  If the message is received within the first 5 minutes of the new hour say then decode using 1300 the next hour. If the decoded message is not garbage then we use it. If the decoded message is garbage then we decode again using the previous hour 1200

edit add: the time value is also hashed in some way.  Or can be a lookup into a string of constant 'random' chars that both encoder and decoder know

edit add more: just say that all we are really doing here is obfuscation not cryptography in any real sense

another obfuscation technique is to channel hop to help with frustrating any man-in-middle.  The message contains some random channel number for the next message, which can be time based as well. Use this channel for the next time period. Like the password time hash we have to compensate. For a time (say up until 5 minutes after the hour) the decoder script has to listen on two channels. The next channel and the previous channel

Edited by Mollymews
  • Haha 1
Link to comment
Share on other sites

we can validate who a message is from with:

listen(..., key id, ...)
{
   if (llGetOwnerKey(id) == key_of_our_correspondent) ... we are good ...
}

what I think is happening with ItHad's app is that people are trying to listen/learn what message needs to be sent to the game server object that will fool the game server object into believing they have completed the task/level when they haven't

with this kind of attack the villain doesn't have to decode the message, they just have to know which encoded string to send as it relates to them

so we need a way where the same message from the same player/agent can be encoded in multiple different ways, yet the decoder can resolve the different encodings back to the same message

a place to start with this is to vary the data contained in the message: For example:

- encoded letters can be any case. So mix them up. Sometimes use 'A' sometimes 'a'. Decoding with llToUpper/llToLower
- numbers can be either decimal or hex. type chosen randomly
- value separators can be any of upto 8 chars chosen at random. decode with llParseStringToList(..., ["~", "$", "_", ";", "."], ...)
- values can be in any order

example: A20;b0x10F~C-3487521$K8z3Fs7SWf.t12435.SHello

parsing this A = 20, b = 0x10F, C = -3487521, K8z3Fs7SWf, t = 12435, S = Hello

where A = A, b = B, C = C, t = T, S = S are keys followed by the data for the key type

any other char in the first position following a separator signals noise randomly inserted into the message, which can be discarded

then we can randomise the order of these. For example

C-3487521$SHello;A20_K8z3Fs7SWf.t12435.b0x10F

because key value pairs this can be parsed to mean the same as: A20;b0x10F~C-3487521$K8z3Fs7SWf.t12435.SHello

we can stick more than 1 block of noise in any position into our message also, each block of noise being of any random length

then run it thru our cipher encoder

Edited by Mollymews
underscore
Link to comment
Share on other sites

14 hours ago, Kyrah Abattoir said:

Sounds like encryption isn't what you need, message signing is.

sender -> message + sha1(message + secret)

receiver -> if( sha1(rcv_message + secret) == rcv_signature) then...

Yes this is one thing. Signing a message in such a way that it can't be used multiple times

 

12 hours ago, Mollymews said:

we can validate who a message is from with:


listen(..., key id, ...)
{
   if (llGetOwnerKey(id) == key_of_our_correspondent) ... we are good ...
}

what I think is happening with ItHad's app is that people are trying to listen/learn what message needs to be sent to the game server object that will fool the game server object into believing they have completed the task/level when they haven't

with this kind of attack the villain doesn't have to decode the message, they just have to know which encoded string to send as it relates to them

And also obfuscating the message so that it also can't be read.

I think so far from what I have gathered on this thread and looked at myself. I think that...

Creating a value from server time (that obviously isn't just direct server time) to sign the message maybe

And then obfuscating the message through one of the methods listed above, perhaps using a value obtained from an avatars key, perhaps not.

I think the most important thing is the message not being able to be used more than once or at least for more than maybe 10 seconds.

Maybe with the server time idea I could have two "buffer" variables that the old code rolls onto for 10 seconds, making a message only viable for 30 seconds at most. Maybe even less time than that.

Link to comment
Share on other sites

4 hours ago, ItHadToComeToThis said:

obfuscating the message so that it also can't be read

when I see words like encryption and random then my ears perk up, and I can end filling my own ears with my own blah blah blah, obfuscating the actual question in the stated problem

the problem question is: Can we send a gameplay plaintext message known to any/every one on a known channel, and not have that message used for a gameplay illicit purpose ?

the answer is yes when the devices we make for other people to use/own are uniquely named and permissions are No-Modify

listen(integer channel, string name, key id, string message)
{
   list details = llGetObjectDetails(id, [OBJECT_NAME, OBJECT_CREATOR, OBJECT_CREATION_TIME, OBJECT_OWNER]);
   integer ok =
      (llList2String(details, 0) == NameOfMyDevice) &&
      (llList2Key(details, 1) == MyCreatorKey) &&
      (llList2String(details, 2) == TimeMyDeviceCreated);
   if (ok)
   {
      key player = llList2Key(details, 3);
      
      ... action message from this player ...
   }
}

NameOfMyDevice is unique. Nothing I make has the same name as anything else I make. It can't be changed because No-Modify
MyCreatorKey is unique and can't be spoofed
TimeMyDeviceCreated is a constant timestamp which survives rez and owner transfer and can't be spoofed. It is not possible for me, from the Build menu to ordinarily create two new prims with the same timestamp

Link to comment
Share on other sites

@Mollymews @Kyrah Abattoir

What do you think to this as a signing idea. Can you offer any improvement on this or does this seem satisfactory?.

 

Sender

integer sChan=-214762581;

default{
    state_entry(){
        llSetText("Sender",<1.0,1.0,1.0>,1.0);
        key user=llGetOwner();
        string sign=llSHA1String(llMD5String((string)user+(string)(llGetUnixTime()/10),10));
        llOwnerSay("Sending : "+sign);
        llRegionSay(sChan,(string)user+",message,value,"+sign);
    }
}

 

Receiver

list pStr=[];
integer sChan=-214762581;
key user;

string cMsgSign;
string lMsgSign;

default{
    state_entry(){
        llSetText("Receiver",<1.0,1.0,1.0>,1.0);
        llListen(sChan,"",NULL_KEY,"");
        user=llGetOwner();
        cMsgSign=llSHA1String(llMD5String((string)user+(string)(llGetUnixTime()/10),10));
        llSetTimerEvent(1.0);
    }
    listen(integer channel, string name, key id, string message){
        pStr=llCSV2List(message);
        //llOwnerSay(llList2CSV(pStr));
        //llOwnerSay(llList2String(pStr,llGetListLength(pStr)-1));
        llOwnerSay("Checking against\nFirst : "+cMsgSign+"\nSecond : "+lMsgSign);
        if(llList2String(pStr,llGetListLength(pStr)-1)==cMsgSign){
            llOwnerSay("Passed");
        }
        if(llList2String(pStr,llGetListLength(pStr)-1)==lMsgSign){
            llOwnerSay("Passed2");
        }
    }
    timer(){
        if(cMsgSign!=llSHA1String(llMD5String((string)user+(string)(llGetUnixTime()/10),10))){
            lMsgSign=cMsgSign;
            cMsgSign=llSHA1String(llMD5String((string)user+(string)(llGetUnixTime()/10),10));
        }
    }
}

 

Edit 1 I should probably add...dont mind the two if statements for cMsgSign and lMsgSign. They are just to check if each signing is registered

Edit 2 : I should further add that, incase anyone wonders, this is just my concept at the minute so I dont mind sharing the signing method xD

Edited by ItHadToComeToThis
Link to comment
Share on other sites

whenever we do any kind of encoding then we want to see it from the pov of a villain, who is looking for patterns in the transmitted string.  In this case we are adding noise with the unix clock

what we should do is write a test script to see what our villain sees:

example:

integer counter;

default
{
    state_entry()
    {  
        llOwnerSay("begin..");
        llSetTimerEvent(1.0); // fire approx. every 1 seconds
    }

    timer()
    {
        key user = llGetOwner();
        integer mark = llGetUnixTime();
       
        integer time = mark;
        
        string sign = llSHA1String(llMD5String((string)user+(string)(time/10),10));
        llOwnerSay("N: " + sign);
        
        time = mark + 60; // add a minute
        sign = llSHA1String(llMD5String((string)user+(string)(time/10),10));
        llOwnerSay("M: " + sign);
        
        time = mark + 3600; // add an hour
        sign = llSHA1String(llMD5String((string)user+(string)(time/10),10));
        llOwnerSay("H: " + sign);
        
        time = mark + 86400; // add a day
        sign = llSHA1String(llMD5String((string)user+(string)(time/10),10));
        llOwnerSay("D: " + sign);
               
        if (++counter == 60) 
        {
            llSetTimerEvent(0.0);
            llOwnerSay("...end");
        }
    }
}

 

how secure we think any encoding might be is a judgment call. Basically how much effort are our villains going to put in to break our game/app

Link to comment
Share on other sites

a thing with a shared secret is that is best that we vary the secret from message to message, in some decodable way

any study of the output messages will easily show that the messages include some time based component, and the villain's guess will always start with unix time

i would probably use (unix time / 10) to seed a prng which generates 'random' numbers, rather than encode (unix time/ 10) itself. By adding some kind of prng into the mix, our villain has to guess what that prng is as well

Link to comment
Share on other sites

4 hours ago, Kyrah Abattoir said:

You already have a time element. You do understand that sha1 is non-reversible right?

the issue isn't that the encoded-string is non-reversible. The issue is that a secret is not a secret when it can be determined

unix time is not a secret. With  sha1(mykey + unix time) then the output looks random but it isn't

is trivial for our villain to write a program that will produce sha1(mykey + unixtime) for all unix time. At this moment in time, use this authenication code from the list of authentication codes that the program has produced

the user's key is not a secret, nor is unix time.  If we are going to use time, then we use the time as a seed for a pre-mixer before we sha1 it. HadIt has chosen to use MD5 as a pre-mixer seeded with time. I would go with another kind of pre-mixer, like a custom prng

in this case the mixing algorithm is the secret

 

Edited by Mollymews
seeded
Link to comment
Share on other sites

5 minutes ago, Kyrah Abattoir said:

I did not say that unix time was a secret but that your code still NEED a shared secret and maybe also a completely random element.

yes

but we can't put a random element into an authentication code.  The receiver can't know what that random element is.  So the element has to be deterministic, and it has to vary so that we don't send the same code repeatedly

Link to comment
Share on other sites

Here's some authentication code I use.

key logstring(string s)                     // sends a POST to an API endpoint for logging
{   if (gLogURL == "") { return(NULL_KEY); }// logging not enabled
    s = llStringTrim(s,STRING_TRIM);        // trim string for consistency
    list params = [HTTP_METHOD, "POST"];    // send with auth token
    params = params + [HTTP_CUSTOM_HEADER, "X-AUTHTOKEN-NAME"] + gLogAuthTokenName;
    params = params + [HTTP_CUSTOM_HEADER, "X-AUTHTOKEN-HASH"] + llSHA1String(gLogAuthTokenValue + s); // key is SHA1 of of authtoken followed by message
    return(llHTTPRequest(gLogURL, params, s)); // make HTTP request
}

I want to log string s to my server at url gLogURL. s is a JSON-formatted string, although that doesn't matter here.

First, the string is trimmed, to avoid any ambiguity about where it begins and ends, which matters when taking a secure hash.

Then the HTTP parameters are used to carry some data. It's a POST operation, since we're sending a string. Two custom headers are added; X-AUTHTOKEN-NAME and X-AUTHTOKEN-HASH. Think of the "name" as an ID which tells which hash is in use. The X-AUTHTOKEN-HASH is the SHA1 secure hash of gLogAuthTokenValue followed by the string being sent.

At the receiving end, the server extracts the SHA1 hash, which is always 40 characters, and the string s, which is the rest. It trims the string, just in case HTTP stuck a line feed or a null or something at the end. Then the server takes the token value, which is a secret known by both LSL script and server, appends s, and computes the SHA1 hash. This must match the hash value just extracted from the received message. If they match, both ends are using the same shared secret value, the one in gLogAuthTokenValue.

Known vulnerabilities:

  1. Someone getting hold of the key from the LSL script. The part of the script with the key must be no-mod at all times and not visible to the public anywhere. This is the most likely cause of trouble.
  2. Replay attack. Someone could intercept a message and send it again. If sending the same message twice is a problem, messages need timestamps or serial numbers to prevent that. That's useful anyway, in case something retries.
  3. Breaking SHA-1. From a cryptographic perspective, SHA-1 has been "broken". It took 110 GPU-years on 2017-vintage GPUs. So someone could, for US$20,000 - US$50,000 in AWS bills, send one message of their choosing to your server. How much money does your opponent have?  (SHA-2 is more secure and currently recommended, and LSL should offer a built-in SHA2 function, but it's not available yet. SHA-2 in LSL code is available but slow.)
Link to comment
Share on other sites

I've used a fairly simple encryption scheme and it has served well.

* Sender generates a random salt (pick whatever method you like, seeded random number, whatever)

* Generate MD5/SHA from the concatenation of a password (hard coded in the script) and the salt.

* Generate the encrypted text with llXorBase64 using the MD5/SHA as the encryption key.

* Send the salt followed by a separator and then the encrypted data to the receiver (web or another object)

* The receiver strips off the salt, generates an MD5/SHA from the embedded password and the salt, and then decrypts the Base64 using llXorBase64 to get the original data.

I dislike time based salts as there is always the possibility that the sender/receiver will calculate them differently. Maybe not the most hardened way to encrypt data, but it is easy, and as long as you aren't repeating salts frequently, it would be difficult to obtain the shared secret... especially if you append/prepend additional random trash data to the data you are encrypting that is just tossed out after decryption.

 

Edited by Phate Shepherd
Link to comment
Share on other sites

6 hours ago, animats said:

my server at url gLogURL.

yes to everything you mention

when we are using our own web server or Experience KVP then the problem is a lot less complicated as our villain, from within the LSL environment, can't view the messages being passed

the OP posit is that in the situation where we aren't using our own web server and not using KVP then how might it be done ?

which is pretty much some method of obfuscation/encryption of the message. A method that will generate, as many as feasibly practical, different encodings for the same message. Adding random salt/noise to the message before encoding is the most feasibly practical. The salt/noise being stripped out by the decoder

the best LSL source for random salt/noise is llFrand(). As llFrand() is not deterministic, at least from within LSL

the only place we use a deterministic source of random (our own prng or secret string(s)) is when creating authentication codes or passwords

Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 1475 days.

Please take a moment to consider if this thread is worth bumping.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...