Lots of financial institutions and related companies eagerly encourage their customers to opt in to “paperless” programs. These programs allow companies to provide sensitive documents as PDF files rather than sending paper documents in the mail. Since electronic documents are much easier to store than physical paper, I always opt in to paperless programs when given the chance. As a result, I end up downloading and storing many PDFs each month.
If you’re like me, you probably have a special place on your computer where you keep sensitive documents. On my Mac, that place is an encrypted disk image. Disk images need to be mounted in order to access their contents. When mounting an encrypted disk image, OS X will prompt the user for a password.
Every time I downloaded a statement PDF, I needed to mount the disk image–filling a password prompt in the process–and move the PDF into the appropriate folder inside it for safekeeping. That system worked fine, but I’m lazy. Why should I manually have to file PDFs when the computer can do that for me?
I knew outright that automating the filing of PDFs would center around Hazel. If you’re unfamiliar with Hazel, its web site offers this description:
Hazel is a System Preference pane that works silently in the background, automatically filing, organizing and cleaning.
Essentially, Hazel is a rule engine. It continuously monitors folders, and if it notices files inside those folders that fit some chosen criteria, it will do some chosen task(s) with those files. Hazel is a perfect solution for my PDF filing problem: it can be taught to recognize a bank/credit card/electric bill/etc. statement and file it in the appropriate folder.
The problem was that Hazel has no built-in way of mounting disk images. One work-around could have been to leave the disk image mounted all of the time so Hazel could work with it whenever it needed to, but then it would have been silly and pointless to bother with the disk image at all since it basically would have been used like a folder.
Luckily, Hazel can run arbitrary shell scripts and AppleScripts as part of a rule’s actions. This meant that if I wrote a script to mount the disk image, Hazel could run it (thereby mounting the disk image on its own) before attempting to file PDFs.
Mounting an Encrypted Disk Image
hdiutil is a command line utility built in to Mac OS X for working with disk images. To learn more about
hdiutil, you can view its manual page by running
To mount a disk image, you can invoke
$ hdiutil attach <path_to_image_here>
This works equally well for unencrypted full or sparse disk images (images with .dmg or .sparsebundle extensions.) However, this doesn’t work for encrypted disk images, since on its own
hdiutil has no idea what the password should be. According to
hdiutil’s manual page,
hdiutil will attempt to “read a null-terminated passphrase from standard input” when invoked with the
-stdinpass flag, like this (
printf is used to null-terminate the password like
$ printf '<password_here>' | hdiutil attach -stdinpass <path_to_image_here>
Although I was now able to mount the disk image, I wasn’t thrilled about having to hardcode my password. It turned out that I didn’t have to.
Reading Stored Passwords from Keychain
When manually mounting the disk image in Finder, I noticed that the password prompt had an option to “Remember password in my keychain”:
Using that option meant that there would now be a way to read the password from the keychain database, instead of hardcoding the password as shown in the example above.
Once the disk image password is “remembered” in Keychain, the disk image can programmatically be mounted simply by running
hdiutil attach exactly like you would for non-encrypted disk images:
$ hdiutil attach <path_to_image_here>
Running this command with an encrypted disk image will trigger a keychain-related prompt:
The most secure option is to choose “Allow”, which will then automatically use the password stored in your keychain to mount the disk image. Selecting “Always Allow” will prevent the prompt from appearing during subsequent attempts to mount the disk image, but is not recommended since it is inherently much less secure. “Always Allow” allows any program on your system to be able to potentially mount the encrypted disk image and access its data without your knowledge or explicit consent.
As a side note, if you ever want to remove a “remembered” disk image password from the keychain database for some reason:
- Open Keychain.app.
- Select the ‘login’ keychain from the ‘Keychains’ pane on the left.
- Select the ‘Passwords’ category from the ‘Category’ pane on the left.
- Type the word ‘disk’ into the search box. You should see a list of all remembered disk image passwords.
- Right-click on any password you want to delete and pick the ‘Delete’ option from the menu that appears.
- You’ll be prompted to confirm the deletion.
Now, I was able to programmatically mount an encrypted disk image without hardcoding its password.
Getting Hazel to mount the disk image was as simple as getting it to run a script containing the
hdiutil command mentioned above.
I created a script file that looks like this. Since my disk image is named Records.sparsebundle, I named the script
#!/bin/bash hdiutil attach ~/Backup/Records.sparsebundle
The script should have user execute permissions set on it (
chmod u+x script_name.sh). If it doesn’t, Hazel won’t allow you to select the script as part of a rule action.
While Hazel does allow for configuration and use of “embedded” scripts directly inside rules, I chose to use an external script rather than embedded scripts so that the same command wouldn’t be duplicated across multiple rules. Another benefit is that if the command ever has to be updated in the future, it can be changed once in a single location and still affect all rules that depend on it. The external script could also be made generic to accept disk image paths as parameters, which could be provided inside Hazel rules by calling the external script from within an embedded script.
The Puzzle Is Complete
I created one rule in Hazel for each type of document to be filed. Here’s a typical example:
As the last step in the Hazel rule, I chose to have Hazel reveal the affected file in Finder after it is moved so I can view the file and then manually unmount the disk image afterwards, since that’s the workflow I wanted. Alternatively, I could have had Hazel run
hdiutil detach /Volumes/<Image Name> to unmount the disk image after moving the affected file, making the filing process completely automated.
Problem solved. I never have to file a PDF manually again.
Bonus: Just for Kicks
open <path_to_image_here> can also be used to mount encrypted disk images, which is easier to type manually than
hdiutil attach <path_to_image_here>. The main difference between these two ways of mounting a disk image is that
hdiutil will block on the keychain dialog while
open will not, so
hdiutil attach is better for the Hazel use case, since it will cause Hazel to wait for the keychain dialog ‘Allow’ button to be clicked before attempting to do anything else.
Retrieving Disk Image Passwords Manually
It’s possible, though unnecessary and less secure than the technique outlined above, to programmatically read a disk image password from your keychain and use it to mount a disk image.
Don’t do this unless you have a good reason to, and are aware of the implications.
security is another command line utility built in to Mac OS X for working with keychains. To learn more about the
security utility, you can view its manual page by running
To retrieve the remembered password for a disk image from the keychain database,
security can be invoked thusly:
$ security find-generic-password -w -D "disk image password" -l <image_filename_here>
Here’s a breakdown of what each flag means:
-w: For the record found, display the password in plain text. (This flag will most likely only work in OS X 10.8 Mountain Lion since it doesn’t appear in
security’s manual page for OS X 10.7.4. There is also a
-gflag that prints the password along with other information that would have to be stripped out.)
-D "disk image password": Only search through records of type “disk image password”.
-l <image_filename_here>: Only search for the record whose label matches the disk image filename (not the full path.) Although the manual page for
securitydoesn’t actually define what a label is, I was able to determine through experimentation that for disk image keychain records, the label is just the “Name” field for that record in Keychain.app, which is set to the disk image filename by default.
security command can then be combined with the
hdiutil command mentioned earlier in this article to mount an encrypted disk image:
$ printf `security find-generic-password -w -D "disk image password" -l <image_filename_here>` | hdiutil attach -stdinpass <path_to_image_here>
Once again, this method is unnecessary when compared to — and is less secure than — the technique outlined above. It is unnecessary since
open can implicitly read disk image passwords from a keychain on their own, as outlined above. Further, using
security to manually read the password will still prompt to allow access to a keychain item, exactly as would happen using the first technique, so no effort is saved in that aspect. Finally, using
security in this way pipes a password to
hdiutil in plain text, which is inherently less secure. All of that said, I have included this information for completeness, as it could be useful in certain situations.
All of the techniques outlined in this article will work just fine inside a VNC/screen sharing session; you will see an extra prompt for your Mac user password that you wouldn’t see if you were using the computer in person.
All of the techniques outlined in this article will not work when used inside a remote SSH session unless you’ve selected the insecure “Always Allow” option in the keychain security prompt for a given disk image password. I suspect this is a deliberate security-focused behavior that’s meant to ensure that a user is physically present at the machine to click the “Allow” confirmation button when “Always Allow” isn’t selected, instead of attempting to do some kind of typed confirmation over SSH.
Updated on July 17, 2017: Major portions of this article were rewritten to be more straightforward and security-conscious thanks to motivation from this comment.
Updated on January 17, 2017: The
hdiutil commands that appear in this article were updated to correctly use the
-stdinpass flag with null-terminated strings, thanks to this comment.