Mac – Creating encrypted disk image passphrase in Keychain, and strange GUID account

encryptionguidkeychaintime-capsuletime-machine

When a user persists an encrypted disk (or disk image) passphrase in the Keychain (e.g via Disk Utility as shown here, https://apple.stackexchange.com/a/229309/5392 or when setting up an encrypted Time Machine backup, possibly on a remote device), an entry gets created in either the login (via Disk Utility) or System (for Time Machine) keychains.

Keychain screenshot

Examining such an entry with security provides the following data:

$ security find-generic-password -D "disk image password" -l "A65D7474-F452-51CB-9BCC-3D594C918978.sparsebundle"
keychain: "/Library/Keychains/System.keychain"
version: 256
class: "genp"
attributes:
    0x00000007 <blob>="A65D7474-F452-51CB-9BCC-3D594C918978.sparsebundle"
    0x00000008 <blob>=<NULL>
    "acct"<blob>="17F24503-E0E2-45B9-86EA-C79D857BA2AC"
    "cdat"<timedate>=0x32303137303131303132333130335A00  "20170110123103Z\000"
    "crtr"<uint32>=<NULL>
    "cusi"<sint32>=<NULL>
    "desc"<blob>="disk image password"
    "gena"<blob>=<NULL>
    "icmt"<blob>=<NULL>
    "invi"<sint32>=<NULL>
    "mdat"<timedate>=0x32303137303131303132333130335A00  "20170110123103Z\000"
    "nega"<sint32>=<NULL>
    "prot"<blob>=<NULL>
    "scrp"<sint32>=<NULL>
    "svce"<blob>="A65D7474-F452-51CB-9BCC-3D594C918978.sparsebundle"
    "type"<uint32>=<NULL>

The account's GUID matches none of the users that can be found via:

dscl /Search -list /Users GeneratedUID

The ultimate goal is to non-interactively create the necessary entry in the Keychain for Time Machine to pick up the password and attach an encrypted, size-limited sparsebundle disk image on a Time Capsule, for which this is the missing piece.

Unfortunately diskimages-helper is not allowed to mount it (observed via Console.app) when using the following command to create the entry:

sudo security add-generic-password -a "$username" -w "${passphrase}" -D "disk image password" -s "${sparsebundle}" -T /System/Library/PrivateFrameworks/DiskImages.framework/Versions/A/Resources/diskimages-helper /Library/Keychains/System.keychain

BTW this is on Sierra.

Additional references:

Best Answer

Getting the UUID:

The account field in the keychain entry corresponds to the UUID of the encrypted disk image which can be retrieved like this:

hdiutil isencrypted example.sparsebundle 2>&1 | grep uuid | cut -f2 -d" "

Note that the hdiutil isencrypted command always prints its output to stderr for some reason which is why we must redirect stderr to stdout (2>&1) before piping it to grep. Alternately, you can use the -plist option and parse the XML if it fits your use case better.


Creating the Keychain item:

For Time Machine to automatically do scheduled backups to a Time Capsule, the disk image password must be stored in the system wide keychain located at /Library/Keychains/System.keychain rather than your password protected user login keychain.

To non-interactively create an entry in the System keychain, you can do:

$ security add-generic-password -D "disk image password" \
-T /System/Library/PrivateFrameworks/DiskImages.framework/Versions/A/Resources/diskimages-helper \
-a `hdiutil isencrypted /path/to/your/TimeMachine.sparsebundle 2>&1 | grep uuid | cut -f2 -d" "` \
-s "TimeMachine.sparsebundle" \
-w $YOUR_PASSPHRASE \
/Library/Keychains/System.keychain

we invoke the uuid command from the previous step in a subshell here to use as the account name.

Security Concerns:

Instead of a password, the System keychain is secured using a key file at /private/var/db/SystemKey. When the system is running, all admin users have permission to access the keychain.

This isn't a security risk per se, but people often forget/overlook when enabling Time Machine that it's for the whole computer and not just their user account. Keep this in mind when choosing your Time Machine password not to use one that's very personal or private to you if you share adminship (and, obviously, make sure you've enabled FileVault full disk encryption to prevent 3rd party access).

Another concern is that while including your password inline as an argument with -w avoids the interactive password prompt, it is also insecure as it will be saved in plaintext to your shell history (if you don't supress it) and in the output of commands like ps. And because you must specify the System keychain path as the last argument, it is very tricky to non-interactively add an item to the system keychain without exposing the password in this way.

But there is an alternate, albeit more involved solution for adding items to the user login keychain that avoids exposing your password:


Using expect to handle interactive password prompts:

Since you can't pipe the password to security directly, you can instead specify -w as the last option and omit the password. This will make it prompt for a password, and then you can use expect to handle the interactive prompts programmatically - the caveat being you cannot specify which keychain to add the item to, so it will be added to the default login keychain.

I'm not sure if this is within the scope of your question, but just in case, here's a quick and dirty expect script that allows you to non-interactively provide a password to the prompt:

#!/usr/bin/env expect -f

# Get passphrase piped through stdin
fconfigure stdin -blocking false
set PASSPHRASE [read stdin]

# get the uuid of the disk image
set IMAGE_PATH [lindex $argv 0]   
spawn bash -c "hdiutil isencrypted $IMAGE_PATH"
expect {
    -re "\[A-F0-9\]{8}(-\[A-F0-9\]{4}){3}-\[A-F0-9\]{12}" { set UUID $expect_out(0,string) }
    -re "hdiutil: isencrypted failed.*" { exit 1 }
    "encrypted: NO" { exit 1 }
}

# add password to keychain
set send_slow {1 .1}
spawn -noecho security add-generic-password -a $UUID -s "[file tail $IMAGE_PATH]" -D "disk image password" -T /System/Library/PrivateFrameworks/DiskImages.framework/Versions/A/Resources/diskimages-helper -w
expect "password data for new item:"
send -s -- "$PASSPHRASE\r"
expect "retype password for new item:"
send -s -- "$PASSPHRASE\r"

expect eof

For example on how to run it, here I save it to a file hdiutilAddToKeychain.exp and pipe it a password from an imaginary function that you can substitute for however you get the password in your workflow:

$ getPassword | ./hdiutilAddToKeychain.exp /path/to/example.sparsebundle

This should create the appropriate item in your login keychain, but the OS may still give you a GUI password prompt to allow access the first time you try to open it. I'm not aware of any way around this first time prompt.

You may also be able to use security's export and import subcommands to copy the password from your login keychain to the System keychain once it's been added this way.