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
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:
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!