I tend to overlook this frequently, so as a note to self, it’s possible to store arbitrary passwords or secrets in the macOS keychain, so as to use them from, say, Shell scripts.

From the command line I add an item named myk01 which will contain generic password with

$ security add-generic-password -a jpm -s myk01 -j 'this is for the program xyz' -w
password data for new item:
retype password for new item:

The secret is in the keychain, as this command tells me:

$ security find-generic-password -a jpm -s myk01
keychain: "/Users/jpm/Library/Keychains/login.keychain-db"
version: 512
class: "genp"
attributes:
    0x00000007 <blob>="myk01"
    0x00000008 <blob>=<NULL>
    "acct"<blob>="jpm"
    "cdat"<timedate>=0x32303231303431383039313632375A00  "20210418091627Z\000"
    "crtr"<uint32>=<NULL>
    "cusi"<sint32>=<NULL>
    "desc"<blob>=<NULL>
    "gena"<blob>=<NULL>
    "icmt"<blob>="this is for the program xyz"
    "invi"<sint32>=<NULL>
    "mdat"<timedate>=0x32303231303431383039313632375A00  "20210418091627Z\000"
    "nega"<sint32>=<NULL>
    "prot"<blob>=<NULL>
    "scrp"<sint32>=<NULL>
    "svce"<blob>="myk01"
    "type"<uint32>=<NULL>

macOS keychain

And I can obtain just the password by adding a -w to the command:

$ security find-generic-password -a jpm -s myk01 -w
sekr1t

And that latter format is what I will use to obtain a password for this particular command:

#!/bin/sh

export PASS=$(security find-generic-password -a $(whoami) -s myk01 -w)

/usr/local/bin/program-which-uses-that-variable-for-authentication

Useful on occasion.

Golang

I want to access such a generic password from a Go program, which I do with go-keychain. I test with a “personalized” example from the README:

package main

import (
	"fmt"
	"github.com/keybase/go-keychain"
	"log"
)

func main() {
	q := keychain.NewItem()
	q.SetSecClass(keychain.SecClassGenericPassword)
	q.SetMatchLimit(keychain.MatchLimitOne)
	q.SetReturnData(true)
	q.SetAccount("jpm")
	q.SetService("myk01")

	fmt.Println(q)

	res, err := keychain.QueryItem(q)
	if err != nil {
		log.Fatal(err)
	} else if len(res) != 1 {
		fmt.Println("Not found")
	} else {
		password := string(res[0].Data)
		fmt.Printf("Password is: %T: %v\n", password, password)
	}
}
{map[acct:jpm class:140735646167104 m_Limit:140735646169888 r_Data:true svce:myk01]}
Password is: string: sekr1t

Further reading

  • Roberto points us to a bit more high-level method using envchain, with support for macOS keychain or with D-Bus secret service.

shell and macos :: 18 Apr 2021 :: e-mail