在上一篇文章里我们展示了如何使用AS3CryptoAPI来加密和解密数据,在这篇文章里面我们会深入到使用此类库的接口,工厂加密来保护我们SWC库里面的代码,使其不那么容易被破解掉
我们的第一步当然是要创建一个我们要保护的库工程,另外一个工程来保存访问此SWC库的接口和工厂以访问我们的保护代码,super-duper-ultra-mega-secret我们要保护此代码,我创建了CircleCalculator,CircleCalculatorInterface。CircleCalculator应该保存CircleCalculatorInterface接口的引用这样我们就可以访问我们创建的接口的相关实现
CircleCalculatorInterface里创建了一个接口类来定义我们希望我们的终端用户能够调用的方法,我们保护实现部分因为我们不希望我们的用户反编译我们的代码然后来查看我们是如何实现相关的逻辑的。
package com.company
{
public interface ICircleCalculator
{
function calcArea(radius:Number):Number;
function calcCircumference(radius:Number):Number;
}
}
在CircleCalculator里我们的 super-duper-ultra-mega-secre实现了ICircleCalculator接口
package com.company
{
import com.company.ICircleCalculator;
public class CircleCalculator implements ICircleCalculator
{
public function CircleCalculator()
{
}
public function calcArea(radius:Number):Number
{
return Math.PI * radius * radius;
}
public function calcCircumference(radius:Number):Number
{
return 2 * Math.PI * radius;
}
}
}
现在我们有了处于编译状态的CircleCalculator工程,打开此工程的BIN目录然后从CircleCalculator.swc文件中解压出library.swf文件(解压可以使用任何的ZIP解压工具),此文件包含了我们需要加密的实际文件
为了加密这个文件,我创建了一个叫做LibraryEncrypter的AIR应用程序,它使用下拉文件列表导入文件然后使用BLOWFISH来加密文件,密钥存储在开始的加密比特位中,在现实世界中的应用中你肯定希望更加安全,比如将其保存到服务端以获得更好的安全特性
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300" layout="vertical" horizontalAlign="center" verticalAlign="middle"
nativeDragEnter="handleDragEnter(event)"
nativeDragDrop="handleDropSwf(event)"
>
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import com.hurlant.crypto.Crypto;
import com.hurlant.crypto.symmetric.ICipher;
import com.hurlant.crypto.prng.Random;
private function handleDragEnter(event:NativeDragEvent):void
{
NativeDragManager.acceptDragDrop(this);
}
private function handleDropSwf(event:NativeDragEvent):void
{
var filelist:Array = event.clipboard.getData("air:file list") as Array;
for each(var file:File in filelist)
{
//对SWC文件采用简单的blowfish加密
var fs:FileStream = new FileStream();
fs.open(file, FileMode.READ);
var fileBytes:ByteArray = new ByteArray();
fs.readBytes(fileBytes);
fs.close();
//产生随机的键
var key:ByteArray = new ByteArray();
var random:Random = new Random();
random.nextBytes(key, 8);
var aes:ICipher = Crypto.getCipher("blowfish-ecb", key, Crypto.getPad("pkcs5"));
aes.encrypt(fileBytes);
//使用加密的数据流重写文件
fs.open(file, FileMode.WRITE);
fs.writeBytes(key);
fs.writeBytes(fileBytes);
fs.truncate();
fs.close();
fs = null;
Alert.show("Encryption Completed", "Results");
}
}
]]>
</mx:Script>
<mx:Text text="Drop SWF here to encrypt"/>
</mx:WindowedApplication>
既然这已经完成了,接下来就把library.swf拖到该应用完成加密
如果你很好奇,那么你可以将加密的SWF拖进一个web浏览器,右击然后看见“影片未加载“就会出现,另一个方法就是使用解密工具来确保加密的正确性
创建一个加密SWC库的最后一步就是要创建一个工厂类来解释我们加密的SWC文件,你需要确认包含的库文件,包括CircleCalculatorInterface,这样做是为了确保我们的应用程序要能够被FLEX和AIR同时访问
package com.company
{
import com.hurlant.crypto.Crypto;
import com.hurlant.crypto.symmetric.ICipher;
import flash.display.Loader;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;
import flash.utils.ByteArray;
import mx.core.ByteArrayAsset;
public class CircleCalculatorFactory extends EventDispatcher
{
//这是加密后的库文件
[Embed (source="library.swf", mimeType="application/octet-stream")]
private var encryptedSwf:Class;
private var _circleCalculator:ICircleCalculator=null;
public function CircleCalculatorFactory(completeHandler:Function)
{
super();
this.addEventListener(Event.COMPLETE, completeHandler);
//装载包含CircleCalculator 类的文件
var fileData:ByteArrayAsset = ByteArrayAsset(new encryptedSwf());
var key:ByteArray = new ByteArray();
fileData.readBytes(key, 0, 8);
var encryptedBytes:ByteArray = new ByteArray();
fileData.readBytes(encryptedBytes);
//解密library.swf
var aes:ICipher = Crypto.getCipher("blowfish-ecb", key, Crypto.getPad("pkcs5"));
aes.decrypt(encryptedBytes);
//将该SWF装载到目前的域
var ldr:Loader = new Loader();
var ldrContext:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
//对AIR的支持
if(ldrContext.hasOwnProperty("allowLoadBytesCodeExecution"))
ldrContext.allowLoadBytesCodeExecution = true;
ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, loadSwfComplete);
ldr.loadBytes(encryptedBytes, ldrContext);
}
private function loadSwfComplete(event:Event):void
{
var cc:Class = ApplicationDomain.currentDomain.getDefinition("com.company.CircleCalculator") as Class;
_circleCalculator = new cc();
dispatchEvent(new Event(Event.COMPLETE));
}
/**
* @return an object implementing the ICircleCalculator interface
*/
public function getInstance():ICircleCalculator
{
return _circleCalculator;
}
}
}
最后我们创建一个工程来使用我们创建的这个加密库
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:degrafa="com.degrafa.*" xmlns:paint="com.degrafa.paint.*" xmlns:geometry="com.degrafa.geometry.*"
backgroundGradientColors="[#666666, #222222]"
layout="absolute" viewSourceURL="srcview/index.html">
<mx:Script>
<![CDATA[
import com.company.ICircleCalculator;
import com.company.CircleCalculatorFactory;
private var circleCalcFactory:CircleCalculatorFactory = new CircleCalculatorFactory(initComplete);
private var circleCalculator:ICircleCalculator = null;
[Bindable]
private var circumference:Number=0;
[Bindable]
private var area:Number=0;
private function initComplete(event:Event):void
{
circleCalculator = circleCalcFactory.getInstance();
calculateCircle();
}
private function calculateCircle():void
{
circumference = circleCalculator.calcCircumference(radiusSlider.value);
area = circleCalculator.calcArea(radiusSlider.value);
}
]]>
</mx:Script>
<mx:VBox x="20" y="20">
<mx:Label text="Radius:"/>
<mx:HSlider id="radiusSlider" value="100" minimum="10" maximum="400" width="200" change="calculateCircle()" liveDragging="true"/>
<mx:Spacer height="20"/>
<mx:Text text="Circumference: {circumference}"/>
<mx:Text text="Area: {area}"/>
</mx:VBox>
<degrafa:Surface horizontalCenter="0" verticalCenter="0">
<degrafa:fills>
<paint:SolidFill id="blue"
color="#9999ff"/>
</degrafa:fills>
<degrafa:strokes>
<paint:SolidStroke id="white"
color="#FFFFFF"
alpha="1"
weight="2"/>
</degrafa:strokes>
<degrafa:GeometryGroup>
<geometry:Circle fill="{blue}"
stroke="{white}"
radius="{radiusSlider.value}"/>
</degrafa:GeometryGroup>
</degrafa:Surface>
</mx:Application>
In article 1, I showed a trivial example of using the AS3Crypto API for encrypting data. In this article, we’ll go over an example using Interfaces, Factories, and Encryption to protect code in a SWC library from being easily decompiled.
The first order of business is to create a Flex Library project to hold our protected code, and another Flex Library project to hold the Interface and Factory to access it. In this case, our super-duper-ultra-mega-secret code we want to protect is how to calculate the circumference and area of a circle. I’ve created projects called CircleCalculator and CircleCalculatorInterface. CircleCalculator should reference CircleCalculatorInterface so we can implement the interface we’ll create.
In CircleCalculatorInterface, create an Interface class to define what we want our end-user to be able to do. We’re hiding the code because we don’t want them to be able to decompile our code to see how circle areas and circumferences are calculated.
package com.company
{
public interface ICircleCalculator
{
function calcArea(radius:Number):Number;
function calcCircumference(radius:Number):Number;
}
}
Next, in CircleCalculator, create our super-duper-ultra-mega-secret code that implements the ICircleCalculator interface.
package com.company
{
import com.company.ICircleCalculator;
public class CircleCalculator implements ICircleCalculator
{
public function CircleCalculator()
{
}
public function calcArea(radius:Number):Number
{
return Math.PI * radius * radius;
}
public function calcCircumference(radius:Number):Number
{
return 2 * Math.PI * radius;
}
}
}
Now that we have the CircleCalculator project in a state that is compiling, open up the bin folder and extract library.swf from CircleCalculator.swc using the zip tool of your choice. Then copy library.swf to the root folder of CircleCalculatorInterface. Library.swf contains the actual code that we’re going to be encrypting.
In order to encrypt library.swf, I’ve created a tool called LibraryEncrypter as an AIR application. It takes any dropped file and encrypts it using blowfish. The key is stored at the beginning of the encrypted bytes. In a real-world scenario, you’d obviously want to do more obfuscation of the key or store it server-side for increased protection.
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300" layout="vertical" horizontalAlign="center" verticalAlign="middle"
nativeDragEnter="handleDragEnter(event)"
nativeDragDrop="handleDropSwf(event)"
>
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import com.hurlant.crypto.Crypto;
import com.hurlant.crypto.symmetric.ICipher;
import com.hurlant.crypto.prng.Random;
private function handleDragEnter(event:NativeDragEvent):void
{
NativeDragManager.acceptDragDrop(this);
}
private function handleDropSwf(event:NativeDragEvent):void
{
var filelist:Array = event.clipboard.getData("air:file list") as Array;
for each(var file:File in filelist)
{
//do simple blowfish encryption for the swf file
var fs:FileStream = new FileStream();
fs.open(file, FileMode.READ);
var fileBytes:ByteArray = new ByteArray();
fs.readBytes(fileBytes);
fs.close();
//generate a random key
var key:ByteArray = new ByteArray();
var random:Random = new Random();
random.nextBytes(key, 8);
var aes:ICipher = Crypto.getCipher("blowfish-ecb", key, Crypto.getPad("pkcs5"));
aes.encrypt(fileBytes);
//rewrite the file with the encrypted blob
fs.open(file, FileMode.WRITE);
fs.writeBytes(key);
fs.writeBytes(fileBytes);
fs.truncate();
fs.close();
fs = null;
Alert.show("Encryption Completed", "Results");
}
}
]]>
</mx:Script>
<mx:Text text="Drop SWF here to encrypt"/>
</mx:WindowedApplication>
Now that that’s done, launch the app and drop library.swf from CircleCalculatorInterface onto it.
If you’re curious, you can drop the encrypted swf into a web browser, right-click and see that “movie not loaded…” will appear. Another test is to use a decompiler tool to ensure the encryption worked ok.
The final step in the process of creating an encrypted swc library is to create a Factory class in CircleCalculatorInterface that can interpret and understand our encrypted library.swf file. You’ll also need to check the box in CircleCalculatorInterface properties for including the AIR libraries. We do this so that our library can be used by either Flex or AIR when we release it to a client.
package com.company
{
import com.hurlant.crypto.Crypto;
import com.hurlant.crypto.symmetric.ICipher;
import flash.display.Loader;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;
import flash.utils.ByteArray;
import mx.core.ByteArrayAsset;
public class CircleCalculatorFactory extends EventDispatcher
{
//this is the CircleCalculator library.swf file (encrypted with LibraryEncrypter of course)
[Embed (source="library.swf", mimeType="application/octet-stream")]
private var encryptedSwf:Class;
private var _circleCalculator:ICircleCalculator=null;
public function CircleCalculatorFactory(completeHandler:Function)
{
super();
this.addEventListener(Event.COMPLETE, completeHandler);
//load up the swf file that contains the CircleCalculator class
var fileData:ByteArrayAsset = ByteArrayAsset(new encryptedSwf());
var key:ByteArray = new ByteArray();
fileData.readBytes(key, 0, 8);
var encryptedBytes:ByteArray = new ByteArray();
fileData.readBytes(encryptedBytes);
//decrypt library.swf
var aes:ICipher = Crypto.getCipher("blowfish-ecb", key, Crypto.getPad("pkcs5"));
aes.decrypt(encryptedBytes);
//load the swf bytes into the current application domain
var ldr:Loader = new Loader();
var ldrContext:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
//do this for AIR support
if(ldrContext.hasOwnProperty("allowLoadBytesCodeExecution"))
ldrContext.allowLoadBytesCodeExecution = true;
ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, loadSwfComplete);
ldr.loadBytes(encryptedBytes, ldrContext);
}
private function loadSwfComplete(event:Event):void
{
var cc:Class = ApplicationDomain.currentDomain.getDefinition("com.company.CircleCalculator") as Class;
_circleCalculator = new cc();
dispatchEvent(new Event(Event.COMPLETE));
}
/**
* @return an object implementing the ICircleCalculator interface
*/
public function getInstance():ICircleCalculator
{
return _circleCalculator;
}
}
}
Lastly, let’s create an example Flex application that loads and uses our CircleCalculatorInterface library project.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:degrafa="com.degrafa.*" xmlns:paint="com.degrafa.paint.*" xmlns:geometry="com.degrafa.geometry.*"
backgroundGradientColors="[#666666, #222222]"
layout="absolute" viewSourceURL="srcview/index.html">
<mx:Script>
<![CDATA[
import com.company.ICircleCalculator;
import com.company.CircleCalculatorFactory;
private var circleCalcFactory:CircleCalculatorFactory = new CircleCalculatorFactory(initComplete);
private var circleCalculator:ICircleCalculator = null;
[Bindable]
private var circumference:Number=0;
[Bindable]
private var area:Number=0;
private function initComplete(event:Event):void
{
circleCalculator = circleCalcFactory.getInstance();
calculateCircle();
}
private function calculateCircle():void
{
circumference = circleCalculator.calcCircumference(radiusSlider.value);
area = circleCalculator.calcArea(radiusSlider.value);
}
]]>
</mx:Script>
<mx:VBox x="20" y="20">
<mx:Label text="Radius:"/>
<mx:HSlider id="radiusSlider" value="100" minimum="10" maximum="400" width="200" change="calculateCircle()" liveDragging="true"/>
<mx:Spacer height="20"/>
<mx:Text text="Circumference: {circumference}"/>
<mx:Text text="Area: {area}"/>
</mx:VBox>
<degrafa:Surface horizontalCenter="0" verticalCenter="0">
<degrafa:fills>
<paint:SolidFill id="blue"
color="#9999ff"/>
</degrafa:fills>
<degrafa:strokes>
<paint:SolidStroke id="white"
color="#FFFFFF"
alpha="1"
weight="2"/>
</degrafa:strokes>
<degrafa:GeometryGroup>
<geometry:Circle fill="{blue}"
stroke="{white}"
radius="{radiusSlider.value}"/>
</degrafa:GeometryGroup>
</degrafa:Surface>
</mx:Application>