Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Signing your Git Commits using GPG on MacOS Sierra/High Sierra

Methods of Signing with GPG

Last updated March 28, 2021

There are now two ways to approach this:

  1. Using gpg and generating keys
  2. Using Kryptonite by krypt.co

This Gist explains how to do this using gpg in a step-by-step fashion. Kryptonite is actually wickedly easy to use-but you will still need to follow the instructions

For using a GUI-based GIT tool such as Tower or Github Desktop, follow the steps here for signing with either GPG or Krypt.co.

Using GPG

Step 1: Install software

We use the Homebrew package manager for this step.

brew install gpg2 gnupg pinentry-mac       

Step 2: Create the .gnupg Directory

If this directory does not exist, create it.

# Make the directory
mkdir ~/.gnupg

# The below configuration used to be added within that directory but is no longer required and breaks on M1-based Macs
# echo 'pinentry-program $(brew --prefix)/bin/pinentry-mac' > ~/.gnupg/gpg-agent.conf

Step 3: Update or Create ~/.gnupg/gpg.conf

If this file does not exist, create it.

# This tells gpg to use the gpg-agent
echo 'use-agent' > ~/.gnupg/gpg.conf

Step 4: Modify your Shell

Append the following to your ~/.bash_profile or ~/.bashrc or ~/.zshrc

...
export GPG_TTY=$(tty)

Step 5: Restart your Terminal or source your ~/.*rc file

# on the built-in bash on macos use
source ~/.bash_profile
# if using bash through homebrew over ssh use
source ~/.bashrc
# and if using zsh
source ~/.zshrc

Step 6: Update the Permissions on your ~/.gnupg Directory

You will need to modify the permissions to 700 to secure this directory.

chmod 700 ~/.gnupg

Step 7: Create your GPG Key

Run the following command to generate your key, note we have to use the --expert flag so as to generate a 4096-bit key.

gpg --full-gen-key

Step 8: Answer the Questions

Once you have entered your options, pinentry will prompt you for a password for the new PGP key.

Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
Your selection? 4
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 3y
Key does not expire at all
Is this correct? (y/N) y

You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
    "Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"

Real name: Dr Duh
Email address: doc@duh.to
Comment:
You selected this USER-ID:
    "Dr Duh <doc@duh.to>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
You need a Passphrase to protect your secret key.

Step 9: Get your key info for Git, etc.

# List your keys
gpg -k

Step 10: Get your key id

Use the next command to generate a short form of the key fingerprint.

Copy the text after the rsa4096/ and before the date generated and use the copied id in step 13:

gpg -K --keyid-format SHORT
sec rsa4096/######## YYYY-MM-DD [SC] [expires: YYYY-MM-DD]

*You need to copy the output from your terminal similar to the example above where the ######## is following the slash. *

Step 11: Export the fingerprint

In the output from step 10, the line below the row that says 'pub' shows a fingerprint-this is what you use in the placeholder. The output from below is what you copy to Github:

# The export command below gives you the key you add to GitHub
gpg --armor --export <your key id>

Step 12: Configure Git to use gpg

git config --global gpg.program $(which gpg)

Step 13: Configure Git to use your signing key

The below command needs the fingerprint from step 10 above:

git config --global user.signingkey 1111111

Step 14: Configure Git to sign all commits (Optional-you can configure this per repository too)

This tells Git to sign all commits using the key you specified in step 13.

git config --global commit.gpgsign true

Step 15: Perform a Commit

git commit -S -s -m "My Signed Commit"

Step 16: Pinentry Prompt

You will now be prompted by Pinentry for the password for your signing key. You can enter it into the Dialog box-with the option of saving the password to the macOS X Keychain.

Step 17: Submit your PGP key to Github to verify your Commits

Login into Github.com and go to your settings, SSH and GPG Keys, and add your GPG key from the page.

Step 18: Submitting Your Key to a Public Keyserver (very optional)

Before you jump on submitting your key to a service such as the MIT PGP Key Server, you should consider the following:

  • You cannot delete your key once submitted
  • Spammers have been known to harvest email addresses from these servers
  • If you're only signing your Git commits to Github this isn't necessary

Troubleshooting

If you have any errors when generating a key regarding gpg-agent, try the following command to see what error it generates:

gpg-agent --daemon

Git Signing with a GUI Application (e.g. Git Tower or GitHub Desktop)

Manually Installed GPG

Step 1: Modify ~/.gnupg/gpg-agent.conf

use-standard-socket
# Below option is deprecated
# pinentry-program $(brew --prefix)/bin/pinentry-mac
enable-ssh-support

Step 2: Modify ~/.gnupg/gpg.conf

use-agent
no-tty

Step 3: Restart GPG Agent

gpgconf --reload gpg-agent

Step 4: Copy startup-gpg-agent.sh to ~/bin/

Copy the .sh file in this gist to ~/bin/.

Step 5: Copy org.gnupg.gpg-agent.plist file to ~/Library/LaunchAgents/

Copy the the plist file in this Gist to ~/Library/LaunchAgents/.

Using Krypt.co

Step 1: Copy startup-gpg-agent.sh to ~/bin/

Copy the .sh file in this gist to ~/bin/.

Step 2: Copy org.gnupg.gpg-agent.plist file to ~/Library/LaunchAgents/

Copy the the plist file in this Gist to ~/Library/LaunchAgents/.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!-- This needs to be placed at ~/Library/LaunchAgents/org.gnupg.gpg-agent.plist -->
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.gnupg.gpg-agent</string>
<key>ProgramArguments</key>
<array>
<!-- Be sure to set this path correctly! -->
<string>/Users/yourusername/bin/start-gpg-agent.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
# Ensure that gpg can find the agent when needed
if [ -f ~/.gnupg/.gpg-agent-info ] && [ -n "$(pgrep gpg-agent)" ]; then
source ~/.gnupg/.gpg-agent-info
export GPG_AGENT_INFO
else
eval $(gpg-agent --daemon --write-env-file ~/.gnupg/.gpg-agent-info)
fi
# This line is important for GUI tools to also find it
launchctl setenv GPG_AGENT_INFO $GPG_AGENT_INFO
@Billy-

This comment has been minimized.

Copy link

@Billy- Billy- commented Mar 25, 2019

This looks like a really detailed write-up! But I'm getting hung up at the first step - brew reports Error: No available formula with the name "gpg-agent" ... I am running latest Mojave (10.14.3)

@Lillebo

This comment has been minimized.

Copy link

@Lillebo Lillebo commented May 23, 2019

GPG step 4 should be

export GPG_TTY=$(tty)
@jdhom

This comment has been minimized.

Copy link

@jdhom jdhom commented Jun 11, 2019

@Billy- that gpg-agent has moved to gnupg. see post here. Nice gist

@troyfontaine

This comment has been minimized.

Copy link
Owner Author

@troyfontaine troyfontaine commented Jun 12, 2019

This looks like a really detailed write-up! But I'm getting hung up at the first step - brew reports Error: No available formula with the name "gpg-agent" ... I am running latest Mojave (10.14.3)

Sorry! Should be fixed now!

@troyfontaine

This comment has been minimized.

Copy link
Owner Author

@troyfontaine troyfontaine commented Jun 12, 2019

GPG step 4 should be

export GPG_TTY=$(tty)

I've updated it based on the documentation recommending back ticks.

@goldsail

This comment has been minimized.

Copy link

@goldsail goldsail commented Aug 7, 2019

Steps 2-5 and Step 7 are critical to solving the issue, as few other answers mention these steps.

  • Steps 2-5 make it possible to prompt the window to let you type in the passphrase.
  • Step 7 allows necessary file access.

Without these steps, few other answers work.
Thank you very much!

@ulope

This comment has been minimized.

Copy link

@ulope ulope commented Mar 30, 2020

With gpg2 this has become easier. It's no longer necessary to put use-agent into the gpg config file or to manually start the agent. This now done transparently by gpg on demand.
It's also not nedded anymore to install gnupg via brew.

@mnbattaglia

This comment has been minimized.

Copy link

@mnbattaglia mnbattaglia commented Jul 23, 2020

In order to do Step 6 you need to look at Step 8. Why is in that order?

@mnbattaglia

This comment has been minimized.

Copy link

@mnbattaglia mnbattaglia commented Jul 23, 2020

In step 10 it uses a K (uppercase) param instead of k (lowercase). Is that intended? Beware the result is different: shows "sec" instead of "pub", and step 11 mentions "pub"

@alekc

This comment has been minimized.

Copy link

@alekc alekc commented Nov 28, 2020

For me gpg agent didn't fetch new config until I ran gpgconf --kill gpg-agent might be useful to add it to the troubleshooting.

@jsjoeio

This comment has been minimized.

Copy link

@jsjoeio jsjoeio commented Dec 14, 2020

Thanks for making this, super easy to follow! Saved me a ton of time.

@samuelboland

This comment has been minimized.

Copy link

@samuelboland samuelboland commented Jan 19, 2021

This is an excellent gist, thank you for taking the time to write this up.

@ianmuninio

This comment has been minimized.

Copy link

@ianmuninio ianmuninio commented Jan 27, 2021

For me gpg agent didn't fetch new config until I ran gpgconf --kill gpg-agent might be useful to add it to the troubleshooting.

+1 for this!

@ptlls

This comment has been minimized.

Copy link

@ptlls ptlls commented Mar 10, 2021

Homebrew for M1 (Apple Silicon) lives in a different directory so on Step 2 it's better to do this instead in the second command (brew --prefix returns the proper directory for Apple Silicon and Intel).

echo 'pinentry-program $(brew --prefix)/bin/pinentry-mac' > ~/.gnupg/gpg-agent.conf

This is the recommended approach in https://formulae.brew.sh/formula/pinentry-mac

@aequasi

This comment has been minimized.

Copy link

@aequasi aequasi commented Mar 15, 2021

Homebrew for M1 (Apple Silicon) lives in a different directory so on Step 2 it's better to do this instead in the second command (brew --prefix returns the proper directory for Apple Silicon and Intel).

echo 'pinentry-program $(brew --prefix)/bin/pinentry-mac' > ~/.gnupg/gpg-agent.conf

This is the recommended approach in https://formulae.brew.sh/formula/pinentry-mac

I've followed the entire post, and this comment, restarted the gpg-agent, and even restarted the entire mac, and I still get

gpg: agent_genkey failed: No pinentry

Is there any logs I can check for this?

@PabiGamito

This comment has been minimized.

Copy link

@PabiGamito PabiGamito commented Mar 17, 2021

I've followed the entire post, and this comment, restarted the gpg-agent, and even restarted the entire mac, and I still get

gpg: agent_genkey failed: No pinentry

Is there any logs I can check for this?

I'm getting this same problem 😬

@sysint64

This comment has been minimized.

Copy link

@sysint64 sysint64 commented Mar 23, 2021

@aequasi, @PabiGamito try to restart your computer, it helped me.

@julesrenaud

This comment has been minimized.

Copy link

@julesrenaud julesrenaud commented Mar 28, 2021

For those with issues following the tutorial on a Mac with an M1 chip (i.e. getting the gpg: agent_genkey failed: No pinentry error), removing the pinentry-program entry in the ~/.gnupg/gpg-agent.conf file fixed it for me. Make sure to reload the gpg-agent afterwards (gpgconf --reload gpg-agent). Found the solution here after an hour of digging around.

@MarsianMan

This comment has been minimized.

Copy link

@MarsianMan MarsianMan commented Apr 20, 2021

GPG step 4 should be

export GPG_TTY=$(tty)

I've updated it based on the documentation recommending back ticks.

FYI, use of back ticks is outdated on modern systems. Back ticks are the older style of creating a sub-shells; unfortunately because there was no difference between the start and stop characters, it becomes impossible to nest these commands as the interpreter cannot tell how the nesting is done. This is especially for commands where multiple sub-shells might be used in a nested manner.

The usage of backticks in the documentation is to specify that command is run in a subshell and not a string

export VARIABLE=`echo "I am`whoami` which is the same as `whoami`"`
# The above example is trivial but is a commonly desired use case that breaks. Hence a different syntax was introduced. 
# This syntax makes it simple to have multiple sub-shells within a sub-shell.
export VARIABLE=$(echo "I am $(whoami) which is the same as $(whoami)")

Sorry for the overly trivial examples, but the use cases are real, I assure you. 😄

Google Style guide https://google.github.io/styleguide/shellguide.html#command-substitution

@troyfontaine

This comment has been minimized.

Copy link
Owner Author

@troyfontaine troyfontaine commented Apr 20, 2021

GPG step 4 should be

export GPG_TTY=$(tty)

I've updated it based on the documentation recommending back ticks.

FYI, use of back ticks is outdated on modern systems. Back ticks are the older style of creating a sub-shells; unfortunately because there was no difference between the start and stop characters, it becomes impossible to nest these commands as the interpreter cannot tell how the nesting is done. This is especially for commands where multiple sub-shells might be used in a nested manner.

The usage of backticks in the documentation is to specify that command is run in a subshell and not a string

export VARIABLE=`echo "I am`whoami` which is the same as `whoami`"`
# The above example is trivial but is a commonly desired use case that breaks. Hence a different syntax was introduced. 
# This syntax makes it simple to have multiple sub-shells within a sub-shell.
export VARIABLE=$(echo "I am $(whoami) which is the same as $(whoami)")

Sorry for the overly trivial examples, but the use cases are real, I assure you. 😄

Google Style guide https://google.github.io/styleguide/shellguide.html#command-substitution

Okay, I can't disagree-it just made the most sense to go with what the maintainer suggested as they should be the subject-matter expert on their usage when implementing. Clearly they don't have that part of their docs getting much support, hence the old syntax. I've updated the gist-though the old syntax still works and doesn't introduce any issues in this context.

@tenkvil

This comment has been minimized.

Copy link

@tenkvil tenkvil commented Apr 30, 2021

Big thank you for this guide!

@racterub

This comment has been minimized.

Copy link

@racterub racterub commented May 31, 2021

For those with issues following the tutorial on a Mac with an M1 chip (i.e. getting the gpg: agent_genkey failed: No pinentry error), removing the pinentry-program entry in the ~/.gnupg/gpg-agent.conf file fixed it for me. Make sure to reload the gpg-agent afterwards (gpgconf --reload gpg-agent). Found the solution here after an hour of digging around.

This does helped me.
I am using Macbook Pro 2020-mid with clean install of BigSur.
Maybe this should be added in the tutorial :/

@troyfontaine

This comment has been minimized.

Copy link
Owner Author

@troyfontaine troyfontaine commented May 31, 2021

@racterub it is commented out with an explanation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment