Exploring Secure Notes in the Mac OS X Keychain
So I was exploring the Mac OS X Keychain API and looking into the available methods, however I noticed that there was no way to access a Secure Note that you can create in the Keychain Access.app
application.
A Secure Note, like its name implies is just a secure piece of text that is stored into your keychain. You create one by going to Keychain Access.app
, File -> New Secure Note Item, and then fill out the fields.
If you double click a Secure Note, you view its properties, like so:
But looking at the Keychain Services API again, there is nothing that immediately stands out to retrieving the content of a Secure Note. The 'easy to use APIs' are limited to SecKeychainFindGenericPassword()
and SecKeychainFindInternetPassword()
. Upon searching, I came across the security
command line tool provided by Apple. Again, looking at the available options there is nothing relating to Secure Notes at all.
More searching, and it turns out that Secure Notes are implemented as just a "Generic Password" in the keychain, and its just Keychain Access.app
that handles the creation and viewing of the Secure Notes. So how exactly do you get them out? You can use the security
tool, with the find-generic-password
feature, but you need to provide some specifics. Keychain entries have a notion of a 'creator', which is actually a 4 character string, which makes sense as that is how Mac used to identify what program would open a file.
The four character code for Secure Notes is, 'note', unsuprisingly. We also need to know the "Service String", which is used to identify what this generic password is for, and this happens to be the title of the Secure Note. This is also why you cannot have two secure notes with the same title, as the Service String is how the keychain distinguishes different Secure Notes from each other.
So, running the command security find-generic-password -C note -s "Testing Note"
, you get some output displaying metadata about the generic password (the Secure Note) that was the result of the search result.
keychain: "/Users/USERNAME/Library/Keychains/login.keychain"
class: "genp"
attributes:
0x00000007 <blob>="Testing Note"
0x00000008 <blob>=<NULL>
"acct"<blob>=<NULL>
"cdat"<timedate>=0x32303134313231323137333130395A00 "20141212173109Z\000"
"crtr"<uint32>=<NULL>
"cusi"<sint32>=<NULL>
"desc"<blob>="secure note"
"gena"<blob>=<NULL>
"icmt"<blob>=<NULL>
"invi"<sint32>=<NULL>
"mdat"<timedate>=0x32303134313231323137333130395A00 "20141212173109Z\000"
"nega"<sint32>=<NULL>
"prot"<blob>=<NULL>
"scrp"<sint32>=<NULL>
"svce"<blob>="Testing Note"
"type"<uint32>="note"
You notice that you don't see the "password" content, or the text of your Secure Note in this output. For that, we either need to pass the -g
option to output the password bytes (in base64, and its attempt at decoding it), or just pass -w
, which just outputs the password bytes only in base64. So running security find-generic-password -C note -s "Testing Note" -w
will just output:
3c3f786d 6c207665 7273696f 6e3d2231 2e302220 656e636f
64696e67 3d225554 462d3822 3f3e0a3c 21444f43 54595045
20706c69 73742050 55424c49 4320222d 2f2f4170 706c652f
2f445444 20504c49 53542031 2e302f2f 454e2220 22687474
703a2f2f 7777772e 6170706c 652e636f 6d2f4454 44732f50
726f7065 7274794c 6973742d 312e302e 64746422 3e0a3c70
6c697374 20766572 73696f6e 3d22312e 30223e0a 3c646963
....... (and so on)
Writing a quick python3 script to decode the base64 encoding, and loading the result as XML as noticed in the -g
output that it is a property list:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #!/usr/bin/env python3
import xml.etree.ElementTree as ET
import plistlib, pprint, binascii
# not full hex string for brevity!
hex_data = '''3c3f786d6c2076657273696f6e3d22312e3022206....'''
# decode hex into bytes
xml_bytes = binascii.unhexlify(hex_data)
# create ElementTree object since its an XML PList
ET.fromstring(xml_bytes)
# print out xml
print(ET.tostring(xml_bytes))
# or you can load it straight into a python object using plistlib
plist_dict = plistlib.loads(xml_bytes)
pprint.pprint(plist_dict)
|
Running this against the output returns:
<plist version="1.0">
<dict>
<key>NOTE</key>
<string>12345
abcdefghijklmnopqrstuvwxyz
HELLO WORLD
=)
</string>
<key>RTFD</key>
<data>
cnRmZAAAAAADAAAAAgAAAAcAAABUWFQucnRmAQAAAC43AQAAKwAAAAEAAAAvAQAAe1xy
dGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxMzQzXGNvY29hc3VicnRmMTYwCntc
Zm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEhlbHZldGljYTt9CntcY29sb3J0Ymw7
XHJlZDI1NVxncmVlbjI1NVxibHVlMjU1O30KXHBhcmRcdHg1NjBcdHgxMTIwXHR4MTY4
MFx0eDIyNDBcdHgyODAwXHR4MzM2MFx0eDM5MjBcdHg0NDgwXHR4NTA0MFx0eDU2MDBc
dHg2MTYwXHR4NjcyMFxwYXJkaXJuYXR1cmFsCgpcZjBcZnMyNCBcY2YwIDEyMzQ1XAph
YmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5elwKSEVMTE8gV09STERcCj0pXAp9AQAAACMA
AAABAAAABwAAAFRYVC5ydGYQAAAAXSaLVLYBAAAAAAAAAAAAAA==
</data>
</dict>
</plist>
As you can see, we have the content of the note in the <string>
tag, but we also have this other key, "RTFD". Its again
encoded in base64, so decoding it, we see that it starts with:
b'rtfd\x00\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\x07\x00\x00\x00TXT.rtf\x01 ......
So is this a .rtf file? Saving it as a .rtf or .rtfd doesn't work, but then upon searching, I found that RTFDs are a slightly different format of rich text files, and ones that are saved from TextEdit for example, are Mac OS X bundles! (As in they are secretly a folder that the OS hides from you) It seems that Keychain Access.app
creates a RTF file for the note text, but you can't really serialize a bundle to bytes, as its a folder with files inside. More searching, and I found the Apple Type Code list, and there is "com.apple.rtfd", but also "com.apple.flat-rtfd", which it says is a "pasteboard" format!
The Pasteboard can be thought of as Mac OS X's 'clipboard', or mechanis of trasnferring data between applications, drag and drop, and more. It seems that Keychain Access.app
specifically creates this pasteboard content because of a feature that Keychain Access.app
has, the ability to copy the secure note to the clipboard.
So that is how Keychain Access.app
stores Secure Notes in the keychain. I thought this was an interesting little reverse engineering experiment. Hope this is useful to someone!