Max Did It
Coding
,Mon
5 Comments
Tagged , ,

Asymmetric Encryption with Flash and PHP

I have recently delved into the topic of encryption, since I wanted to send encrypted data from a Flash application to a PHP script. I had to implement this manually since I didn’t have the option of using HTTPS on my web space.

In this article, I describe

  • how to generate an asymmetric encryption key in PHP,
  • decode the public key in Actionscript 3 and
  • use it to encrypt data which is then sent and decrypted on the server side.

Asymmetric encryption works by generating two keys, the public and the private key. The public key is used to encrypt data, while the private key is used to decrypt data.

In my case, I generate the key pair on the server, sending the public key to the Actionscript client, while the server keeps the private key. This way, the client is able to encrypt data with the public key, but only the server can read the encrypted data with the private key.

Intercepting the public key doesn’t enable you to read the data sent by the client. This method is still vulnerable to man-in-the-middle attacks, since it still enables others to send their own encrypted data with the public key if they manage to intercept the communication between server and client. This means that there should be further authentication between server and client.

I am using the as3crypto library to encrypt the data on the Actionscript 3 side. Some missing features in the library still make it necessary to implement your own key decoding functionality. This is needed to actually use the public key sent by the server.

Requesting the Public Key

I am using two PHP scripts in this example.

  • The request.php script is called by the Actionscript client to request a public key.
  • The receive.php script will receive the encrypted data.

In the first step, the Flash client sends a request to the request.php PHP script. I use the URLLoader class to do this, and to be able to read the data sent back by the server.

Note: It is a good idea to send further parameters in this request that authenticate the client with the server. This gives the server the opportunity to deny public key requests by other Flash applications or entirely different sources.

The request itself is simple enough:

private function requestPublicKey():void
{
	// URL request to the address where the request.php is located.
	var urlRequest:URLRequest = new URLRequest("request.php");
 
	// send the request
	var urlLoader:URLLoader = new URLLoader();
	urlLoader.addEventListener(Event.COMPLETE, handleRequestComplete);
	urlLoader.load(urlRequest);
}

I will describe the handleRequestComplete function in one of the next sections.

It is a good idea to add further event listeners to the URLLoader in order to handle errors, but I have left them out of the example for brevity.

Generating and Sending the Public Key

In the request.php script on the server side, the first thing we want to do (aside from optionally authenticating the request) is to generate the key pair. To do this, I use the openssl_pkey_new function. This function will generate a private/public key pair using the RSA algorithm.

The function returns a resource that stores the data of the encryption keys. In the next step, I extract the public key from this resource. This is done with the openssl_pkey_get_details function. This function returns an array with information about the key resources. The public key is stored in the key field of the array.

In the next step, I simply print the public key to send it back to the client in the HTTP answer returned by the PHP script.

$key_resource = openssl_pkey_new();
$key_details = openssl_pkey_get_details($key_resource);
$public_key = $key_details['key'];
print($public_key);

Storing the Key as a Session Parameter

There is another thing we want to do before the request.php script finishes. The request.php is only responsible for giving out the public key, the receive.php will be the script that decrypts the data sent by the client.

That means, of course, that the receive.php script needs to have access to the key generated by the request.php. To achieve this, we simply start a session in the request.php script and store the key as a session variable. This way, the receive.php script can resume the session and retrieve the key.

First, we start a session in the request.php script with the session_start() function. This will generate a session ID internally and send it back to the Actionscript client in the response as a cookie.

The neat thing is that Flash handles cookies received by URLLoader internally. That means you don’t have to do anything on the client side to manage the session data. Flash will automatically add the session ID cookie to your request to the receive.php script, provided that the PHP script is on the same domain as the request.php file.

Then, we store the key pair in the $_SESSION array.

Note: The naive way of doing this would be to just assign the key resource returned by openssl_pkey_new to the $_SESSION array:

$_SESSION['key'] = $key_resource;

However, if you try to access the resource later in another script, you will find that the field in the array is set, but empty.

The reason is that the key resource will be freed and the data removed by garbage collection after the script finishes, leaving a variable pointing to nothing.

The easiest way around this is to export the key into a string with openssl-pkey-export. The PEM encoded string returned by the function can later be used to retrieve the original key.

The second half of the request.php file would look something like this:

session_start();
 
$exported_key;
openssl_pkey_export($key_resource, $exported_key);
 
$_SESSION['key'] = $exported_key;

Retrieving the Public Key

Alright, one simple chapter before it gets ugly. If you echo the public key in the PHP script, then the Flash client should receive it as data when the URLLoader completed it’s request.

We retrieve the data in the handleRequestComplete event handler function described above.

private function handleIdentificationComplete(e:Event):void
{
	var urlLoader:URLLoader = (e.target as URLLoader);
	var keyData:String = urlLoader.data;
}

Easy enough. The retrieved data should look similar to this:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEpguIcFYt34v0Y2YmhzEuVmjs
GV1YVStuYbCqrVODRjsXml0v+jPUgbkH6cyJftTx/XQckVAs9taP9ZFN06xfJvJo
kKe7ScmrowUQOJQWWy1iuYNBLy7R6u26l7SvF8r47ReUGS8sZvAcOHVmlqxc1A2n
NPUMuKK05YEXs4k4YwIDAQAB
-----END PUBLIC KEY-----

So, lets use this key.

Decoding the Key

And this is where it gets nasty. I use the as3crypto library to encrypt the data using the public key. The public key is encoded in the PEM format.

The as3crypto library does have a PEM class to decode keys in this format. However, trying this with the keys sent by PHP will result in an error and a trace message similar to this:

I DONT KNOW HOW TO HANDLE DER stuff of TYPE 1

The author of the library started implementing a new version of the class, but unfortunately, never finished it.

We still can use as3crypto for the encryption itself. We need to pass two parameters to the RSAKey.parsePublicKey function. These two parameters are contained in the data send by the server. We need to decode it.

The first step is easy enough. You need to remove the header, footer and line breaks of the data you received.

var encryptedKey:String = keyData.replace("-----BEGIN PUBLIC KEY-----", "");
encryptedKey = keyData.replace("-----END PUBLIC KEY-----", "");
encryptedKey = keyData.replace(/\s/g, "");

The string you are now left with is encoded with the Base64 method. The as3crypto library gives you the necessary functions to decode the string. Make sure to decode the string into a ByteArray. Since the data might contain null bytes, decoding it into a string might cut off part of the data.

var decryptedData:ByteArray = Base64.decodeToByteArray(encryptedKey);
decryptedData.position = 0;

We are now left with a ByteArray that has the information we want encoded in the DER subset of the ASN.1 format.

There were two sources that helped me a great deal to decode that data:

The ASN.1 format consists of different “tags”, which are data types that can contain other tags in them. I have implemented the decoder as a function that recursively calls other functions which decode the types contained in the byte data. This implementation is still quick and dirty and there are many things that can be optimized or solved in a more elegant way, but for now, it does the job.

private function decodeASN(decryptedData:ByteArray):Object
{
	// read by
	var tag:int = decryptedData.readByte() & 0xFF;
 
	var tagClass:int = (tag >> 6) & 3;
	var tagEncoding:int = (tag >> 5) & 1;
	var tagNumber:int = tag & 0x1F;
 
	var length:int = decodeLength(decryptedData);
 
	var result:Object;
 
	switch (tagNumber)
	{
		case 2:
			// Integer
			result = decodeInteger(decryptedData, length);
			break;
 
		case 3:
			// Bit String
			result = decodeBitString(decryptedData, length);
			break;
 
		case 6:
			// Object Identifier
			result = decodeObjectIdentifier(decryptedData, length);
			break;
 
		case 16: 
			// Sequence
			result = decodeSequence(decryptedData, length);
			break;
 
		default:
			decryptedData.position += length;
	}
 
	return result;
}

This is the main function. It only supports four different types of tags, since these are the only ones contained in the public key. Encountering tags it doesn’t know will cause the function to simply skip the respective blocks of data.

At first, the function reads a single byte which contains the tag info. Note: this only works with the simple tag types we expect to encounter. ASN.1 can contain tags which need several tag info bytes.

After that, the length of the tag’s data is calculated. This is done in the decodeLength function:

private function decodeLength(decryptedData:ByteArray):int
{
	var lengthByte:int = decryptedData.readByte();
	var longForm:Boolean = ((lengthByte >> 7) & 1) == 1;
	var length:int = lengthByte & 0x7F;
	var result:int = 0;
 
	if (longForm)
	{
		for (var i:int = 0; i < length; i++)
		{
			result <<= 8;
			result += (decryptedData.readByte() & 0xFF);
		}
	}
	else
	{
		result = length;
	}
 
	return result;
}

Then, we have the four functions that decode the content of the different tag types. I will post them here without detailed description, anyone interested in the functionality can check out the links posted above.

This is the function I use to decode Sequence tags. I simply store the data in an array and recursively call the decodeASN function again to decode the tags contained in the sequence.

private function decodeSequence(decryptedData:ByteArray, length:int):Array
{
	var end:int = decryptedData.position + length;
	var array:Array = new Array();
 
	while (decryptedData.position < end)
	{
		array.push(decodeASN(decryptedData));
	}
 
	return array;
}

This is the function I use to decode ObjectIdentifier tags. This is not strictly necessary to decode the public key, but the information could be used to make sure that the data we are decoding actually contains what we expect. In this case, decoding the public RSA key’s object identifier should result in the value "1.2.840.113549.1.1.1";

private function decodeObjectIdentifier(decryptedData:ByteArray, length:int):String 
{
	var end:int = decryptedData.position + length;
 
	var result:String = "";
	var currentByte:int = decryptedData.readByte() & 0xFF;
 
	var value1:int = int(currentByte / 40);
	var value2:int = currentByte - (value1 * 40);
 
	result += value1 + "." + value2;
 
	var currentValue:int = 0;
 
	while (decryptedData.position < end)
	{
		currentByte = decryptedData.readByte() & 0xFF;
		if(((currentValue >> 7) & 1) == 0)
		{
			result += "." + currentValue;
			currentValue = 0;
		}
	}
 
	return result;
}

In our case, decoding the BitString tag type is not very spectacular, as it just contains another sequence.

private function decodeBitString(decryptedData:ByteArray, length:int):Object 
{
	var offset:int = decryptedData.readByte() & 0xFF;
 
	return decodeASN(decryptedData);
}

And finally, the most important function which decodes the Integer tag types. There are two integers stored in the byte array that we need for our key. Note: I don’t store the result of the integer data in Actionscript’s int or Number types. At least one of the numbers is bigger than the 64bit size that Number offers. This is why I store their hex representation in a String, which is also the format that as3crypto’s function expects it in.

private function decodeInteger(decryptedData:ByteArray, length:int):String 
{
	var end:int = decryptedData.position + length;
 
	var byteArray:ByteArray = new ByteArray();
	decryptedData.readBytes(byteArray, 0, length);
 
	var result:String = Hex.fromArray(byteArray);
 
	return result;
}

Alright, with this, we can decode the public key data. The result of this specific implementation is an array containing further arrays and objects with data in them. I use the returned data to create as3crypto’s RSAKey object.

var decodedData:Object = decodeASN(decryptedData);
var key:RSAKey = RSAKey.parsePublicKey(decodedData[1][0], decodedData[1][1]);

The two parameters that I pass to the RSAKey.parsePublicKey function are the two integers contained in the ASN.1 encoded data.

Encrypting the Data

Then, finally, we can take our data and encrypt it with the public key we just painstakingly obtained.

The encrypt function of as3crypto’s RSAKey class expects two ByteArrays: One with the data you want to encrypt, and one in which the encrypted data will be put in.

var message:String = "Wow, this was quite the ride."
 
var messageBytes:ByteArray = new ByteArray();
messageBytes.writeUTFBytes(response);
messageBytes.position = 0;
 
var messageEncrypted:ByteArray = new ByteArray();
key.encrypt(messageBytes, messageEncrypted, messageBytes.length);

Sending the Encrypted Data to the Server

Once the message is encrypted, we want to send it back to the server. You do this by creating another URLRequest, setting it’s method to POST and then just passing it the encrypted ByteArray.

We then create another URLLoader to send the data.

var urlRequest:URLRequest = new URLRequest("retrieve.php");
urlRequest.method = URLRequestMethod.POST;
urlRequest.data = messageEncrypted;
 
var urlLoader:URLLoader = new URLLoader();
urlLoader.addEventListener(Event.COMPLETE, handleSendComplete);
 
urlLoader.load(urlRequest);

Decrypting the Client’s Message

Now we are back on the server side. In the retrieve.php script that we sent our data to, we first need to retrieve the private key that we have generated before. To do this, we first resume the session, get the session variable and reconstruct the key from the PEM encoded string. We use the openssl_pkey_get_private function for this.

session_start();
$keyString = $_SESSION['key'];
$keyRessource = openssl_pkey_get_private($keyString);

Then, we retrieve the data sent by the client. To retrieve the raw data in the HTTP POST message, we use file-get-contents.

$message= file_get_contents('php://input');

And, finally, we decrypt the message with, surprise, openssl_private_decrypt.

$decrypted_message;
openssl_private_decrypt($message, $decrypted_message, $keyRessource );

There we go! The variable $decrypted_message now contains the message sent by the client.

Comments

5 Comments have been posted so far - Leave a Comment

robro
robro
,Mon

You can't possibly imagine how helpful your post was! Where's your Paypal / Flattr button?

Reply

Max Knoblich
Max Knoblich
,Mon

Thanks, I'm glad you liked it! I don't have a donate button yet, but I might include one soon.


Cozzbie
Cozzbie
,Wed

currentValue >> 7) & 1) == 0)
{
result += "." + currentValue;
currentValue = 0;
}

Flash compiler throws an error at this point

Reply

Max Knoblich
Max Knoblich
,Wed

Thanks for the heads up. The "if" has been cut off for some reason in that line. I have corrected it.


Anthony Pace
Anthony Pace
,Thu

The encryption method you are using is unsafe, as it allows an MITM attack. Public/Private key based encryption is only safe when using a known public key from a Root Certificate Authority stored on the client; which, is the reason you have a certificate storage on your OS that the typical browser makes use of.

Nice article; yet, unless it's a known key, this method is kind of pointless against a hacker, or even a script kiddie, that knows what he or she is doing.

What you could do, if the app was downloaded via an appstore/marketplace, is include the known key, and do proper comparisons.

Reply

Leave A Comment

Your email address will not be published. Required fields are marked *

Connect with Facebook

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>