源码:https://github.com/lingzhao/EncryptedResourceDemo

UPDATE: The example project has been updated to work with iOS5.

If you are distributing an iPhone or iPad app you may be giving away more than you realize. Resources that you embed in your application bundle can be extracted very easily by anyone who has downloaded the app with iTunes or has the app on their iOS device. This is not such a problem if those resources are just parts of your UI or the odd NIB file, but once you realise that your high-value resources, such as embedded PDFs, audio files, proprietary datasets and high-resolution images are just as accessible then you might want to consider how to protect those resources from prying eyes or unauthorised use outside your application.

To see just how accessible your resources are, use a tool such as iPhone Explorer to connect to your device. This tool lets you browse all the app bundles on your device and copy any resource to your Mac or PC. Alternatively, just locate the ipa file that is downloaded when you purchase an app with iTunes, change the extension to ‘.zip’ then unzip it. All your resources are on display.

Protection through encryption

In this article I’m going to walk through one approach to protecting your resources from unauthorised use outside your application. During the build phase, your high-value resources will be encrypted before being embedded in the app bundle. These resources can then be decrypted on-the-fly when required in your app. I had a number of goals in mind when developing the solution presented in this article:

  • Adding new resources to be encrypted should be quick and easy.
  • The encryption process should be completely automatic during the build phase.
  • Encrypted PDFs or HTML files must be useable in an embedded UIWebView
  • Decryption must be in-memory and not create temporary unprotected files

I also did not want to complicate the submission process by introducing encryption that might be subject to export restrictions, so we are limiting ourselves to using the CommonCrypto API, which is already distributed as part of iOS. The usual caveats apply when using any sort of encryption approach – given enough time and determination someone will bypass any protection scheme you can come up with. However, make it difficult enough to crack the protection compared to the value of the protected resources and it becomes a question of economics. If you are working with client-supplied resources then it becomes even more important to use a scheme such as the one described here to protectyourself from charges of negligence should those resources turn up on some torrent site.

An encryption command-line tool

So, down to the details. We’re going to create a simple command-line tool that will be run as part of the build process to encrypt resources using the AES256 symmetric cipher. An Xcode project to build the tool can be found at the bottom of this article. The tool takes command-line arguments specifying a 256-bit key, the input file path and an output file path. We will use a custom build step to call the command-line tool for each of our resources that we want to be encrypted.

Setting up our project

XCode project showing EncryptedResources folder and custom build step

Now we have a tool to perform the encryption, we can turn to an example project that makes use of the encryption to protect embedded resources. You can find an example Xcode project at the end of this article. This sample simply displays a UIWebView when run and populates it with protected HTML and image files, which are embedded as encrypted resources in the app.

The first step is to create a sub-folder under the project folder to contain the original resources that we want to protect. In the example we have called this sub-folder ‘EncryptedResources’. One of our goals was to make adding new resources as quick and easy as possible and when we have finished setting up the project we will be able to add a new protected resource simply by dragging it into this folder within Finder. As a cosmetic convenience I also added the folder to the Xcode project as a folder reference (by checking the ‘Create Folder References for any added folders’ checkbox) but please remember to deselect any target membership on the ‘Targets’ tab of the Info dialog for this folder or the unencrypted resources will be added to the app bundle by the regular ‘Copy Bundle Resources’ build step.

Adding a custom build step

To process the files in the EncryptedResources folder, we add a custom build step by selecting the ‘Project > New Build Phase > New Run Script Build Phase’ menu item in Xcode. In the example we have moved this build step to the start of the project target (under the Targets > EncryptedResourceDemo item in the ‘Groups and Files’ list) so that it is run as the first step in the build process. The shell script associated with this step can be viewed by selecting the ‘File/Get Info’ menu item when the step is selected:

DIRNAME=EncryptedResources
ENC_KEY="abcdefghijklmnopqrstuvwxyz123456"
 
INDIR=$PROJECT_DIR/$DIRNAME
OUTDIR=$TARGET_BUILD_DIR/$CONTENTS_FOLDER_PATH/$DIRNAME
 
if [ ! -d "$OUTDIR" ]; then
mkdir -p "$OUTDIR"
fi
 
for file in "$INDIR"/*
do
echo "Encrypting $file"
"$PROJECT_DIR/crypt" -e -k $ENC_KEY -i "$file" -o "$OUTDIR/`basename "$file"`"
done

The shell script iterates over every file in the ‘EncryptedResources’ folder and calls our ‘crypt’ tool for each file, placing the encrypted output in the application bundle. Note that the script expects the ‘crypt’ tool to be present in the base of the project directory. As an alternative, you could place the ‘crypt’ tool somewhere on your path and modify the script accordingly. Note also, that it is in the build script where we specify the key to use for encryption. The 256-bit key is specified as 32 x 8-bit characters – you should change it from the default given here.

Using protected resources in applications

So now we have a built app bundle containing our protected resources. The resources can still be viewed with tools such as iPhone Explorer or extracted from an iTunes ipa file but are useless without the correct decryption key. We now turn to how these resources can be used legitimately by your app. In creating a decryption framework, I wanted a scheme that would be as flexible as possible and not place an unnecessary burden on the developer every time she wanted to use a protected resource. I also needed a scheme that decrypted in memory and did not create temporary files (which could be viewed with iTunes Explorer while an app was running). I chose to create a custom URL protocol and extend the URL loading system. This allows us to use encrypted resources anywhere that a URL can be specified and, thanks to the widespread use of URLs in the iOS frameworks, we get a lot of bang for our buck with relatively little new code including:

  • Loading encrypted resources into memory with [NSData dataWithContentsOfURL:]
  • Viewing encrypted HTML files and images in UIWebView

Custom URL protocols

If you haven’t come across implementing custom URL protocols before and you are interested in finding out more then check out the iPhone Reference Library. By subclassing NSURLProtocol and implementing a few required methods we can grant the URL loading system the ability to load other types of resources beyond the standard built-in schemes (http:https:ftp: and file:).

Our NSURLProtocol subclass is called EncryptedFileURLProtocol and the implementation can be found under the EncryptedFileURLProtocol group in the example project. It implements a new URL scheme (encrypted-file:) that works like the standardfile: scheme. A URL using this scheme specifies a file on the local system just like file:, however, the files will be decrypted on-the-fly when the resource is loaded.

A custom NSURLProtocol subclass must override the canInitWithRequest:. The URL loading system uses this method to determine which protocol can handle a particular request. The loading system asks each registered protocol in turn whether it can handle the specified request. The first protocol to return YES gets to handle the request. The implementation of our canInitWithRequest: method is shown below and will return YES if the requested URL scheme is ‘encrypted-file:’.

33
34
35
36
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
return ([[[request URL] scheme] isEqualToString:ENCRYPTED_FILE_SCHEME_NAME]);
}

A custom NSURLProtocol must also override two additional methods, startLoading and stopLoading. These are called by the URL loading system at appropriate points when handling a request. Our startLoading method initializes the cryptographic engine and opens an NSInputStream to load the resource. The decryption key is also specified at this point. It is shared between all instances of our custom NSURLProtocol and needs to match the key used for encryption during the build process. The default key value is specified on line 20 of EncryptedFileURLProtocol.m and can be modfied using the class-level key property.

58
59
60
61
62
63
64
65
66
67
68
- (void)startLoading
{
inBuffer = malloc(BUFFER_LENGTH);
outBuffer = malloc(BUFFER_LENGTH);
CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, [sharedKey cStringUsingEncoding:NSISOLatin1StringEncoding], kCCKeySizeAES256, NULL, &cryptoRef);
 
inStream = [[NSInputStream alloc] initWithFileAtPath:[[self.request URL] path]];
[inStream setDelegate:self];
[inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inStream open];
}

The stopLoading method is called when the request ends, either due to normal completion or an error condition. In our implementation we clean up the input stream, cryptographic engine and buffers:

71
72
73
74
75
76
77
78
79
80
- (void)stopLoading
{
[inStream close];
[inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inStream release];
inStream = nil;
CCCryptorRelease(cryptoRef);
free(inBuffer);
free(outBuffer);
}

Because we have scheduled our input stream on the current run loop and specified our custom instance of NSURLProtocol as the stream delegate, we will be called periodically with chunks of data to process. Below is an extract from the stream event handler where a chunk of data read from the input stream is decrypted and passed on to the URL loading system:

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
  switch(streamEvent) {
case NSStreamEventHasBytesAvailable:
{
size_t len = 0;
len = [(NSInputStream *)inStream read:inBuffer maxLength:BUFFER_LENGTH];
if (len)
{
// Decrypt read bytes
if (kCCSuccess != CCCryptorUpdate(cryptoRef, inBuffer, len, outBuffer, BUFFER_LENGTH, &len))
{
[self.client URLProtocol:self didFailWithError:[NSError errorWithDomain:ERROR_DOMAIN code:DECRYPTION_ERROR_CODE userInfo:nil]];
return;
}
 
// Pass decrypted bytes on to URL loading system
NSData *data = [NSData dataWithBytesNoCopy:outBuffer length:len freeWhenDone:NO];
[self.client URLProtocol:self didLoadData:data];
}
break;
}
...

Implementing these four methods is nearly all we need to do to leverage the power of the URL loading system. The final piece of the puzzle is to register our custom NSURLProtocol subclass with the system. This is done in the application:didFinishLaunchingWithOptions: method of our application delegate (in EncryptedResourceDemoAppDelegate.m):

30
31
    // Register the custom URL protocol with the URL loading system
[NSURLProtocol registerClass:[EncryptedFileURLProtocol class]];

Using the encrypted-file: URL scheme

Now that we have implemented our custom NSURLProtocol subclass and registered it with the URL loading system we can start using URLs that use the encrypted-file: scheme. The example application demonstrates one way to do this – that is to use the scheme to load protected resources into a UIWebView instance. This is actually not very different from using a regular file: URL to load and view an embedded HTML resource. The code from the viewDidLoad method of our main view controller is shown below (from EncryptedResourceDemoViewController.m):

21
22
23
24
25
26
27
28
29
30
31
- (void)viewDidLoad {
[super viewDidLoad];
 
webView.scalesPageToFit = YES;
 
NSString *indexPath = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html" inDirectory:@"EncryptedResources"];
 
NSURL *url = [NSURL encryptedFileURLWithPath:indexPath];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView loadRequest:request];
}

The app bundle is queried for the path to ‘index.html’ in the EncryptedResources sub-folder. This is then used to construct an NSURL. An NSURLRequest is created from the URL and a loadRequest: message is sent to the web view. If you are familiar with NSURL and its class methods then you may have spotted the unfamiliar encryptedFileURLWithPath: method. I have extended NSURL using a category to add this method as a convenience. It works just like fileURLWithPath: but creates a URL using the encrypted-file: scheme rather than the regular file: scheme. One cool benefit of extending the URL loading system is that any relative URLs referenced in the HTML, such as the srcparameter of IMG elements, will also use the encrypted-file: protocol and will be decrypted on-the-fly.

As mentioned above, URLs are used in many places in the iOS frameworks. To load an encrypted resource into memory you can do the following:

    NSString *indexPath = [[NSBundle mainBundle] pathForResource:@"..." ofType:@"..." inDirectory:@"EncryptedResources"];
 
NSURL *url = [NSURL encryptedFileURLWithPath:indexPath];
NSData *data = [NSData dataWithContentsOfURL:url];

This could used to create a UIImage from an encrypted image file using [UIImage initWithData:] or you could go one step further by extending UIImage using categories to implement a initWithContentsOfEncryptedFile: method.

Next steps

In this article I have presented a scheme for protecting the resources that you embed in your iPhone and iPad applications. Along the way we have also learned about how to use the CommonCrypto API and how to implement a custom URL protocol. The example project demonstrates how to use the scheme and you are free to use the ‘crypt’ command-line tool and EncryptedFileURLProtocol source in your own projects. Something you might want to think about (depending on the value of the resources you are trying to protect and your level of paranoia) is a mechanism for obfuscating the decryption key. With the current scheme the key is compiled into the application binary as a plain string and could be extracted by anyone with a hex editor, a little patience and a little knowledge. Of course, this assumes that they have worked out that the resources are encrypted using AES256.

We’d love to hear if you have found this article useful or even used it in your own projects.

Protecting resources in iPhone and iPad apps的更多相关文章

  1. Professional iOS Network Programming Connecting the Enterprise to the iPhone and iPad

    Book Description Learn to develop iPhone and iPad applications for networked enterprise environments ...

  2. 在Mac电脑上为iPhone或iPad录屏的方法

    在以前的Mac和iOS版本下,录制iPhone或者iPad屏幕操作是一件稍微复杂的事情.但是随着Yosemite的出现,在Mac电脑上为iPhone或iPad录屏的方法就变得简单了.下面就介绍一下具体 ...

  3. iPhone与iPad在开发上的区别

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

  4. iOS 为iPhone和iPad创建不同的storyboard

    复制Main.storyboard,重命名为Main_iPad.storyboard 在info.plist文件中添加 Main storyboard file base name (iPad) -- ...

  5. css去掉iPhone、iPad默认按钮样式

    原文链接:http://blog.sina.com.cn/s/blog_7d796c0d0102uyd2.html 只要在样式里面加一句去掉css去掉iPhone.iPad的默认按钮样式就可以了!~ ...

  6. CRM2013版本 IOS APP 说明(IPhone、IPad)

    CRM2013版本 IOS APP 说明(IPhone.IPad) IPhone版本 首页 CRM APP在登录时输入账号信息,可以进行首面.其首页显示内容可以在CRM后台设置. 系统默认显示:Pho ...

  7. Iphone和iPad适配, 横竖屏

    竖屏情况下: [UIScreen mainScreen].bounds.size.width = 320 [UIScreen mainScreen].bounds.size.width = 568 横 ...

  8. 【转】越狱的 iPhone、iPad 通过网站实现一键安装 ipa 格式的 APP 应用

    1.已经越狱的 iPhone.iPad 设备,当通过其自带的 safari 浏览器访问 ipa 应用下载网站时,利用 itms-services 协议,可以一键安装 ipa 文件的 iOS 应用,例如 ...

  9. daily news新闻阅读客户端应用源码(兼容iPhone和iPad)

    daily news新闻阅读客户端应用源码(兼容iPhone和iPad),也是一款兼容性较好的应用,可以支iphone和ipad的阅读阅读器源码,设计风格和排列效果很不错,现在做新闻资讯客户端的朋友可 ...

随机推荐

  1. Mariadb 事务

    事务 事务具有ACID特性:原子性(A,atomicity).一致性(C,consistency).隔离性(I,isolation).持久性(D,durabulity). 1.原子性:事务内的所有操作 ...

  2. 软考之J2SE

    特别感谢软考让我如今就接触了神奇的java.曾经尽管真不知道java是个神马,看完马士兵的视频发现里面的东西并不陌生.有vb,c++,c#做基础加上这次的J2SE发现原来编程语言有非常多同样的特性.也 ...

  3. NHibernate之旅(8):巧用组件之依赖对象

    本节内容 引入 方案1:直接加入 方案2:巧用组件 实例分析 结语 引入 通过前面7篇的学习,有点乏味了~~~这篇来学习一个技巧.大家一起想想假设我要在Customer类中实现一个Fullname属性 ...

  4. Broccoli & Babel使用演示样例

    1 创建项目project文件夹:test 2 在test下运行 npm init 按提示填写package.json文件 3 安装broccoli命令行工具broccoli-cli npm inst ...

  5. 远程查看日志-linux

    ssh 连接服务器 ssh user@www.xxx.com -p60022 用户名@ip 端口 进入日志所在目录 cat FILENAME 查看文本文件,P.S. 在查较大文件时为了避免刷屏,请使用 ...

  6. clojure学习记录

    take 从列表中获取子列表 into a b  把b conj 到a中 (defn count-a-seq [lat]  (reduce (fn [x y] (+ x 1)) 0 lat)) red ...

  7. 以太网接口TCP/IP协议介绍,说的很容易懂了

      以太网接口TCP/IP协议介绍,说的很容易懂了  TCP/IP协议,或称为TCP/IP协议栈,或互联网协议系列. TCP/IP协议栈(按TCP/IP参考模型划分) 应用层 FTP SMTP HTT ...

  8. Redis: Redis on Windows Setup

    ylbtech-Redis: Redis on Windows Setup 1.返回顶部 1. 2. 3. 4. 5. 6. 7. 8. 9. 2.返回顶部   3.返回顶部   4.返回顶部   5 ...

  9. gitlab调试

    Bundle complete! 104 Gemfile dependencies, 161 gems now installed.Gems in the groups development, te ...

  10. LVS集群体系和调度算法

    集群体系和调度算法 LVS集群体系架构 1)使用LVS架设的服务器集群系统有三个部分组成: 最前端的负载均衡层,用Load Balancer表示, 中间的服务器群组层,用Server Array表示, ...