Home  >  

Introducing Nitro-LM "Lite"

AddThis Social Bookmark Button
nitrolm_lite.png

 

 

 

 

I've been consulting for Simplified Logic the creators of Nitro-LM full time for the past two years. Nitro-LM is a licensing and encryption solution for software written in C/C++, Java, Eclipse, Eclipse Plugins, and Adobe Flex and AIR. It differs from other software protection mechanisms out there in that it allows you to maintain control of your software from the Nitro-LM hosted cloud service while still allowing you to custom tailor the user's licensing and registration experience. This allows for a much less draconian approach to software licensing and protection than you're probably experienced in the past.

During this time, I've also given numerous demos and sat in on sales calls that go something like this...

"Wow, I didn't realize your product did all this stuff. I'll definitely look into it in the future. Right now though, I've already implemented a username/password system. I was just looking for a way to encrypt and protect my Flex/AIR app from being decompiled. Does Nitro-LM have an option for just doing encryption only?

Nitro-LM "Lite" was announced at 360|Flex Indy to fill this obvious gap in the product line as well as meet customer demand for a solution to the problem of Flex and AIR decompiling. Since I'm a developer myself, I thought the best way to introduce this product to other developers would be a walk-thru of encrypting an example application of an online store. The example I'll encrypt is the standard Flex Store app used to teach the Cairngorm micro-architecture. The Nitro-LM SWC itself is just an ActionScript API and can easily be wrapped or bundled into any of the various micro-architectures.

First, let's take a look at the unencrypted store app.

flex_store.png

Direct Link

Nitro-LM "Lite" doesn't specify what you're allowed to encrypt. You can encrypt images, small videos, Flex Applications, Flash swfs, or Modules. In this example, we're going to convert the entire application into a single Flex Module and embed it inside a Flex Application wrapper that'll handle the Nitro-LM communication and decryption. In a production application, I recommend that you break your application into many modules and make them as small as possible. While Nitro-LM "Lite" performance is unnoticeable in most instances, an end-user trying to decrypt a 400kb object in memory in the flash player player might see some delay. I try to keep the objects/modules I decrypt under 150kb or less. This example is a bit on the heavy side since I'm decrypting an entire large module instead of breaking it down into smaller chunks.

First, let's take a look at the modifications I made to the main flexstore.mxml file.

Original:

 

<!--
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
// All Rights Reserved.
// The following is Sample Code and is subject to all restrictions on such code
// as contained in the End User License Agreement accompanying this product.
// If you have received this file from a source other than Adobe,
// then your use, modification, or distribution of it requires
// the prior written permission of Adobe.
//
////////////////////////////////////////////////////////////////////////////////
-->
<mx:Application pageTitle="FlexStore"
				layout="absolute" 
				minWidth="990" minHeight="550"
			    preinitialize="onLoadTheme()"
			    creationComplete="onLoadCatalog()"
			    xmlns:screens="com.flexstore.view.screens.*"
			    xmlns:mx="http://www.adobe.com/2006/mxml">
...
</mx:Application>

Modified:

 

<!--
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
// All Rights Reserved.
// The following is Sample Code and is subject to all restrictions on such code
// as contained in the End User License Agreement accompanying this product.
// If you have received this file from a source other than Adobe,
// then your use, modification, or distribution of it requires
// the prior written permission of Adobe.
//
////////////////////////////////////////////////////////////////////////////////
-->
<mx:Module pageTitle="FlexStore"
				layout="absolute" 
				width="100%"
				minWidth="990" minHeight="550"
			    preinitialize="onLoadTheme()"
			    creationComplete="onLoadCatalog()"
			    xmlns:screens="com.flexstore.view.screens.*"
			    xmlns:mx="http://www.adobe.com/2006/mxml">
...
</mx:Module>

I've converted the main Application into an mx:Module and modified the width property. In the project properties inside FlexBuilder, I've made the modifications to compile flexstore.mxml as a module and created a new Application called EncryptedFlexStore.mxml. This EncryptedFlexStore application will be my container for the newly encrypted flexstore module. Let's take a look inside:

 

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()">
	<mx:Script>
		<![CDATA[
		import com.hurlant.crypto.rsa.RSAKey;
		import com.nitrolm.ILicenseClient;
		import com.nitrolm.LicenseClientEvent;
		import com.nitrolm.LicenseClientFactory;
		import com.nitrolm.NLMConstants;
		import com.nitrolm.ProductKeys;
		
		import de.polygonal.ds.HashMap;
		
		import flash.events.Event;
		import flash.system.ApplicationDomain;
		import flash.utils.ByteArray;
		
		import mx.controls.Alert;
		import mx.modules.ModuleLoader;
		
		//the embedded product key (allows for secure communication to Nitro-LM)
		[Embed (source="rQHrpVZjnlpstlfGQ1Pv.ser", mimeType="application/octet-stream")]
		private var ServerCommunicationKey:Class;
		
		//the encrypted version of the Flickr Book SWF
		//this will be copied into the source folder by the ANT script
		[Embed (source="flexstore_e.swf", mimeType="application/octet-stream")]
		private var EncryptedSWF:Class;
		
		
		//the product we created in the Nitro-LM administration tool
		private static const CAIRNGORM_STORE_PRODUCT:String = "rQHrpVZjnlpstlfGQ1Pv";
		private static const CAIRNGORM_STORE_VERSION:String = "1";
		
		//the factory that generates an ILicenseClient interface capable of communicating with Nitro-LM
		private var factory:LicenseClientFactory = null;
		
		//the main interface we'll use for communicating with Nitro-LM
		private var licenseClient:ILicenseClient = null;
		
		private function init():void
		{
 			//create an instance of our server communication key so that the Nitro-LM library
 			//will use it when communicating to the server in a secure fashion.
 			ProductKeys.putPublicKey(CAIRNGORM_STORE_PRODUCT, new ServerCommunicationKey());
 			
 			//initialize the factory and call initNitroLMDone with Event.COMPLETE
 			//we have to make sure we can communicate with the servers and everything is initialized
 			//before making any Nitro-LM API calls
 			factory = new LicenseClientFactory(initNitroLMDone);
 		}
		
		private function initNitroLMDone(event:Event):void
		{
 			//get an ILicenseClient interface
 			licenseClient = factory.getInstance();
 			
 			//request a decryption key for the Cairngorm Store product
 			licenseClient.addEventListener(LicenseClientEvent.LICENSE_RESPONSE, handleRequestKeyResponse);
 			licenseClient.requestKey(CAIRNGORM_STORE_PRODUCT, CAIRNGORM_STORE_VERSION);
 		}
		
		private function handleRequestKeyResponse(event:LicenseClientEvent):void
		{
 			//remove the event listener to clean up memory
 			licenseClient.removeEventListener(LicenseClientEvent.LICENSE_RESPONSE, handleRequestKeyResponse);
 			
 			//make sure we got a valid response code from Nitro-LM for our key request
 			if(event.response == NLMConstants.RESPONSE_OK)
 			{
  				if(event.data != null)
  				{
   					var keyBytes:ByteArray = (event.data as HashMap).find("key");
   					if(keyBytes != null)
   					{
    						var decryptKey:RSAKey = ProductKeys.readRSAPublicKey(keyBytes);
    						var decryptedSWF:ByteArray = licenseClient.decryptModule(new EncryptedSWF(), decryptKey);
    						
    						var loader:ModuleLoader = new ModuleLoader();
    						loader.applicationDomain = ApplicationDomain.currentDomain;
    						loader.percentWidth = 100;
    						loader.percentHeight = 100;
    						loader.loadModule("flexstore.swf", decryptedSWF);
    						this.addChild(loader);
    						return;
    					}
   				}
  			}
 			Alert.show(NLMConstants.responseToString(event.response));
 		}
		]]>
	</mx:Script>
</mx:Application>

In Nitro-LM "Lite", there are actually 4 keys (2 keypairs) in play. At the highest level, Nitro-LM uses RSA Public/Private keypairs for both communicating to the Nitro-LM cloud and encrypting assets.

The first keypair is the communication keypair. When you register for Nitro-LM "Lite", you download the public key (*.ser) file for your particular application. The private counterpart to this is never stored anywhere except on the cloud servers so that Nitro-LM can understand the requests you're sending it.

The second keypair is the encryption keypair. You also download the private encryption key (*.vser) file when you register for Nitro-LM "Lite". This file is used ONLY during the build process and should never get out into the clear.

The process an application goes through to decrypt an asset in memory is as follows.

  1. Use the public communication key to encrypt and send a request to the cloud for a decryption key. Note that ONLY the server can decrypt and understand this request.
  2. Receive and unpackage the decryption key from the server and use it to decrypt some type of asset. In our example, we're decrypting a Flex Module.

There are many additional layers of security and encryption built into Nitro-LM, but for the sake of a public article, we're only going to be discussing the outermost RSA layer that the developer will interact with.

If you've been able to follow the code thus far, you should have a fairly good idea of how all the decryption is working. But how do you actually encrypt and package the module SWF in the first place? The answer to this is in an ANT script that is used to compile the application. I use the standard flexTasks that ship with the Flex SDK for compiling the SWF and module files. I then also use a special nitrolm-encrypt task that ships as a JAR file along with Nitro-LM "Lite".

In my example, I use a 4-phase build process. I first build the wrapper to generate a link report. Second, I compile the module SWF using that link report to make it as small as possible. Third, I encrypt the module SWF using the keys I've downloaded. Finally, I re-build the wrapper since I'm embedding the encrypted swf inside the app. This isn't absolutely necessary as you could also get the encrypted swf bytes through a URL connection or locally in an AIR app if you wanted to. It just made for a nice clean example for my demo.

 

<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="NitroEncryptedModulesDemo"  basedir="." default="compile.release">

	<property name="OPTIMIZE" value="true"/>
	<property name="DEBUG" value="false"/>
	
    <property name="FLEX_HOME" value="C:/Program Files/Adobe/Flex Builder 3 Plug-in/sdks/3.3.0"/>
	
	<property name="src.dir" value="."/>
	<property name="lib.dir" value="../libs"/>
    <property name="out.dir" value="C:/Program Files/Apache Software Foundation/Apache2.2/htdocs/encryptedflexstore"/>
    <property name="jars.dir" value="../jars"/>
    	
	<taskdef name="nitrolm-encrypt" classname="com.simplifiedlogic.nitrolm.LMEncryptAsset" classpath="${jars.dir}/AssetEncrypterX.jar"/>
	<taskdef resource="flexTasks.tasks" classpath="${FLEX_HOME}/ant/lib/flexTasks.jar"/>
		
	<target name="clean" description="Clean the project">
		<delete dir="${out.dir}" failonerror="false"/>
	</target>
			
	<target name="init" description="initialize the project" depends="clean">
		<mkdir dir="${out.dir}"/>
		<copy todir="${out.dir}" includeemptydirs="false">
			<fileset dir="${src.dir}">
				<exclude name="**/*.as"/>
				<exclude name="**/*.mxml"/>
				<exclude name="**/build.xml"/>
				<exclude name="**/*.properties"/>
				<exclude name="**/*.ser"/>
				<exclude name="**/*.vser"/>
				<include name="**/*"/>
			</fileset>
		</copy>
	</target>
	
	<target name="compile.release" description="Compile wrapper swf pass 1,2,3" depends="init">
		<!-- compile loader SWF pass 1 so we have a link report -->
		<mxmlc file="EncryptedFlexStore.mxml" output="${out.dir}/EncryptedFlexStore.swf"
			debug="${DEBUG}"
			optimize="${OPTIMIZE}"
			link-report="${out.dir}/report.xml"
			locale="en_US">

			<compiler.context-root>/EncryptedFlexStore</compiler.context-root>
			<source-path path-element="${src.dir}"/>
			<library-path dir="${FLEX_HOME}/frameworks/libs" append="true">
				<include name="*.swc"/>
			</library-path>
			<library-path dir="${FLEX_HOME}/frameworks/locale" append="true">
				<include name="{locale}"/>
			</library-path>			
			<library-path dir="${lib.dir}" append="true">
				<include name="FlexNitroLMLiteInterface.swc"/>
			</library-path>			
		</mxmlc>
				
		<!-- compile module SWF to protect including link report.xml -->
		<mxmlc
			file="${src.dir}/flexstore.mxml"
			output="${src.dir}/flexstore_e.swf"
			debug="${DEBUG}"
			optimize="${OPTIMIZE}"
			load-externs="${out.dir}/report.xml"
			locale="en_US">

			<compiler.context-root>/FlexStore</compiler.context-root>
			<source-path path-element="${src.dir}"/>
			<library-path dir="${FLEX_HOME}/frameworks/libs" append="true">
				<include name="*.swc"/>
			</library-path>
			<library-path dir="${FLEX_HOME}/frameworks/locale" append="true">
				<include name="{locale}"/>
			</library-path>			
			<library-path dir="${lib.dir}" append="true">
				<include name="*.swc"/>
			</library-path>			
		</mxmlc>
		
		<!-- delete the link report -->
		<delete file="${out.dir}/report.xml" />
		
		<!-- encrypt the swf to protect -->
		<nitrolm-encrypt
			filename="${src.dir}/flexstore_e.swf"
			product="rQHrpVZjnlpstlfGQ1Pv"
			library="rQHrpVZjnlpstlfGQ1Pv"
			keydir="."/>

		<!-- re-compile pass 2 of the loader SWF so the new encrypted protected swf is embedded correctly -->
		<mxmlc file="EncryptedFlexStore.mxml" output="${out.dir}/EncryptedFlexStore.swf"
			debug="${DEBUG}"
			optimize="${OPTIMIZE}"
			locale="en_US">

			<compiler.context-root>/EncryptedFlexStore</compiler.context-root>
			<source-path path-element="${src.dir}"/>
			<library-path dir="${FLEX_HOME}/frameworks/libs" append="true">
				<include name="*.swc"/>
			</library-path>
			<library-path dir="${FLEX_HOME}/frameworks/locale" append="true">
				<include name="{locale}"/>
			</library-path>			
			<library-path dir="${lib.dir}" append="true">
				<include name="FlexNitroLMLiteInterface.swc"/>
			</library-path>			
		</mxmlc>
		
		<html-wrapper 
		            title="Flex Store"
		            file="index.html"
		            height="100%"
		            width="100%"
		            bgcolor="grey"
		            application="app"
		            swf="EncryptedFlexStore"
		            version-major="9"
		            version-minor="0"
		            version-revision="0"
		            history="true"
		            template="express-installation"
		            output="${out.dir}"/>
	</target>
</project>

Finally, let's take a look at the results of our labor. Everything works as it did before except that all of the code is protected from a decompiler attack.

nitrolm_after.png

Direct Link

So, now that you've been introduced to Nitro-LM "Lite", the next step in the learning process is to browse through the Nitro-LM website, and also the Nitro-LM "Lite" administration app.

After registering for the Nitro-LM "Lite" Admin tool, you'll get access to the example code I've used for this article. It doesn't cost anything to register, or to use the example keys included in the example downloads against your own applications. I'd recommend against using the example keys in a production app because they're publicly available and they get rotated every so often. At $500/product/year, Purchasing keys for Nitro-LM "Lite" is priced similarly to buying an SSL certificate for a website.

nitrolm_support.png

Read more from Andrew Westberg. Andrew Westberg's Atom feed andrewwestberg on Twitter

Comments

3 Comments

Samuel Moshe said:

Focusing on source code as equity is a mistake.
Companies that do this are wasting money and time. Source code is not an asset, and is basically meaningless. In application space, your biggest and only asset is solving a problem worth solving in a way that makes sense for your users to do it. It's the only thing that you will ever have. Even if someone did steal your source code, there's no telling they would be able to do anything with it.

In general though, it's not that hard to steal anything, especially process, which you should be much more concerned about than code. Maybe, the better idea would be to focus on developing something worth using.

Just my two cents.

@Samuel,

You're correct. Your thoughts that source code itself is not an asset are dead on. The techniques, processes, and informational data it may contain, however are important to protect. For example, someone with a financial flex app may not have any real data or IP inside the app itself, but decompiling and studying the app 's operation could have lots of useful information for a hacker trying to learn how to go after the server data directly.

I've created a separate blog post that should explain in more detail why it's important to protect your source code. This takes you step-by-step through cracking an AIR application.

How to Hack an AIR app SWF

Leave a comment


Tag Cloud

Question of the Week: Dream App

If you had an unlimited budget and unlimited resources what application would you build and why would you build it?

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.