I have been working on a new ThunderBay SaaS product for months now. And two of the myriad issues I have had to consider is that of user management and licensing and intellectual property protection (IPP). Now many people will argue that one should not protect source code as IP, but rather business processes as IP. When it comes to online Flex products, however, I could not disagree more![1]
Do you know how ridiculously easy it is to reverse-engineer/decompile a Flex 3 application and its AVM byte code?
Try it: simply use SWFDecompiler to open any SWF. Even if that .swf file has been optimized for production release, you can still see the classes, methods, and logic. If you are developing an online Flex solution, then you had better worry about protecting your source code. Why lose your invest of time and effort and let competitors steal your ideas and solutions? Patents and trademarks are not the issue here. Those are ex post facto protection mechanisms. At the very least, using source-code IPP will present an immediate “barrier-to-entry” to your competitors.
Now, do not misunderstand the intent of this blog. I am a huge proponent of mentoring, open-source, and the community-driven evolution of software. Google and blog entries have been radical saviors to many technical roadblocks and bugs that I have encountered. In return, I reciprocate by blogging solutions and contributing to open-source (Cairngorm Extensions/CGX, Mate Localizations, Flex Behaviour Injection, etc.) I do not, however, open source ALL my software or solutions.
As a consultant or developer working on a disruptive solution you would be have to be an idiot to not protect your core IP and your core source.
So how do you protect your source code? Really only two (2) viable solutions exist for Flash/Flex solutions: code obfuscation or encryption. Source or byte code obfuscation works well for some purposes; but may present real problems with non-trivial Flex architectures. Encryption, however, is more secure and also provides opportunities for you to track customer usage and authorize access.
You can "grow-your-own" encryption mechanism and embed the decryption key in your code. Or you can use a client-cloud model to request your decryption key from a cloud-based service. Nitro-LM is a hosted, commercial technology platform that provides user management, product licenses, and software protection (encryption) for Java, .NET, Adobe Flex/AIR, C/C++ software. Nitro-LM administration, user authorization, and key creation/access is all managed online as a hosted software-as-a-service (SaaS). And while the license services are offered as an annual subscription, the yearly cost to provide RSA encryption for my software product IPP is trivial. Granted, if you have a free product, then a subscription-based licensing model may not work for you. If you have a free product, I would contend that you probably do not have a viable revenue model in the first place… so why protect it?
Anyone who has ever tried to write user management or user license modules recalls that those features and the associated issues of maintenance & availability quickly become a "full-time job". The broad suite of features and functionality offered by Nitro-LM is amazing and the support is excellent (shout out to Andrew Westberg). So sign-up for a webinar and check it out for yourself; and no I do NOT get any compensation for my promotion.
For the purpose of this blog, I will be presenting a solution for IP source-code protection using Nitro-LM and "encryption of Flex RSLs". Nitro-LM already publishes solutions for encrypting your Flex application or a Flex "module". But nothing was available for encrypting a Flex RSL (aka shared library). So I created that solution, released it as open-source to SimplifiedLogic, and now present it to you. Shown below is a live demo of a Flex app using a custom preloader [display] and dynamic RSL decryption:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | < ?xml version="1.0" encoding="utf-8"?> <mx:application layout="absolute" minWidth="1024" minHeight="768" backgroundColor="#777777" backgroundGradientColors="[#777777,#BABABA]" preloader="tb.preloader.ThunderPreloader" creationComplete="onAppReady();" xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:adobe="http://www.adobe.com/2006/cairngorm"> <!-- Note the ThunderBay loading indicator is shown because of the preloader reference to ThunderPreloader; which is NOT a preloader but a subclass of DownloadProgressBar --> <mx:script> < ![CDATA[ import mx.controls.Alert; import tb.utils.DelayedCall; private function onAppReady():void { DelayedCall.schedule( showStatus,[],600); function showStatus():void { var msg : String = 'Finished: CGX encrypted RSL was decrypted & loaded using Nitro-LM!'; mx.controls.Alert.show(msg,'Encrypted RSLs Loaded'); } } ]]> </mx:script> <!-- A class from the encrypted RSL CairngormExtensions.swf --> <adobe:frontcontroller id="myController" /> </mx:application> |
This demo loads an encrytped CaignormExtension RSL. Download this encrypted RSL directly and try to decompile it! Now download and decompile the standard version that is NOT encrypted. The latter version you can get access to class code and logic. The former is completely locked and unreadable. I love this feature; encryption is awesome!
Flex RSLs act as shared libraries (similar to DLLs on Windows), require only a single download [to the browser/flashplayer] and can be shared among multiple Flex applications. These RSLs can be optimized for small footprint sizes to achieve faster download times. And best of all, you can update an RSL with patches, upgrades, etc… without requiring new downloads of any other RSLs. This power comes with a small price to start-up time and complexities to builds and source-code organization. For a SaaS solutions, especially, I think RSLs are critical requirements. For deeper learning, check out
To understand the process of RSL encryption/decryption, you must first be familiar with how RSLs are loaded. First, do you need to know an interesting secret about a Flex application! A Flex application is constructed as a 2-frame movie. The first frame of a Flex application movie contains on the very critical classes including the SystemManager, the Preloader, the DownloadProgressBar and some glue helper classes.[2] The second frame of a Flex SWF contains the rest of the Flex framework code, your application code and all of your application assets like embedded fonts, images, etc. This means it takes a lot longer to get all of the frame 2 downloaded than the small stuff in frame 1.
Which means that the Flash player can access content on downloaded frames without having to wait for subsequent frames/the entire file to download. By creating a 2-frame movie, Flex applications can take advantage of the streaming support built into the Flash Player and a preloader can appear before all of the Flex framework code and your application code are downloaded. The visible benefit of this whole idea is the loading bar. Even on a large app, Flex quickly shows that it is loading (Frame1 downloaded) and gives continual feedback to the user while the rest of the app downloads (Frame 2).
So here are the steps involved in the startup of a Flex application:
A well-known technique for displaying your own startup splash screen is to subclass the DownloadProgressBar control [or implement the IPreloaderDisplay interface]. Your custom display class can be used during the startup and that class can listen for 6 types of events:
The last two are the interesting part… with those you can listen to events for RSL loading and errors. And, it is during the preloader event dispatching phase that you will manage/decrypt 1 or more encrypted RSLs. Here are the steps involved:
So let’s look at the AS3 code for the custom display class that also supports RSL decryption:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | package tb.preloader { import flash.display.Sprite; import flash.events.Event; import flash.events.ProgressEvent; import mx.core.RSLItem; import mx.core.RSLListLoader; import mx.core.mx_internal; import mx.events.RSLEvent; import mx.preloaders.Preloader; import tb.preloader.nitrolm.NitroDecryptor; use namespace mx_internal; public class ThunderPreloader extends SplashDisplay { /** * Constructor for a Preloader subclass that supports on-demand RSL decryption * */ public function ThunderPreloader() { super(); } /** * The code in the preloader setter below uses listeners to intercept RSLErrors and * handles the loading and decryptiong of encrypted RSLs. * * Note: The parent class is responsibile for the * PROGRESS, COMPLETE, INIT_PROGRESS, INIT_COMPLETE events and handlers * * @param value IPreloaderDisplay subclasses used as interface to startup processes; that have been initiated by SystemManager. */ override public function set preloader(value:Sprite):void { super.preloader = value; _preloader = (value as Preloader); _preloader.addEventListener(RSLEvent.RSL_PROGRESS, onRSLDownloadProgress ); _preloader.addEventListener(RSLEvent.RSL_ERROR, onRSLEncryptionDetected, false, 999); } // ************************************************************** // RSL Event Handlers // ************************************************************** /** * Called during initial RSLEvent.RSL_ERROR; in our case * this is expected because the RSL is encrypted and cannot be properly loaded. * Kill the event propogation and invoke the NitroLM decryptor to reload a decrypted version * of the RSL. * * @param event Initial RSLEvent.RSL_ERROR * */ private function onRSLEncryptionDetected(event:RSLEvent):void { // Could be encrypted so reload it and check. event.stopImmediatePropagation(); _decryptor.loadAndDecrypt(event.url, onRSLDecryptedAndLoaded);} /** * Method invoked when the encrypted RSL has been decrypted and loaded into the AVM. * This causes the next RSL to start loading (if any available). * @param event * */ private function onRSLDecryptedAndLoaded(event:Event):void { // We must manually force the Preloader to continue load all subsequent RSLs // that are in the queue (after the current RSL has processed/loaded). Preloader(_preloader).mx_internal::resume(); } /** * Show the progress of the current RSL download relative to the total percentage * Not relevant to * @param event RSLEvent to update progress of load each RSL * */ private function onRSLDownloadProgress(event:RSLEvent):void { // Now that are tracking on RSL loads, remove generic listener... _preloader.removeEventListener(ProgressEvent.PROGRESS, onDownload_Progress); centerOnScreen(); progressBar.progress = calculatePercentDone(event); if (progressBar.visible && !isReady) progressBar.visible = false; } // ************************************************************** // Private Attributes // ************************************************************** private function calculatePercentDone(event:RSLEvent):Number { var amountPerRSL : Number = DOWNLOAD_PERCENTAGE / event.rslTotal; var alreadyLoaded : Number = amountPerRSL * event.rslIndex; var currentLoading : Number = amountPerRSL * (event.bytesLoaded / event.bytesTotal); return alreadyLoaded + currentLoading; } /** * Decryptor facade class that hides complexities of asynchronous decryption * of encrtyped RSL. * * @private */ private var _decryptor : NitroDecryptor = new NitroDecryptor(); // ************************************************************** // Monkey-Patch Attributes (to force linker to load the patched classes) // ************************************************************** private var _preloader : Preloader; private var _unused4 : RSLListLoader; } } |
Because the Flex 3 & 4 mx framework never planned for the use of encrypted RSLs, any RSLError event immediately stops the current and all other RSL load processes. In our situation, however, we do not consider an "encryption error" to be a real error… so we have to "fix" or monkey-patch the Flex codebase to support new "resume()" functionality.
Don’t worry, I have provided all that "patch" code in source code for this blog! In order to employ RSL encryption within your own Flex application(s), you will need:
Below is a sample ANT script that you can use to encrypt a target Flex library using a Nitro-LM RSA private key:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | < ?xml version="1.0" encoding="utf-8"?> <project name="Deploy EncryptedRSL Demo" basedir="." default="encrypt.compiled.swc" > <property name="library.name" value="CairngormExtensions" /> <property name="flexsdk.dir" value="/Users/thomasburleson/Documents/dev/flexSDK" /> <property name="bin.dir" value="/Users/thomasburleson/Documents/dev/frameworks/CGX/build" /> <property name="deploy.dir" value="/Users/thomasburleson/Documents/dev/samples/nitroLM/Flex4_encryptedRSL/bin-debug" /> <property name="build.dev" value="/Users/thomasburleson/Documents/dev/samples/nitroLM/Flex4_encryptedRSL/build" /> <property name="tools.dir" value="${build.dev}/tools/build" /> <target name="encrypt.compiled.swc" depends=""> <encryptswc rsl-dir="${bin.dir}/rsl" swc-dir="${bin.dir}" swc-name="${library.name}" deploy-dir="${deploy.dir}" /> <delete dir="${bin.dir}/rsl" quiet="true"/> <echo message="Finished build and encryption of: ${deploy.dir}/${library.name}.swf "/> </target> <!-- Ant Definitions and Macros --> <taskdef name="nitrolm-encrypt" classname="com.simplifiedlogic.nitrolm.LMEncryptAsset" classpath="${tools.dir}/jars/AssetEncrypterX.jar"/> <macrodef name="encryptSWC"> <attribute name="rsl-dir"/> <attribute name="swc-dir"/> <attribute name="swc-name"/> <attribute name="version-stamp" default="" /> <attribute name="deploy-dir" default="${deploy.dir}" /> <attribute name="encrypt-keys-dir" default="${tools.dir}/nitro_lm/keys" /> <sequential> <!-- Process 1) Extract the SWF from the SWC (RSL that should be encrypted) 2) Optimize the RSL (remove debug info and compress) 3) Encrypt the SWF/RSL with Nitro-LM tools and keys 4) Deploy the encrypted RSL 5) Update the target SWC digest with the encrypted RSL checksum - The "parent" app that loads the RSL uses the SWC/updated digest value to - insure that the RSL is the "expected" RSL and not a pirated one! --> <unzip src="@{swc-dir}/@{swc-name}.swc" dest="@{rsl-dir}"> <patternset> <include name="library.swf" /> </patternset> </unzip> <java jar="${flexsdk.dir}/lib/optimizer.jar" fork="true" failonerror="true"> <jvmarg line="-ea -DAS3 -DAVMPLUS -Dflexlib=${flexsdk.dir}/frameworks -Xms32m -Xmx384m -Dsun.io.useCanonCaches=false"/> <arg line="'@{rsl-dir}/library.swf' --output '@{rsl-dir}/library.swf' --keep-as3-metadata='Bindable,Managed,ChangeEvent,NonCommittingChangeEvent,Transient' "/> </java> <!-- encrypt the swf to protect Here, I am using my private key downloaded from my Nitro-LM account @ https://license.nitromation.com/NitroAdminLite/index.html --> <nitrolm -encrypt filename="@{rsl-dir}/library.swf" product="6LMLulqf9VN9AmbFzKky" library="6LMLulqf9VN9AmbFzKky" keydir="@{encrypt-keys-dir}" /> <delete file="@{deploy-dir}/@{swc-name}.swf" /> <copy file="@{rsl-dir}/library.swf" tofile="@{deploy-dir}/@{swc-name}@{version-stamp}.swf" /> <!-- Update the digest value in the .swc so linking applications will use the updated digest value Do not worry about the library.swf inside the SWC being the original version... --> <java jar="${flexsdk.dir}/lib/digest.jar" fork="true" failonerror="true"> <jvmarg line="-ea -DAS3 -DAVMPLUS -Dflexlib=${flexsdk.dir}/frameworks -Xms32m -Xmx384m -Dsun.io.useCanonCaches=false"/> <arg line="--digest.rsl-file @{rsl-dir}/library.swf --digest.swc-path @{swc-dir}/@{swc-name}.swc"/> </java> </sequential> </macrodef> </project> |
Click here to download the FlashBuilder 4 Demo project (and ant build script) used for the demo above. You can even use the Custom Preloader display solutions to rapidly create your own.
So what is next? Well, if you have non-trivial flex applications you may want to deliver some of the following support:
If you need help with these, contact me. My professional life is as an RIA Flex/Flash consultant, entrepreneur, developer, architect, and mentor. So give me a shout out at Thomas dot Burleson at ThunderbaySoft dot com.
This has to rate as one of the most useful Flex blog posts I’ve ever read, excellent work and thanks. Did Adobe make any changes in Flex 4 to smooth the decryption process, or are the same patch techniques still valid?
Nitro-LM protection can be easily compromised; see Defeating Nitro-LM
Excellent article Tom a very good read. Thanks for the clear code solutions also!
Cheers,
Simon
Tom, have you submitted the monkey patches in JIRA yet? Please make sure we’re actually aware of the work
Matt,
Thanks for the reminder about JIRA. I created bug #SDK-24076 with upload/attached code for the monkey patches to mx.core.*.
Thanks again.
Wow Tom. You really took RSL encryption to the next level.
Andrew,
The great thing about this solution is that is works [without any changes] for encrypted or standard RSLs. So even in development mode, it works auto-magically.
Now to get Adobe to incorporate the “monkey patches” into Flex 4.