源码: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. 安装eclipse中html/jsp/xml editor插件以及改动html页面的字体

    近期在做android项目,用到了jquery mobile 框架以及phonegap,所以就会涉及一些html文件,可是html文件打开的方式是Text Editor ,而且打开之后一些html代码 ...

  2. ≪统计学习精要(The Elements of Statistical Learning)≫课堂笔记(三)

    照例文章第一段跑题,先附上个段子(转载的哦~): I hate CS people. They don't know linear algebra but want to teach projecti ...

  3. C Language Study - gets , getchar & scanf

    慢慢的发现C语言功底是如此的薄弱,被这几个字符输入函数搞糊涂了又~~ 来,再来忧伤一次吧~ 那么.我们从scanf開始: 假如说你要将一串字符输入到一字符数组里,例如以下面程序, char a[2]; ...

  4. 杂项-Java:Ehcache

    ylbtech-杂项-Java:Ehcache EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认的CacheProvider. 1.返回顶部 1. 基 ...

  5. Python 之reduce()函数

    reduce()函数: reduce()函数也是Python内置的一个高阶函数.reduce()函数接收的参数和 map()类似,一个函数 f,一个list,但行为和 map()不同,reduce() ...

  6. html表格合并单元格

    th标签 合并列 colspan="k" 合并行 rowspan="k"   例子<th colspan="2", rowspan=& ...

  7. 10.19NOIP模拟赛(DAY2)

    /* 正解O(n)尺取法orz 我写的二分答案.本来以为会被卡成暴力分...... 这个-'0'-48是我写的吗........我怎么不记得... */ #include<bits\stdc++ ...

  8. mybatis时间查询小技巧

     网上大多数使用mybatis查询的时候都是把时间转换成Date使用的,其实这里时可以直接使用String的,比如 <if test="startTime != null and st ...

  9. 测试神器Swagger的相关使用

    1.Swagger简介 swagger官网地址: https://swagger.io/ swagger官网文档介绍地址: https://swagger.io/about/ ​ swagge是一个易 ...

  10. Akka源码分析-Akka-Streams-GraphStage

    上一篇博客中我们介绍了ActorMaterializer的一小部分源码,其实分析的还是非常简单的,只是初窥了Materializer最基本的初始化过程及其涉及的基本概念.我们知道在materializ ...