03 Aug / 2010
iPhone dev : Securely Handling UDID
Each iPhone and iPad has a unique device ID (just like MAC address) that
uniquely identifies that device. This UDID as it is called, is
used by many iPhone programs. Some examples could be
- Game Score keeping services : they use your UDID to keep track of
your score & rank - storing some user data on the server : for example a RSS reader
could store your favorite blogs on a server as a backup - used for analytics : Companies like Flurry provide mobile
analytics, just like Google Analytics for web sites. They use
UDID to identify the device
With so many handy uses, lot of client-server iPhone apps use the
UDID. Even though it is not as sensitive as a phone number, I
feel, it should be treated as ‘some what sensitive data’. So lets
see how we can handle the UDID securely.
Transmitting UDID across the Internet securely
Avoid sending UDID over plain HTTP. This enables anybody to
intercept and snoop the package. Instead use HTTPS, also
known as SSL (Secure Socket Layer). So what is involved in switching from http to https?
- Server side : You need to
obtain and install an SSL certificate. Lot of ‘domain’
companies like GoDaddy, Dotster ..etc offer affordable
certificates. They cost around $30 / year.- get a cert
- install following this guide
- check that your certificate is installed properly using this online tool
- Client Side : If setting
up the server-side was a little involved… don’t worry.
Switching to HTTPS on the iPhone is super easy. iPhone
SDK’s network library has built in support for SSL. SO all you have to
do is change your server URL.change the original url :http://mobile.myserver.com
to :
https://mobile.myserver.com
that is it!
Now all the data you are posting from iPhone, including UDID, are
transmitted securely!
Here is a quick objective-C snippet to send UDID and a another sample parameter to the server
// parameters to send NSString *udid = [[UIDevice currentDevice] uniqueIdentifier]; int article_id = 10; // some article id NSString *serverURLStr = @"https://mobile.myserver.com/read_article"; // prepare a GET request NSMutableString *postString = [[NSMutableString alloc] init]; [postString appendFormat:@"%@=%@&", @"udid", udid]; [postString appendFormat:@"%@=%@&", @"article_id", [NSString stringWithFormat:@"%d", article_id]]; NSString *postString2 = [postString stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]; NSString *urlString = [NSString stringWithFormat:@"%@?%@", serverURLStr , postString2]; NSURL *url = [NSURL URLWithString:urlString]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSError *error = nil; NSURLResponse *response = nil; NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; // release.. [postString release]; [urlData release];
Handling UDID at the Server side
So now we got UDID securely from the iPhone app, we need to make sure
we handle it securely on server as well. I will use Ruby on Rails
based web application as our server side application.
Stop LOGGING UDID
Rails logs each requests with parameters. So a sample log look
like:
Processing MobileController#read_article (for 127.0.0.1 at 2010-08-03 11:57:25) [GET] Parameters: {"udid"=>"ABC-123-XYZ", "action"=>"info", "controller"=>"mobile", "article_id" => 23}
Lets disable logging udid. Lets add ‘filter_parameter’ to
our controller.
class MobileController < ActionController::Base filter_parameter_logging :udid #, :another_param
This will filter-out logging UDID. Here is a sample output
Processing MobileController#read_article (for 127.0.0.1 at 2010-08-03 11:57:25) [GET] Parameters: {"udid"=>"[FILTERED]", "action"=>"info", "controller"=>"mobile", "article_id" => 23}
Bingo! See how the UDID is no longer printed out…and UDIDs won’t bleed all over our logs
Securely Storing UDIDs
If we are transmitting UDID, most likely we are storing them in a database for later analysis, data mining ..etc. Since we are transmitting UDID securely, lets also make sure we store them securely.
Here is a sample Ruby on Rails data model migration file
class CreateUserLogs < ActiveRecord::Migration def self.up create_table :user_logs do |t| t.string :udid t.integer :article_id #other fields # t.string :ip_address t.timestamps end # add index for faster lookups add_index :user_logs, :udid add_index :user_logs, :article_id end def self.down drop_table :user_logs end end
Approach 1 : Store plain UDID (Don’t Do this!)
def log(params) userlog = UserLog.new userlog.udid = params[:udid] userlog.article_id = params[:article_id] userlog.save! end
Here we are storing udid as is, in plain text. Lets secure it.
Approach 2 : Secure Hash
A simple way to secure data is ‘one way hashing’ it. The idea is
‘secret’ —– hash function —-> encrypted-secret
encrypting is pretty fast. But guessing the ‘secret’ from ‘encrypted-secret’ is difficult
So lets re-write our method as follows
def log(params) userlog = UserLog.new userlog.udid = hash_string(params[:udid]) userlog.article_id = params[:article_id] userlog.save! end
good! We are calling ‘hash_string’ function (we yet have to write it) to encrypt our UDID. The stored UDID is now encrypted & safe!
Which Hash?
There are a number of encryption hash choices. Wikipedia lists the choices here : http://en.wikipedia.org/wiki/Cryptographic_hash_function
The hash function should take a string and return back a string. The hashed value should be an ‘ascii string’ rather than binary, makes it easier to store it in database.
MD5 has been a popular choice. It hashes the data into a 32-char.
Lets test this, kick off ‘irb’ command line ruby or ‘script/console’ in a rails app
irb >> require 'digest' => true >> Digest::MD5.hexdigest("ABC-123-UDID") => "9f358e9002249e661d8a6275ff0044a4"
We got our hash. There are better hashing algorithms than MD5, namely SHA.
SHA1 / SHA128
>> Digest::SHA1.hexdigest("ABC-123-UDID") => "1f77aba76084eaaf4f86e63f7138d80ba35e2a01"
this gives us 40-char hash.
SHA256 (64-char hash)
>> Digest::SHA256.hexdigest("ABC-123-UDID") => "7ef304bf29632978ad80d0bd79443f7296ffdd9fefacbbdd9e0400cce4cc312e"
SHA386 (96 chars)
>> Digest::SHA384.hexdigest("ABC-123-UDID") => "406b1a42c73d200cfb3912371b1f7223c747939a4f1af7b726ac0d4eb19d2107ae02de17db0ad342e99628a23afd4392"
SHA-512 (128-chars)
>> Digest::SHA512.hexdigest("ABC-123-UDID") => "1009141d52006b9ab3484f335afa476890271cfdb11f6e1ac0a88f33353c942c2e43e697daef69be8a81b41eca90bf029ccdf15f68849ecc66cbd6b3d5037623"
As we use higher-and-higher SHA levels, the encrypted string grows longer (32, 40, 64, 96, 128 chars) making them more and more secure. So pick one that suits your security needs. SHA256 is usually plenty.
So here is our hash function
def hash_string(str) Digest::SHA256.hexdigest(str) end
Lets add a little Salt
Now that we have a pretty good hash function (SHA256) we feel pretty good, right? Well almost. Even though doing reverse-coding from hashed string is ‘hard’, it is a relative term. The technique is called ‘Rainbow Tables’. Read more here:
http://en.wikipedia.org/wiki/Rainbow_table
http://www.codinghorror.com/blog/2007/09/rainbow-hash-cracking.html
So we shouldn’t just plainly encrypt just UDID. To thwart rainbow attacks, we can add a little SALT. SALT is just a random string we come up with, so our encrypted value can’t be looked up in rainbow tables easily. So lets update our hash function
@@salt = "w329*&lkj32xnb" #just any random string def hash_string(str) Digest::SHA256.hexdigest(str + @@salt) end
Now we are home!
DO NOT change the SALT going forward. This will change how UDIDs are encrypted.
So there we go. We have covered how to handle UDID securely from iPhone app and also server.
Further Thoughts
If you are skittish about sending UDID over HTTPS, you can actually encrypt the UDID with one of these algorithms at the iPhone app before sending it to the server. CommonCrypto library can be used for encryption.

1 Comment:
By Michael Patrick Ellard 24 Aug 2010
Great article! Thanks, Sujee!