TL;DR: Apple Notes allows users to encrypt note contents at rest and the Apple Cloud Notes Parser now supports parsing of encrypted content.
Background
Apple Notes has allowed users to encrypt their note’s contents at rest in the NoteStore database since iOS 9.3. While some commercial forensics tools can unlock notes, I am unaware of free, open source tools in the community which do so and adding this functionality was a challenge posed to me by Heather Mahalik a few years ago during a SANS FOR585. The foundations which Apple uses to carry out the encryption are well documented standards, but initial attempts at putting them together when the parser was written in Perl failed. The struggles with debugging are what led to completely rewriting the parser into an object-oriented language in late 2019 and early 2020. With the parser rewritten, each of the foundational blocks could be implemented discretely and built into the overall program, leading to decryption of encrypted notes in July of 2020.
This article is a detailed look at what is encrypted, how it is encrypted, and how to decrypt it. If you are just looking at Apple notes for the first time, I would recommend starting with the other entries in the Apple Notes category to understand how it works under normal circumstances before tackling encrypted notes. Throughout this article, I will mainly refer to the ZICCLOUDSYNCINGOBJECT
table when I reference the NoteStore.sqlite database and hence will not be writing that table name every time. I will intentionally include a table name when referencing other tables, such as ZICNOTEDATA.ZDATA
.
Password Recovery
It must be said from the outset that the Apple Cloud Notes Parser is not intended to be a password cracker for Apple Notes. This software is to be used to backup or recover notes which you legally have the password to. With that said, Apple’s documentation says that “if you forgot your password, Apple can’t help you regain access to your locked notes.” While true, if you do not have the password for the encrypted note, but do still have the database, you could use a program like HashCat or John the Ripper to potentially recover it. Both of those programs support password recovery on Apple Notes and Apple File System (APFS) encryption. The rest of this article assumes you either created the note and know the password for the note or have a legal reason for reading the note and have the password.
Face ID and Touch ID can also be used to unlock notes, but these do not change the underlying password. All they do is unlock the password you set up in Settings->Notes->Password and enter it automatically, so the password is still able to be recovered from the database. .
Decrypting with Apple Cloud Notes Parser
While I hope the detailed explanations below allow many others to implement note decryption, Apple Cloud Notes Parser aims to make it easy. To tell the parser which passwords to try, a new argument has been added which expects a file path pointing to a file with one password on each line.
-w, --password-file FILE File with plaintext passwords, one per line.
For example:
What is Encrypted?
When I say that users can encrypt their note’s contents, I need to be careful to be precise. For the most part, what is encrypted is what is found in the ZICNOTEDATA.ZDATA
column in the NoteStore.sqlite database. This leaves most of the note metadata unencrypted, and even some of the content (specifically the ZTITLE1
column) unencrypted. Aside from the ZICNOTEDATA.ZDATA
column, contents of attached files end up encrypted and some of the metadata about objects ends up encypted in the ZENCRYPTEDVALUESJSON
column, such as filenames and URLs. Notice in the below screenshot how, even though the notes are all locked, you can see the day they were created and the title.
According to Apple1, users can encrypt notes containing “images, sketches, tables, maps, and websites.” Each of those functions slightly differently in how they encrypt, but the general rules I’ve seen are:
- If the normal version of the attachment writes to disk, such as an image or a thumbnail of a webpage, that file will contain encrypted data
- If the normal version of the attachment has a user-generated filename, such as an image, that filename will be encrypted and stored in the
ZENCRYPTEDVALUESJSON
column but the file will use theZIDENTIFER
column as its filename - If the normal version of the attachment has a Notes-generated filename, such as a sketch, that filename will remain the same
- If the normal version of the attachment uses the
ZMERGEABLEDATA1
column, such as a table, that blob will be encrypted and stored in theZENCRYPTEDVALUESJSON
column - Files are encrypted on disk using the
ZASSETCRYPTOTAG
andZASSETCRYPTOINITIALIZATIONVECTOR
columns - Fallback images, such as for sketches, are encrypted on disk using the
ZFALLBACKIMAGECRYPTOTAG
andZFALLBACKIMAGECRYPTOINITIALIZATIONVECTOR
columns
How is it Encrypted?
Like much of Apple’s products, specific details on the inner workings of Apple Notes are hard to come by and they use functionality that is not part of the larger libraries Apple makes available to most developers2. The bulk of what is officially known comes from a brief blurb that has been copied over a few different Apple pages over the years1. The blurb describes the underlying foundation of their encryption, but doesn’t get quite far enough for someone unfamiliar with the implementation of these foundations to implement it on their own. That is possibly why this feature shows up in commercial tools and not homegrown ones. Here is the most relevant section of the blurb:
When a user secures a note, a 16-byte key is derived from the user’s passphrase using PBKDF2 and SHA256. The note and all of its attachments are encrypted using AES-GCM. New records are created in Core Data and CloudKit to store the encrypted note, attachments, tag, and initialization vector. After the new records are created, the original unencrypted data is deleted. Attachments that support encryption include images, sketches, tables, maps, and websites. Notes containing other types of attachments can’t be encrypted, and unsupported attachments can’t be added to secure notes.
…
To change the passphrase on a secure note, the user must enter the current passphrase, as Touch ID and Face ID aren’t available when changing the passphrase. After choosing a new passphrase, the Notes app rewraps the keys of all existing notes in the same account that are encrypted by the previous passphrase.
What Changes During Encryption?
During testing of how to decrypt, we wanted to see what exactly happened when a user encrypted a note. I used a MacOS computer to create a note, copied the NoteStore.sqlite database off, then encrypted the note and copied it off again. This is the output of sqldiff’ing the files:
The note I created initially was ZICCLOUDSYNCINGOBJECT.Z_PK=121
and ZICNOTEDATA=41
so you can see how Apple marks the note for deletion and clears out the fields which might have information that should be protected. You can also see the overwriting of ZICNOTEDATA.ZDATA
with a new protobuf and the insertion of a new entry into ZICCLOUDSYNCINGOBJECT
and ZICNOTEDATA
with the encrypted information.
How is it Decrypted?
The above probably is more than sufficient for someone that knows cryptography, but for the layman such as myself, I found I needed a lot of time reading the RFCs and looking at common implementations to understand it. Below I will describe the three basic steps that are taken to decrypt an Apple note and in doing so, how it works. Through this example, I will use a note from my test database with ID 17 (meaning ZICNOTEDATA.ZNOTE=17
and ZICCLOUDSYNCINGOBJECT.Z_PK=17
) as my test data.
Step 1: Derive the Password-Based Key
Apple makes the starting point clear in their description, the use of Password-Based Key Derivation Function 2 (PBKDF2) with the SHA-256 digest algorithm to make a 16-byte key. However, PBKDF2 requires two other parameters to run, the number of iterations and the password salt. Both of those values are found in the ZICCLOUDSYNCINGOBJECT
table, in the obviously named ZCRYPTOITERATIONCOUNT
3 and ZCRYPTOSALT
columns.
This SQLite query would let you would pull the necessary values out of the database to carry out step 1 for all encrypted notes:
PK | Salt (in hex) | Iterations |
---|---|---|
17 | 1165106b6b288bda1e6ecb18e65c7876 | 20000 |
In Ruby, this is what a method might look like4 if you wanted to pass in the user’s password and the crypto_salt and crypto_iterations from the database to derive the key:
Step 2: Unwrap the Encryption Key
In the last part of the quoted text above, Apple says it “rewraps” all of the keys for existing notes when you change the password. This indicates that, similar to the APFS protections, Apple isn’t reencrypting all the data, they simply are rewrapping the key to decrypt the data. There are advantages to this, including being able to “delete” data very quickly by simply deleting the decryption key. One downside is a layman such as myself could easily waste some time trying to “decrypt” the key, instead of “unwrap” it5. Another “downside” (from Apple’s perspective) or advantage (from the perspective of someone who lost their password) is that it is relatively easy to try offline password attacks if you have the database, since you never have to actually decrypt all the data, just unwrap the key.
To unwrap the decryption key, you need an implementation of the AES Key Wrap algorithm. Do not use AES-GCM, even though it is mentioned in the Apple specs as that is not a key wrapping algorithm and will take you down many, wrong, rabbit holes5. Do not be confused with the 24-byte wrapped key in your database when the Apple blurb says there should be a 16-byte key, the AES Key Wrap adds an extra 8-bytes on to the key material during wrapping.
The only things the AES Key Wrap algorithm needs to unwrap a key are the wrapped key and the key-encrypting key (KEK). The wrapped key is stored in the ZICCLOUDSYNCINGOBJECT
table in the obviously named ZCRYPTOWRAPPEDKEY
column and the KEK is what we generated in step 1 using PBKDF2.
This SQLite query would let you would pull the necessary values out of the database to carry out step 2 for all encrypted notes:
PK | Wrapped Key (in hex) |
---|---|
17 | 98c0e56b43b507e60c5465ec5e1bb0c74b756f7d4f4a9bff |
Building on the Ruby example above, this is how one could use the aes_key_wrap
gem to unwrap the wrapped key:
Side Quest 2.5: Get the Right Library
While I wasted a lot of time learning the difference between an AES Key Wrap and AES-GCM in step 2, a very specific Ruby versioning issue burned about a week of my time for step 3. My development box has been around a long time and it still runs Ruby 2.3 and if you read the main page for the OpenSSL gem it clearly says:
NOTE: If you are using Ruby 2.3 (and not Bundler), you must activate the gem version of openssl, otherwise the default gem packaged with the Ruby installation will be used
The layman who glossed over that warning to get into the specifics for the gem would likely pay for that mistake later5. While the first two steps worked fine, step 3 kept generating bad decrypts and throwing an OpenSSL::Cipher::CipherError
because the authentication tag seemed not to be right. My troubleshooting involved all sorts of crazy changes, odd assumptions about maybe Apple using this value instead of that value, and wasting hours of a good friend’s time trying to get the code to work. Finally, I was going down the path of openssl being too old and yet again was googling for specific Ruby and OpenSSL issues when one of the cached pages had the note about Ruby 2.3 on it. As soon as I actually read that warning and added the appropriate line to the AppleDecrypter class to explicitly used the OpenSSL gem instead of the built-in library, things worked immediately.
The most important thing you can look at if you implement this in another language is the IV size. My code was failing because the AES-GCM specifications state an IV of 96-bits should be used, but the IV in Apple Notes is 128-bits. This would not be a problem except the built-in openssl library in Ruby’s standard libraries from Ruy 2.3 appears to only take the first 12-bytes of the IV if you give it a longer value. Whatever library or language you use, make sure you can tell your algorithm that you are giving it a 128-bit IV. Using only part of the IV will, guaranteed, waste a week of your life5.
Step 3: Decrypt the Note
The third and final step of decrypting is to actually decrypt the content. Per Apple’s blurb, the note content is encrypted with AES-GCM. Apple says it stores the encrypted note, the note’s attachments, the tag, and the initialization vector before deleting the original note. Functionally, what that means in the database is that a new entry is created in ZICNOTEDATA
and ZICCLOUDSYNCINGOBJECT
for a new note (as well as for any attachments) and the old ones are “marked for deletion.”6 Even though the row is not immediately deleted (just marked as such for future cleanup), the ZICNOTEDATA.ZDATA
column for the original note is overwritten as well.
In order to decrypt this new value that is stored, you need to have the unwrapped_key that was produced in step 2, the tag which is stored in ZCRYPTOTAG
, and the initialzation vector (IV) which is stored in ZCRYPTOINITIALIZATIONVECTOR
. Both the tag and IV are also stored in the ZICNOTEDATA table, and appear to be consistent in both places.
This SQLite query would let you would pull the necessary values out of the database to carry out step 3 for all encrypted notes:
PK | IV (in hex) | Tag (in hex) |
---|---|---|
17 | 151f64de7be34d15dacdaea9b33471f9 | 806bf2bbd3bf83cf1240b03e7c4d6ab1 |
PK | ZDATA (in hex) |
---|---|
17 | 131b03571fc9ec47ef58e58e21fce5c10aa73a62b9e58a743bcdcc3aff1ea8ab 9964f4535b8597735f3da5f6ae63b9370625a20d633e9cf2986d4d118989124f 0ddfee956e47cb5cbc3617c520b075620b37ae4056f3a1af83351fda634dfb44 6055c75f7143a5600149db333893c0ecb0ef3944e2a64542e9a4375bf1526898 58fed8b21aded0eab0afb11190 |
Continuing on in our basic Ruby example, this is how you could use the openssl
gem to perform the final decryption.
Look at that output! 0x1f 0x8b
, that’s the start of a GZip file! At this point, our normal processing could take over as we would be expecting a GZipped protobuf in the ZICNOTEDATA.ZDATA
column.
Step 4: Repeat
Ok, I lied, our normal processing can’t quite take over yet because as it parses the protobuf it will start to hit encrypted attachments and run into problems. As you hit attachments in an encrypted note, you’ll need to do the same 3 steps above for that object. Each type of attachment, as noted at the top is different in how you have to decryp it. The attachment will have different cryptographic variables from the note, but the same underlying password. Here are brief rundowns of how each encrypted attachment behaves:
How are Attachments Handled?
Tables
Tables are the most straight forward as all they do is take the value that would normally be in ZMERGEABLEDATA1
, base64 encoded it, put it into a JSON object along with the value that used to be in the ZSUMMARY
column, encrypt that JSON, and insert it into the ZENCRYPTEDVALUESJSON
column. The same row will have all of your cryptographic settings and the content.
The right SQL statement to pull all the variables necessary to decrypt this object is:
URLs
URLs are also fairly easy and behave much like tables in that they have the sensitive information in a JSON object in the ZENCRYPTEDVALUESJSON
column. The exception is instead of having mergeable data they have the actual URL with its summary and title.
The right SQL statement to pull all the variables necessary to decrypt this object is:
Images
Images are a bit harder to deal with because of how many rows they go across in the ZICCLOUDSYNCINGOBJECT
table. You’ll have the attachment’s row, then a row for the media, then potentially a lot of rows for thumbnails. While the image data is encrypted within the file on disk, the image’s filename is kept in the ZENCRYPTEDVALUESJSON
column on the media’s row. Importantly, the correct cryptographic settings to decrypt the contents within the file on disk are in the ZASSETCRYPTOTAG
and ZASSETCRYPTOINITIALIZATIONVECTOR
columns for the media row whereas to decrypt the JSON you would continue to use the ZCRYPTOTAG
and ZCRYPTOINITIALIZATIONVECTOR
columns.
On the Image object itself
The right SQL statement to pull all the variables necessary to decrypt this object’s JSON is:
On the Image’s ZMedia row
The right SQL statements to pull all the variables necessary to decrypt the actual file on disk and the JSON is as follows. Note, the image’s filename will be the ZIDENTIFIER
from the second query.
Thumbnails
After discussing images, thumbnails make a lot more sense. They follow basically the same process, except they only have the one row of information, which simplifies things. Their files on disk are also encrypted, but using the normal ZCRYPTOTAG
and ZCRYPTOINITIALIZATIONVECTOR
columns.
The right SQL statement to pull all the variables necessary to decrypt this object’s JSON is:
Sketches
Sketches can be done either like tables parsing a protobuf from ZENCRYPTEDVALUESJSON
, or images decrypting a file on disk from the ZMEDIA
row, or both if you want to get everything. The only big difference is when you are parsing a file from disk using fallback images the tag and IV are in the ZFALLBACKIMAGECRYPTOTAG
and ZFALLBACKIMAGECRYPTOINITIALIZATIONVECTOR
columns.
The right SQL statements to pull all the variables necessary to decrypt the actual file on disk and the JSON are as follows.
Conclusion
Apple’s use of well-documented, public algorithms for its encryption of data at rest in the Notes application allows for straight forward decryption of notes, if the password is known. Even if the password is not known, public tools such as John the Ripper can easily recover the password you need to use your Apple notes. It is my hope that this article is useful for others in implementing the decrypting of Apple notes for your personal use on your own data.
Footnotes
-
Apple Platform Security, Secure features in Notes app ↩ ↩2
-
For example, Apple Notes have been encrypted with AES-GCM since iOS 9, whereas CryptoKit only picked up that functionality in iOS 13. ↩
-
You’ll notice that things which are not encrypted also have
ZCRYPTOITERATIONCOUNT
set, do not use that field to check for encryption, use theZISPASSWORDPROTECTED
field instead. ↩ -
While these examples will work in IRB if you have the required gems installed, they obviously are not fully fleshed out, check for errors, etc. See the AppleDecrypter class in the Apple Cloud Notes Parser for a better implementation to steal for other projects. ↩
-
Note that even though the note is marked for deletion and the
ZICNOTEDATA.ZDATA
column is overwritten, not all artifacts are deleted. In one test example, the thumbnails for a .tiff file which was then encrypted were still present on disk after the note was locked. ↩