Protecting resources in iPhone and iPad apps
源码: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 |
+ (BOOL)canInitWithRequest:(NSURLRequest *)request |
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 |
- (void)startLoading |
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 |
- (void)stopLoading |
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 |
switch(streamEvent) { |
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 |
// Register the custom URL protocol with the URL loading system |
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 |
- (void)viewDidLoad { |
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 src
parameter 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的更多相关文章
- 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 ...
- 在Mac电脑上为iPhone或iPad录屏的方法
在以前的Mac和iOS版本下,录制iPhone或者iPad屏幕操作是一件稍微复杂的事情.但是随着Yosemite的出现,在Mac电脑上为iPhone或iPad录屏的方法就变得简单了.下面就介绍一下具体 ...
- iPhone与iPad在开发上的区别
*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...
- iOS 为iPhone和iPad创建不同的storyboard
复制Main.storyboard,重命名为Main_iPad.storyboard 在info.plist文件中添加 Main storyboard file base name (iPad) -- ...
- css去掉iPhone、iPad默认按钮样式
原文链接:http://blog.sina.com.cn/s/blog_7d796c0d0102uyd2.html 只要在样式里面加一句去掉css去掉iPhone.iPad的默认按钮样式就可以了!~ ...
- CRM2013版本 IOS APP 说明(IPhone、IPad)
CRM2013版本 IOS APP 说明(IPhone.IPad) IPhone版本 首页 CRM APP在登录时输入账号信息,可以进行首面.其首页显示内容可以在CRM后台设置. 系统默认显示:Pho ...
- Iphone和iPad适配, 横竖屏
竖屏情况下: [UIScreen mainScreen].bounds.size.width = 320 [UIScreen mainScreen].bounds.size.width = 568 横 ...
- 【转】越狱的 iPhone、iPad 通过网站实现一键安装 ipa 格式的 APP 应用
1.已经越狱的 iPhone.iPad 设备,当通过其自带的 safari 浏览器访问 ipa 应用下载网站时,利用 itms-services 协议,可以一键安装 ipa 文件的 iOS 应用,例如 ...
- daily news新闻阅读客户端应用源码(兼容iPhone和iPad)
daily news新闻阅读客户端应用源码(兼容iPhone和iPad),也是一款兼容性较好的应用,可以支iphone和ipad的阅读阅读器源码,设计风格和排列效果很不错,现在做新闻资讯客户端的朋友可 ...
随机推荐
- MySQL-数据库创建与删除
创建数据库 在MySQL中,数据库是用于存储和操作诸如表,数据库视图,触发器,存储过程等数据的对象的集合. 要在MySQL中创建数据库,使用CREATE DATABASE语句,如下: CREATE D ...
- UVA10056 - What is the Probability ?(概率)
UVA10056 - What is the Probability ? (概率) 题目链接 题目大意:有n个人玩游戏,一直到一个人胜出之后游戏就能够结束,要不然就一直从第1个到第n个循环进行,没人一 ...
- yispider 开源小说採集器 (来源http://git.oschina.net/yispider/yispider 我的改动版由于他的我无法跑)
我的git地址 http://git.oschina.net/yangdc/yispider 小说採集器 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/ ...
- 【Dairy】2016.10.17-1 OIer最悲剧的事情
OIer最悲剧的事情: 看完题,觉得很可做 然后开始码,码了很久 一测样例,不过.. 开始肉眼查错..手玩样例.. 过了很久 ...
- [noi.ac_D1T2]sort
https://www.zybuluo.com/ysner/note/1289967 题面 定义"翻转排序":每次操作均为把区间\([l,r]\)中所有数倒过来,即\(swap(a ...
- request.getRemoteAddr()和request.getRemoteHost()
转自:https://www.cnblogs.com/aizj/p/7593209.html request.getRemoteAddr()是获得客户端的ip地址.request.getRemoteH ...
- bzoj 1232: [Usaco2008Nov]安慰奶牛cheer【最小生成树】
有趣 每条边在算答案的时候被算了二倍的边权值加上两个端点的权值,然后睡觉点额外加一次 所以可以用这个权做MST,然后加上点权最小的点 #include<iostream> #include ...
- python使用ddt模块对用例执行操作
import time import unittest import ddt from selenium import webdriver TEST_URL = "http://www.ba ...
- 公司6:JrVue重用布局
JrVue是我们基于element重新封装的一套组件库; 为了提高开发效率, 方便对代码的统一管理和维护, 我们提供了一些统一的页面布局, 以及项目常用的中小型模块 我在master新拉一个分支J ...
- Eclipse/STS 在线安装阿里java代码规约插件
1.打开Idea的在线安装插件界面,通过“Help”-->“Install New Software...” 进入 2. 在 “Work with” 栏输入插件包的下载地址:https://p3 ...