Introduction
When backing up the iPhone to your computer iTunes creates multiple files, one of which is all the SMS (text messages) on the phone. Each time a backup is created a new folder is created under C:\users\<username>\AppData\Roaming\Apple Computer\MobileSync\Backup. This folder's name is a string of (what appears to be) random hex digits and will contain multiple files in it. The one that contains the SMS messages will have the file name 3d0d7e5fb2ce288813306e4d4636395e047a3d28 without any extension; this file is the SMS message SQLite database. This article will provide you with the layout of this database and show how to access the messages.
Recommended Software
While first trying to access the database to determine if schema, I tried multiple programs to open the database file. However, for some unknown reason, many programs failed to open the file. The one program that was able to find that could open the database was Database Browser for SQLite and can be downloaded from here.
Also, in order to access the database from .NET code a third-party library is necessary. Again, multiple assemblies where tried without success, but I did find one that worked. This is the dotConnect for SQLite component from Devart. The Standard version is free (although it requires you to enter your email address and first name to download it) and there is a Profession version trial. Both versions (version 5.2) can be downloaded here.
SMS Database
When you open the SMS database file in Database Browser for SQLite you will see a total of nine tables. Below are the nine table names and their purpose (as far as I could determine):
Table Name |
Purpose |
_SqliteDatabaseProperties |
A list of nine properties of the database such as the client version and GUID. |
attachment |
List of SMS message attachment (e.g. pictures), including information such as where the images are backed up to, MIME type, file size, etc. |
chat |
Contains information about chats (e.g. room_name column), phone number information, etc., however I am not fully sure the use for this table. |
chat_handle_join |
Contains only two columns, the chat_id column and the handle_id column, which correspond to the ROWID1 column of the chat table and the ROWID column of the handle table. |
chat_message_join |
Contains only two columns, the chat_id column and the message_id column, which correspond to the ROWID column of the chat table and the ROWID column of the message table. |
handle |
Maps phone numbers (id column) to a unique id (ROWID column). Phone numbers will have a different ROWID for SMS and iMessage (e.g. a phone number 212-555-1049 might have a ROWID of 15 for SMS but a ROWID of 25 for iMessage). |
message |
Contains the actually message information include the text, GUID, service (SMS/iMessage), sent or received, date/time2. |
message_attachment_join |
Contains only two columns, the message_id column and the attachment_id column, which correspond to the ROWID column of the message table and the ROWID column of the message table. |
sqlite_squence |
Contains two columns (name and seq) which maps the text chat, handle, message, and attachment to the values 518, 559, 105705, and 1238 respectively. Not sure of its use but I suspect it is for some internal processing |
1ROWID column, when present in a table, represents the primary key for the table.
2Date/time information is stored in a specific format. The number listed is the number of second since midnight of January 1, 2001 (Mac Epoch).
Accessing SMS Data
In order to access SMS messages (ignoring attachments such as pictures) we only need to concentrate on two tables: the message and handle tables. The message table contains a total of 38 columns, provided below for your reference. Where I was unsure of a specific columns function I noted that and give an explanation based on the column name where I have a reasonable idea of what the columns purpose is. If I have no reasonable idea I simply marked the purpose as "Unknown". Luckily none of these columns are important for just grabbing the most commonly sought after SMS message information (e.g. contents, recipients, date/time, etc.)
Column Name |
Data Type |
Purpose1 |
ROWID |
Integer |
Primary key |
guid |
Text |
Appears to be a GUID assigned to every entry |
text |
Text |
The SMS message contents. |
replace |
Integer |
Unknown. In my test file this column is always 0. |
service_center |
Text |
Unknown. In my test file this column is always null . |
handle_id |
Integer |
Foreign key to the handle table. This is the ID of the person sending/receiving the message (depending on if the SMS message was incoming or outgoing). |
subject |
Text |
Subject of the message (if applicable). |
country |
Text |
Unknown. Based on the name it appears that this column would contain some country information, however in my test file this column is always null . |
attributedBody |
BLOB |
Contains binary data for attributes. When viewing the binary data various attributes can be seen, but the interesting one is NSString which appears to contain the text as in the text column. |
version |
Integer |
Unknown version number. Possibilities included message table or SMS libraries/components. |
type |
Integer |
Unknown. In my test file this column is always set to 0. |
service |
Text |
Either SMS/iMessage, however other possibilities may be possible. |
account |
Text |
If my test file, if the service column is SMS then the account column is e: . However, if the service column is iMessage then the account column is the phone owner's phone number formatted as p:+15551234567 . |
account_guid |
Text |
The GUID for the phone owner's phone number/service combination. |
error |
Integer |
Unknown. Possibly list an error code when an error occurs either sending or receiving SMS messages, however this column is always 0 in my test file. |
date |
Integer |
The date/time the message was created (for outgoing messages) or the date the message was received (for incoming messages), stored as the number of seconds since midnight of January 1, 2001. |
date_read |
Integer |
The date/time an incoming message was read, stored as the number of seconds since midnight of January 1, 2001. |
date_delivered |
Integer |
The date/time an outgoing message was delivered1,stored as the number of seconds since midnight of January 1, 2001. |
is_delivered |
Integer |
1 if True, otherwise 0. |
is_finished |
Integer |
Unknown but I believe this indicates if the message is completed or still a draft; 1 if True, otherwise 0. |
is_emote |
Integer |
Unknown but I believe this indicates if the message contains emotes; 1 if True, otherwise 0. |
is_from_me |
Integer |
Indicates if this is an outgoing message; 1 if True, otherwise 0. If my test file, this column always matches the is_sent column. |
is_empty |
Integer |
Unknown but probably indicates if the message contains any text; 1 if True, otherwise 0. |
is_delayed |
Integer |
Unknown but most likely indicates if the message was delayed while sending (and possibly receiving); 1 if True, otherwise 0. |
is_auto_reply |
Integer |
1 if True, otherwise 0. |
is_prepared |
Integer |
1 if True, otherwise 0. |
is_read |
Integer |
1 if True, otherwise 0. |
is_system_message |
Integer |
1 if True, otherwise 0. |
is_sent |
Integer |
1 if True, otherwise 0. |
has_dd_results |
Integer |
Unknown |
is_service_message |
Integer |
1 if True, otherwise 0. |
is_forward |
Integer |
1 if True, otherwise 0. |
was_downgraded |
Integer |
Unknown |
is_archive |
Integer |
1 if True, otherwise 0. |
cache_has_attachments |
Integer |
Unknown |
cache_roomnames |
Text |
Unknown |
was_data_detected |
Integer |
1 if True, otherwise 0. |
was_deuplicated |
Integer |
1 if True, otherwise 0. |
1I do not know at this time if this is the date/time that the message was delivered to the carrier's network or to the intended recipient, although I strongly believe it is the time to the network.
The handle table is much simpler. It maps the handle_id column in the message table to a specific phone number/service combination. There are only a total of five columns:
Column Name |
Data Type |
Purpose |
ROWID |
Integer |
Primary key |
id |
Text |
Phone number formated as +15551234567 . |
country |
Text |
Appears to indicate the country based on the phone number. In my test file this column is always us . |
service |
Text |
Indicates the service that the message was sent through. In my test file this column is always SMS or iMessage . |
uncanonicalized_id |
Text |
Appears to be the phone number unformatted (some entries are +15551234567 and others are 5551234567 ) or null . |
Prior to Coding
Now that we know the format of the SQLite database and some of the tables, we can begin to access and manipulate the data. If you have not already downloaded the dotConnect for SQLite component, please visit the download page and select choose the Standard version as it is free and should be more than adequate for what we are using it for; for this project I am using Standard Version 5.2. Please note that although the component is free, you will be required to enter your email address and first name before you can begin the download.
The code presented here is for a simple console application that will query the database file for all messages sent, and received, between the phone's owner and a specified telephone number. Since the file is a SQLite database the majority of SQL statement should work for querying data. Since you really should not be changing any data in the database, all queries run against it should be SELECT
statements. If you are not familiar with SQL queries, please visit http://www.tutorialspoint.com/sqlite/sqlite_select_query.htm for an introduction to SQL SELECT
queries. You should also have no trouble finding any additional information you need on SQL statements using Google.
Please note that this console application assumes that the SQLite database file (3d0d7e5fb2ce288813306e4d4636395e047a3d28) is saved in the Debug or Release folder, depending on the mode your code is running in. This is strictly for demonstration and testing purposes. In a production product you would want the ability for the user to search for this file and select which version of the file they want to access, as each time the phone is backed up a new SMS database file is created (with the same name) under a different folder in C:\users\<username>\AppData\Roaming\Apple Computer\MobileSync\Backup. You can use the folder attributes (creation/modified date/time) to find the most recent backup.
Using the Code
Before writing any code, first add a reference to the dotConnect for SQLite component (Devart.Data.dll
and Devart.Data.SQLite.dll
) and import the required namespaces:
Imports System.IO
Imports System.Text.RegularExpressions
Imports Devart.Data.SQLite
Next create a new Message
class and Service
enumeration. The Message
class will help store the information retrieved from the database and perform the conversion of the data/time information. The Service
enumeration helps to identify the service that was used to send/receive the message (either SMS or iMessage):
Public Enum Service
SMS
iMessage
UNKNOWN
End Enum
Public Class Message
Public ReadOnly Epoc As New DateTime(2001, 1, 1, 0, 0, 0, 0)
Private _messageDateRaw As Integer
Private _dateReadRaw As Integer
Public ReadOnly Property Direction As String
Get
If IsFromMe Then
Return "Sent"
Else
Return "Received"
End If
End Get
End Property
Public Property MessageDate As DateTime
Public Property MessageText As String
Public Property DateRead As DateTime
Public Property Service As Service
Public Property IsFromMe As Boolean
Public Sub New(messageText As String, messageDate As Integer, dateRead As Integer, service As String, isFromMe As Boolean)
_messageDateRaw = messageDate
_dateReadRaw = dateRead
Me.MessageText = messageText
Me.MessageDate = Epoc.AddSeconds(_messageDateRaw)
Me.DateRead = Epoc.AddSeconds(_dateReadRaw)
If service.ToLower.Trim = "sms" Then
Me.Service = ConsoleApplication1.Service.SMS
ElseIf service.ToLower.Trim = "imessage" Then
Me.Service = ConsoleApplication1.Service.iMessage
Else
Me.Service = ConsoleApplication1.Service.UNKNOWN
End If
Me.IsFromMe = isFromMe
End Sub
End Class
The main part of this demonstration application is pretty simple; read in a phone number, format the phone number, execute a SELECT query against the SQLite database, and export the formatted results to a text file. Since this is strictly for demonstration purposes, this application will simply find all messages sent between the phone's owner and the entered phone number, however it is heavily commented and should be easy to expand upon since it uses standard SQL. There might be slight differences in queries since this is SQLite, but I have not come across any as of yet.
Sub Main()
Dim phoneNumber As String
Console.Write("Please enter a phone number, including the area code: ")
phoneNumber = Console.ReadLine()
phoneNumber = Regex.Replace(phoneNumber, "[^0-9]", String.Empty)
If phoneNumber.Length = 10 Then
phoneNumber = "+1" & phoneNumber
ElseIf phoneNumber.Length = 11 Then
phoneNumber = "+" & phoneNumber
Else
Console.WriteLine("The phone number entered is not valid.")
Console.ReadKey()
Exit Sub
End If
Dim dbConnection As String = "Data Source=3d0d7e5fb2ce288813306e4d4636395e047a3d28.db"
Dim sqlSelect As String = "SELECT text, message.service, date, date_read, is_from_me FROM message JOIN handle ON message.handle_id = handle.ROWID WHERE handle.id='" & phoneNumber & "' ORDER BY date ASC"
Dim dt As New DataTable
Using cnn As New SQLiteConnection(dbConnection)
cnn.Open()
Dim selectCommand As New SQLiteCommand(sqlSelect, cnn)
Dim reader As SQLiteDataReader = selectCommand.ExecuteReader
dt.Load(reader)
cnn.Close()
End Using
If dt.Rows.Count > 0 Then
Dim msg As Message
Dim messages As New List(Of Message)
For Each row As DataRow In dt.Rows
msg = New Message(row.Item("text").ToString, CInt(row.Item("date")), CInt(row.Item("date_read")), row.Item("service").ToString, CBool(row.Item("is_from_me")))
messages.Add(msg)
Next
Using sw As New StreamWriter(New FileStream("export.txt", FileMode.Create, FileAccess.Write, FileShare.None))
sw.WriteLine("Total Message: " & messages.Count.ToString)
sw.WriteLine(String.Empty)
For Each m As Message In messages
sw.WriteLine("Sent/Received: " & vbTab & m.Direction)
sw.WriteLine("Date: " & vbTab & vbTab & m.MessageDate.ToLongDateString)
sw.WriteLine("Time: " & vbTab & vbTab & m.MessageDate.ToLongTimeString)
sw.WriteLine("Service: " & vbTab & m.Service.ToString)
sw.WriteLine("Message Text")
sw.WriteLine("------------")
sw.WriteLine(m.MessageText)
sw.WriteLine(String.Empty)
sw.WriteLine("=========================")
sw.WriteLine(String.Empty)
Next
sw.Flush()
End Using
Console.WriteLine("Finished exporting SMS messages!")
Console.ReadKey()
Exit Sub
Else
Console.WriteLine("No sent, or received, SMS messages were found for the phone number " & phoneNumber)
Console.ReadKey()
Exit Sub
End If
End Sub
This application does a very simple check of the phone number; it checks to see if the phone number is 10 or 11 digits. If you would like to perform a thorough check of the phone number against the North American Numbering Plan, see my Tip/Trick Telephone Validation Method
Points of Interest
Please note that the zipped source code does not contain an SQLite database file. I hope it is obvious that this file was not included because of privacy concerns, however I hope to create a dummy test file to included with the source code soon. If someone else already has one and would like to provide it to be downloaded with this project for others to test with, please feel free to message me; you will be given the proper credit for your contribution.
History
- October 24, 2014 - Initial Version