Pregunta

The documentation for the Keychain Services API leaves a bit to be desired. One thing that I can't seem to locate are details on accessing the Secure Notes that the Keychain Access app lets you add and edit.

Any insight would be much appreciated. Thanks.

¿Fue útil?

Solución 2

I figured out that you can pull the data using the security command line tool. Secure notes are stored as generic passwords with the following characteristics:

class: "genp" - this is the same as a generic password

type<uint32>="note" - you can use this to identify secure notes specifically when searching (using the -C flag).

desc<blob>="secure note" - I don't know that you can search based on this field but it definitely identifies the item as a secure note

0x00000007 <blob>= "Note name" - I don't know if you can get this via the API but you can definitely get it from the command line tool

acct<blob>=<NULL> - This seems to be a common characteristic of secure notes

Use the command security dump-keychain to find all kinds of useful info about the keychain items.

Otros consejos

Just to expand on the accepted answer:

Indeed "secure notes" are stored as just specially formatted generic passwords. Thus, if you create a secure note, you can get programmatic access to it using the Keychain Services API: SecKeychainFindGenericPassword() or the security command line tool.

As an example of using security, if you have a secure note named "Testing Note":

showing the note in keychain access

You will need to search for "secure notes" and for the title of the note, "Testing Note". The "type" (or desc field) will be "note", and the "service" (or svce field, the name of the keychain entry) will be the actual title of the note. It seems that for every field you specify, it has to be exact, so searching for "Testing *" or "Testing" will not turn up any results for our note.

So you can use this command to search for notes with the type "secure note" and title "Testing Note":

security find-generic-password -C note -s "Testing Note"

And you get as a 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"

To get the password to output as well, you will need to also pass the -g option to the security command, and unless you have explicitly set security as a trusted/allowed program to access that keychain item, it will ask you if you want allow access to a keychain item:

dialog asking if you want to allow access to keychain item

Looking at just the password output (you can use the -w option to only output the "password", or the text of our note, however you don't get the 'decoded' output, just the hex), you get:

security find-generic-password -C note -s "Testing Note" -w

(formatted for clarity)

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)

Not very useful! If we use some python code to decode it: (or any language of your choice)

#!/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)

Now we are getting somewhere! The result of decoding it is:

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

So we obviously have the plaintext password as the value to the key "NOTE" (as this is how plists store dictionaries), but what is the "RTFD" key? Looking at it in binary gives the impression that its some sort of rtfd file:

b'rtfd\x00\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\x07\x00\x00\x00TXT.rtf\x01 ......

But saving it as a .rtfd doesn't work, but then I realized that RTFDs saved from TextEdit for example, are bundles! So how does that work... you can't really serialize a bundle to bytes, as its a folder with files inside, but then upon more searching, (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!

So I used a sample application from Apple that shows detailed information about the clipboard/pasteboard. Then you can right click in Keychain access, "copy secure note":

copy secure note in keychain access

And then if you look at the bytes in ClipboardViewer, you see it matches the un-hexed bytes in the tag in the plist.

Whew! That was a lot longer then I expected.... So in short, a Secure Note is just a generic password, with a title, and the password part being an Apple XML Plist, with the plaintext data, and the data in a pasteboard format suitable for copying to the clipboard with "Copy Secure note to clipboard".

I hope this clears up how Secure Notes are stored, as there is indeed a lack of API functions that access secure notes, and nothing in the official Keychain access API.

You can grab the value of a keychain secure note using a long chain of commands from the macOS terminal. The snippet below gets the value of a note named "foobar" and saves it to a file called foobar.txt on the user's desktop.

security find-generic-password -C note -s 'foobar' -w | xxd -r -p | 
 xmllint --xpath "//dict/data/text()" - | base64 --decode | 
 textutil -stdin -convert txt -output ~/Desktop/foobar.txt

i had a need to do this recently and found this question. it put me on the right track, but i used the following:

$h = security find-generic-password -a AAAAA -s BBBBB -w
$k = -join (($h | sls '(..)' -a).matches.value | %{[char][int]"0x$_" })
$k

very terse, but it is doing select-string (sls) for all (-a) regex match groups with any 2 characters ('(..)'), then we just get the value of the matches themselves which will be an array total and pipe it to a loop that will convert the hex to a char value by using a little 0x shortcut, and finally we join it all together. since we are just squishing it together, can move -join to the beginning instead of doing -join '' at the end.

i'm not generally so terse, but this is such a kind of edge case for my needs, i would rather it was short. i just wanted to store a large value as a note on my mac and use it instead of an environment variable. my needs are more throwaway, but in a prod environment it would likely just be an env var or 'get from a vault' type of situation so this is just due to fiddling around on my mac.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top