访问备份的 iPhone 短信






4.56/5 (8投票s)
当 iPhone 短信通过 iTunes 备份到您的电脑时,短信会被存储在一个 SQLite 数据库中。本文将展示该数据库文件的布局以及如何访问所有已保存的短信信息。
引言
在将 iPhone 备份到电脑时,iTunes 会创建多个文件,其中一个文件包含手机上的所有短信(文本消息)。每次创建备份时,都会在 C:\users\<username>\AppData\Roaming\Apple Computer\MobileSync\Backup 下创建一个新文件夹。此文件夹的名称是一串(似乎是)随机的十六进制数字,其中包含多个文件。包含短信的文件名为 3d0d7e5fb2ce288813306e4d4636395e047a3d28,没有扩展名;此文件就是短信 SQLite 数据库。本文将提供该数据库的布局,并说明如何访问短信。
推荐软件
在尝试访问数据库以确定其架构时,我尝试了多种程序来打开数据库文件。然而,出于某种未知原因,许多程序都无法打开该文件。我找到一个能够打开该数据库的程序是 Database Browser for SQLite,可以从 此处下载。
此外,为了通过 .NET 代码访问数据库,还需要一个第三方库。同样,我尝试了多个程序集但均未成功,但我确实找到了一个可用的。这是 Devart 的 dotConnect for SQLite 组件。标准版是免费的(尽管需要您输入电子邮件地址和名字才能下载),并且有专业版的试用版。这两个版本(版本 5.2)都可以在 此处下载。
短信数据库
当您在 Database Browser for SQLite 中打开短信数据库文件时,您会看到总共九个表。以下是这九个表的名称及其用途(根据我的推测):
表名 | 目的 |
_SqliteDatabaseProperties | 列出了数据库的九个属性,例如客户端版本和 GUID。 |
attachment | 列出了短信附件(例如图片),包括图片备份位置、MIME 类型、文件大小等信息。 |
chat | 包含有关聊天(例如 room_name 列)、电话号码信息等内容,但我不太确定此表的确切用途。 |
chat_handle_join | 仅包含两列:chat_id 列和 handle_id 列,分别对应 chat 表的 ROWID1 列和 handle 表的 ROWID 列。 |
chat_message_join | 仅包含两列:chat_id 列和 message_id 列,分别对应 chat 表的 ROWID 列和 message 表的 ROWID 列。 |
handle | 将电话号码(id 列)映射到一个唯一的 ID(ROWID 列)。短信和 iMessage 的电话号码会有不同的 ROWID(例如,电话号码 212-555-1049 可能对于短信是 ROWID 15,而对于 iMessage 是 ROWID 25)。 |
message | 包含实际的消息信息,包括文本、GUID、服务(SMS/iMessage)、发送或接收、日期/时间2。 |
message_attachment_join | 仅包含两列:message_id 列和 attachment_id 列,分别对应 message 表的 ROWID 列和 message 表的 ROWID 列。 |
sqlite_squence | 包含两列(name 和 seq),将文本 chat、handle、message 和 attachment 分别映射到值 518、559、105705 和 1238。不确定其用途,但 I 怀疑它用于某些内部处理。 |
1当 ROWID 列存在于表中时,它代表该表的主键。
2日期/时间信息以特定格式存储。数字表示自 2001 年 1 月 1 日午夜(Mac Epoch)以来的秒数。
访问短信数据
为了访问短信(忽略附件,如图片),我们只需要关注两个表:message 表和 handle 表。message 表总共包含 38 列,下表供您参考。对于我不太确定的特定列的功能,我已注明,并根据列名给出我能合理推测的用途解释。如果我完全不确定,我会简单地将用途标记为“未知”。幸运的是,这些列中没有一列对于获取最常查找的短信信息(例如内容、收件人、日期/时间等)很重要。
列名 | 数据类型 | 用途1 |
ROWID | 整数 | 主键 |
guid | 文本 | 似乎是分配给每个条目的 GUID。 |
文本 | 文本 | 短信内容。 |
更换 | 整数 | 未知。在我的测试文件中,此列始终为 0。 |
service_center | 文本 | 未知。在我的测试文件中,此列始终为 null 。 |
handle_id | 整数 | 指向 handle 表的外键。这是发送/接收消息的人的 ID(取决于短信是收件还是发件)。 |
subject | 文本 | 消息的主题(如果适用)。 |
country | 文本 | 未知。根据名称,此列似乎应包含某些国家信息,但在我的测试文件中,此列始终为 null 。 |
attributedBody | BLOB | 包含属性的二进制数据。查看二进制数据时可以看到各种属性,但有趣的是 NSString ,它似乎包含 text 列中的文本。 |
version | 整数 | 未知版本号。可能的包括 message 表或短信库/组件。 |
type | 整数 | 未知。在我的测试文件中,此列始终设置为 0。 |
service | 文本 | SMS/iMessage,但可能存在其他可能性。 |
account | 文本 | 在我的测试文件中,如果 service 列为 SMS ,则 account 列为 e: 。但是,如果 service 列为 iMessage ,则 account 列为电话所有者的电话号码,格式为 p:+15551234567 。 |
account_guid | 文本 | 电话所有者电话号码/服务组合的 GUID。 |
error | 整数 | 未知。可能列出发送或接收短信时发生的任何错误的错误代码,但在我的测试文件中此列始终为 0。 |
date | 整数 | 消息创建日期/时间(对于发件),或收到消息的日期(对于收件),存储为自 2001 年 1 月 1 日午夜以来的秒数。 |
date_read | 整数 | 收到消息后的阅读日期/时间,存储为自 2001 年 1 月 1 日午夜以来的秒数。 |
date_delivered | 整数 | 发件已送达的日期/时间1,存储为自 2001 年 1 月 1 日午夜以来的秒数。 |
is_delivered | 整数 | 1 表示 True,否则为 0。 |
is_finished | 整数 | 未知,但我认为这表示消息是否已完成或仍是草稿;1 表示 True,否则为 0。 |
is_emote | 整数 | 未知,但我认为这表示消息是否包含表情符号;1 表示 True,否则为 0。 |
is_from_me | 整数 | 表示是否为发件消息;1 表示 True,否则为 0。在我的测试文件中,此列始终与 is_sent 列匹配。 |
is_empty | 整数 | 未知,但可能表示消息是否包含任何文本;1 表示 True,否则为 0。 |
is_delayed | 整数 | 未知,但最有可能表示消息在发送(可能也是接收)时是否延迟;1 表示 True,否则为 0。 |
is_auto_reply | 整数 | 1 表示 True,否则为 0。 |
is_prepared | 整数 | 1 表示 True,否则为 0。 |
is_read | 整数 | 1 表示 True,否则为 0。 |
is_system_message | 整数 | 1 表示 True,否则为 0。 |
is_sent | 整数 | 1 表示 True,否则为 0。 |
has_dd_results | 整数 | 未知 |
is_service_message | 整数 | 1 表示 True,否则为 0。 |
is_forward | 整数 | 1 表示 True,否则为 0。 |
was_downgraded | 整数 | 未知 |
is_archive | 整数 | 1 表示 True,否则为 0。 |
cache_has_attachments | 整数 | 未知 |
cache_roomnames | 文本 | 未知 |
was_data_detected | 整数 | 1 表示 True,否则为 0。 |
was_deuplicated | 整数 | 1 表示 True,否则为 0。 |
1目前我不知道这是消息发送到运营商网络或最终接收者的日期/时间,尽管我强烈认为它是发送到网络的时间。
handle 表要简单得多。它将 message 表中的 handle_id 列映射到一个特定的电话号码/服务组合。总共只有五列。
列名 | 数据类型 | 目的 |
ROWID | 整数 | 主键 |
id | 文本 | 电话号码格式为 +15551234567 。 |
country | 文本 | 似乎是根据电话号码指示国家。在我的测试文件中,此列始终为 us 。 |
service | 文本 | 指示消息发送所用的服务。在我的测试文件中,此列始终为 SMS 或 iMessage 。 |
uncanonicalized_id | 文本 | 似乎是未格式化的电话号码(有些条目是 +15551234567 ,有些是 5551234567 )或 null 。 |
编码之前
现在我们知道了 SQLite 数据库的格式和部分表,就可以开始访问和处理数据了。如果您还没有下载 dotConnect for SQLite 组件,请访问 下载页面,并选择标准版,因为它免费且足以满足我们的需求;在此项目中,我使用的是标准版 5.2。请注意,尽管该组件是免费的,但您在开始下载之前需要输入您的电子邮件地址和名字。
此处提供的代码是一个简单的控制台应用程序,它将查询数据库文件,获取所有发送和接收的、与指定电话号码之间的消息。由于这是一个 SQLite 数据库,大多数 SQL 语句都适用于查询数据。由于您实际上不应该更改数据库中的任何数据,因此所有针对它的查询都应该是 SELECT
语句。如果您不熟悉 SQL 查询,请访问 https://tutorialspoint.org.cn/sqlite/sqlite_select_query.htm 了解 SQL SELECT
查询的入门知识。您应该也能轻松地使用 Google 查找关于 SQL 语句的任何其他所需信息。
请注意,此控制台应用程序假定 SQLite 数据库文件(3d0d7e5fb2ce288813306e4d4636395e047a3d28)保存在 Debug 或 Release 文件夹中,具体取决于代码运行的模式。这严格是为了演示和测试目的。在生产产品中,您将需要让用户能够搜索此文件并选择他们想要访问的文件版本,因为每次备份手机时,都会在 C:\users\<username>\AppData\Roaming\Apple Computer\MobileSync\Backup 下的另一个文件夹中创建一个新的 SMS 数据库文件(名称相同)。您可以使用文件夹属性(创建/修改日期/时间)来查找最新的备份。
Using the Code
在编写任何代码之前,首先添加对 dotConnect for SQLite 组件(Devart.Data.dll
和 Devart.Data.SQLite.dll
)的引用,并导入所需的命名空间。
Imports System.IO
Imports System.Text.RegularExpressions
Imports Devart.Data.SQLite
接下来,创建一个新的 Message
类和一个 Service
枚举。Message
类将有助于存储从数据库检索的信息,并执行日期/时间信息的转换。Service
枚举有助于识别用于发送/接收消息的服务(SMS 或 iMessage)。
Public Enum Service
SMS
iMessage
UNKNOWN
End Enum
Public Class Message
'All date/time values in the database are saved as the number of seconds since Midnight of January 1, 2001.
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
'Convert the saved date/time value in the database to a DateTime object
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
这个演示应用程序的主要部分非常简单:读取一个电话号码,格式化电话号码,对 SQLite 数据库执行 SELECT 查询,并将格式化的结果导出到文本文件。由于这纯粹是为了演示目的,此应用程序将仅查找所有在电话所有者和输入的电话号码之间发送的消息,但它有详细的注释,并且由于使用了标准的 SQL,应该很容易扩展。由于这是 SQLite,查询可能会略有不同,但我尚未遇到任何问题。
Sub Main()
Dim phoneNumber As String
Console.Write("Please enter a phone number, including the area code: ")
'Read phone number enter by user
phoneNumber = Console.ReadLine()
'Remove all non-numeric characters
phoneNumber = Regex.Replace(phoneNumber, "[^0-9]", String.Empty)
If phoneNumber.Length = 10 Then
'Phone number contains the area code, without the leading 1
phoneNumber = "+1" & phoneNumber
ElseIf phoneNumber.Length = 11 Then
'Phone number contains the area code with the leading 1
phoneNumber = "+" & phoneNumber
Else
Console.WriteLine("The phone number entered is not valid.")
Console.ReadKey()
Exit Sub
End If
'The name of the SMS backup file. This assumes the file is in the same folder at the executing assembly.
Dim dbConnection As String = "Data Source=3d0d7e5fb2ce288813306e4d4636395e047a3d28.db"
'Create the SQL SELECT statement to join the message and handle table based on the provided phone number, sorted by ascending date
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
'Create a new SQLite connection
Using cnn As New SQLiteConnection(dbConnection)
'Open connection to the database
cnn.Open()
'Create the select command
Dim selectCommand As New SQLiteCommand(sqlSelect, cnn)
'Execute the SELECT command
Dim reader As SQLiteDataReader = selectCommand.ExecuteReader
'Load results into a DataTable
dt.Load(reader)
'Close the connection to the database
cnn.Close()
End Using
'Check if any results were returned
If dt.Rows.Count > 0 Then
'At least one result was found. Create a new StreamWriter to write the results to a file.
Dim msg As Message
'Create a new list to hold Message objects
Dim messages As New List(Of Message)
'Loop through all the results
For Each row As DataRow In dt.Rows
'Create a new Message object
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")))
'Add new Message object to the list
messages.Add(msg)
Next
'Create a new StreamWriter to export the results to a file
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)
'Loop through all the Messages in the list and write the information to the stream
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
'Flush any remaining data to the file
sw.Flush()
End Using
'Inform user that the export is finished
Console.WriteLine("Finished exporting SMS messages!")
Console.ReadKey()
Exit Sub
Else
'No results were found
Console.WriteLine("No sent, or received, SMS messages were found for the phone number " & phoneNumber)
Console.ReadKey()
Exit Sub
End If
End Sub
此应用程序对电话号码进行非常简单的检查;它会检查电话号码是否为 10 或 11 位数字。如果您想根据北美电话号码计划对电话号码进行全面检查,请参阅我的技巧/窍门 电话验证方法。
关注点
请注意,压缩的源代码不包含 SQLite 数据库文件。我希望很明显,由于隐私原因未包含此文件,但我希望很快创建一个虚拟测试文件与源代码一起提供。如果有人已经拥有一个并愿意提供该文件与此项目一起下载供其他人测试,请随时给我留言;您将获得应有的贡献者荣誉。
历史
- 2014 年 10 月 24 日 - 初始版本