I needed an encryption system to handle in world payments being sent to my webserver.
I needed a system that would also be safe from message spoofing. I needed to make sure that the datastream URL could not be simply copied and resent. It would be bad if someone somehow copied the datastream URL for a payment and resent it over and over again to the server.
This system (when used with a MySQL DB to store the used keys) will ensure no message can be copied and sent twice.
Since I'm no encryption "Expert", I would like some input into this here system I've developed. Simply put the LSL script in an object and it should hook-up with my webserver.
If this script can be validated as being a decent solution, I would also like it to be made available in the script library as it would seem to be safe if the private keys I use are kept private.
Script for in world object
LSL ========================= LSL
// Written by Zolen Giano
//This script demonstrates passing encrypted data to a PHP webserver and back.
//This script encrypts both the data and public key with a random number.
//Then decrypts the returning data with same random number
//private key for encrypting random seed
string encryptionkey = "MyPrivateKey";
//User Variables
integer MainTimer = 30; //does a cycle every 30 seconds
integer SleepTimer = 5; // seconds to pause after a request
string split="|"; //the field delimiter for parsing
//HTTP info - Server and webpage (don't hammer on my server plz)
string baseURL = "http://clubworks.co.cc/testcrypt.php?";
//dont forget the '?' on the end of pagename
// System Variables
integer channel;
string seed;
string secretkey;
integer fails;
integer goods;
string OwnerName;
integer DataCycles;
key httprequest;
default
{
state_entry()
{
channel=(integer)llFrand(100000)-1000000;
OwnerName=llKey2Name(llGetOwner());
llSay(0, "Warming Up...Hello "+OwnerName+". Communicating on channel "+(string)channel+".");
llSetText("Warming up...",<1,0,0>,1);
llSetTimerEvent(5);
}
on_rez(integer param)
{
llResetScript();
}
touch_start(integer total_number)
{
if (llDetectedKey(0)!=llGetOwner())
llWhisper(0,"Sorry, controlled by "+OwnerName+" only.");
else
{
llSetTimerEvent(MainTimer);
llDialog(llDetectedKey(0),"Please Select", ["Stats", "Reset"],channel);
llListen(channel, "", llGetOwner(), "");
}
}
listen(integer thsichannel, string name, key id, string msg)
{
if (msg=="Stats")
{
llOwnerSay("Free Memory: "+(string)llGetFreeMemory()+" Bytes Free.");
llOwnerSay("Data Cycles: "+(string)DataCycles+". 1 cycle every "+(string)MainTimer+" seconds.");
llOwnerSay("Good Requests: "+(string)goods);
llOwnerSay("Fails: "+(string)fails);
}
if (msg=="Reset")
{
llResetScript();
}
}
timer()
{
// set the timer
llSetTimerEvent(MainTimer);
// count cycles for stats
DataCycles++;
//sample data
string data1=(string)llKey2Name(llGetOwner());
string data2=(string)llGetRegionName();
string data3=(string)((integer)llGetRegionFPS());
//built the data string
string datum=data1+split+data2+split+data3;
//make a large random seed
seed=(string)((integer)llFrand(100000000)+100000000);
//add the seed number to the beginning of the data string for checking later
datum=(string)seed+split+datum;
//encrypt the data string with the random seed
string secret=llXorBase64StringsCorrect(llStringToBase64(datum),llStringToBase64((string)seed));
//encrypt the random seed with our private encryption key
string secretkey=llXorBase64StringsCorrect(llStringToBase64((string)seed),llStringToBase64(encryptionkey));
//build our encrypted URL to send
string thisurl="&k="+llEscapeURL((string)secretkey)+"&d="+llEscapeURL(secret);
//Show progress report in yellow hovertext
llSetText("Sending Data:\n"+(string)thisurl,<1,1,0>,1);
llSleep(SleepTimer);
//Send the request to webserver
httprequest = llHTTPRequest(baseURL+thisurl,[HTTP_METHOD,"GET"],"");
}
//Webserver response
http_response(key request_id, integer status, list metadata, string body)
{
if (request_id == httprequest)
{
//Show status in white
llSetText("Server Response:\n\n"+body, <1,1,1>, 1);
llSleep(SleepTimer);
//Parse the webserver response
list parsed = llParseString2List( body, [ split ], [] );
//pick out the returning encrypted seed
string returnseed=llList2String( parsed, 1 );
//decrypt the returning seed with our private encryption key
returnseed=llBase64ToString(llXorBase64Strings(returnseed, llStringToBase64(encryptionkey)));
//Check if our returning seed is the same as our original random seed
if ((string)seed==returnseed)
{
// add to statistics
goods++;
//pick out the encrypted message from the web server response.
string returnsecret=llList2String( parsed, 2 );
//decrypt our returning message with our seed
string returnmessage=llBase64ToString(llXorBase64Strings(returnsecret, llStringToBase64(seed)));
//Show decrypted message in green hovertext
llSetText("Decrypted Message:\n"+returnmessage,<0,1,0>,1);
}
else
{
// add to statistics
fails++;
//Show Failed Status in Red
llSetText("Data Request Failed\nWaiting for Next Cycle",<1,0,0>,1);
}
//limit the number of cycles to 10.
//remove this if you want to hammer on the web server forever
if(DataCycles >10) {llOwnerSay("I'm tired. Sleeping...");llSetTimerEvent(0);}
}
}
}
/LSL=================/LSL
Webserver script - testcrypt.php
PHP=======================PHP
<?
//Round trip SL to PHP encryption example written by Zolen Giano
//this PHP script decrypts and processes a message sent from SL,
//and returns an encrypted response.
//This system uses random public keys which are decrypted by the
//internal private key to produce a unique data stream every time.
// your private key
$encryptionkey="MyPrivateKey";
//field seperator for parsing
$split="|";
//Simple XOR encryption function
function simplexor($s1, $s2)
{
$s1 = base64_decode($s1);
$s2 = base64_decode($s2);
while(strlen($s2) < strlen($s1))
$s2 .= $s2;
return base64_encode($s1 ^ $s2);
}
//grabs the incomming variables
$thiskey=$_GET['k'];
$secret=$_GET['d'];
//Here is a good spot to do your DB query for duplicate keys.
//store the incomming thiskeys in your DB
//If thiskey already exists in the DB means this is a spoofed or repeated message.
//decrypt the public key using our private key to get the original random seed.
$seed=base64_decode(simplexor($thiskey,base64_encode($encryptionkey)));
//decrypt the data using the random seed
$datum=base64_decode(simplexor($secret,base64_encode($seed)));
//parse the data into an array
$parsed = explode ($split,$datum);
//Security check to make sure the outer seed exists and equals the inner seed
if ($seed!="" and ((string)((integer)$seed)==$parsed[0]))
{
//do our processing...
//this is where you can insert your parsed bits to your database
//Send some unencrypted data back to SL for fun
echo "Server Clubworks.co.cc Online\n".$split;
//build a data string we want to encrypt.
$datum = "\nWelcome ".$parsed[1]." from ".$parsed[2].".\n\n Your region FPS is ".$parsed[3];
//encrypt the seed and send it back to SL for checking
echo (simplexor(base64_encode((string)$seed),base64_encode($encryptionkey)).$split);
//encrypt our data stream and send it
echo (simplexor(base64_encode($datum),base64_encode($seed)));
}
else
{
//failed security test
echo "Unauthorized Access";
die();
}
?>
/PHP================/PHP
Cheers!
- zg