GitHub SSH Remote Connection Configuration and GPG Commit Signature Verification

I just reinstalled the operating system and need to configure GitHub's SSH keys from scratch. Having to search for commands each time is quite tedious, so I decided to record the process for future reference.

About the operating system, Git version, and use case in this article:

  • Windows 11 IoT Enterprise LTSC 24H2
  • git version 2.47.0.windows.1
  • Two GitHub accounts, both requiring SSH and GPG keys configuration
  • All the commands used below are executed in Git Bash.
  • Content enclosed in angle brackets < > in the commands needs to be adjusted according to personal circumstances, such as the filename <Filename>. When adjusting, remove the angle brackets, meaning the commands should not contain any < >.
  • Please adjust the file paths, setting options, and other content in the text according to your actual situation.

Git Installation

Since this is a freshly installed system, Git isn’t installed yet. So first, let’s download Git.

After the installation, launch Git Bash.

SSH Remote Connection Configuration

Next, configure the SSH key for remote connections.

SSH Key Generation

Use the Ed25519 algorithm to generate an SSH key, replacing <your_email@example.com> with your primary GitHub email address.

1
ssh-keygen -t ed25519 -C "<your_email@example.com>"

At this point, the system will prompt Enter file in which to save the key for selecting the key’s storage location. Press Enter to accept the default location /c/Users/<username>/.ssh/id_ed25519.

Then, you’ll see prompts for Enter passphrase for "/c/Users/<username>/.ssh/id_ed25519" (empty for no passphrase): and Enter same passphrase again: to set and confirm a passphrase for the key. You can press Enter twice to skip setting a passphrase. Note that if you do set a passphrase, you’ll need to enter it each time you perform git push, which may be inconvenient.

Finally, the system will output the key’s location, fingerprint, and other details. The complete output will look similar to the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Generating public/private ed25519 key pair.  
Enter file in which to save the key (/c/Users/<username>/.ssh/id_ed25519):
Enter passphrase for "/c/Users/<username>/.ssh/id_ed25519" (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /c/Users/<username>/.ssh/id_ed25519
Your public key has been saved in /c/Users/<username>/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:jfalkjfstsdfgy <your_email@example.com>
The key's randomart image is:
+--[ED25519 256]--+
| +*+*- |
| -*//- |
| /-/++/ |
| **/** |
| /+*/+ |
| //*+. |
| +-*/* |
| /*--/ |
| /*-** |
+----[SHA256]-----+

Add SSH Key to Your GitHub Account

After creating the SSH key pair, you need to add the public key to your GitHub account.

First, open the public key file with Notepad and copy all its contents.

1
notepad ~/.ssh/id_ed25519.pub

In the ~/.ssh directory (i.e., C:\Users\<username>\.ssh), you’ll find two files: id_ed25519 and id_ed25519.pub. The former is the private key, and the latter is the public key. Only the public key needs to be added to your GitHub account—do not upload the private key online.

Next, log in to your GitHub account in a browser and follow these steps: click your profile photo in the top right corner → SettingsSSH and GPG keys in the sidebar → New SSH key button in the top right.

In the Title field, enter a name to identify the key, making it easier to distinguish keys from different devices. Keep Key type set to the default Authentication Key option, and paste the copied public key into the Key field. Finally, click Add SSH Key.

Difference Between Authentication Key and Signing Key

Generated by ChatGPT:

When adding SSH keys on GitHub, you’ll see two types of keys: Authentication Key and Signing Key. Here’s the difference between them:

  • Authentication Key
    • Purpose: The Authentication Key is used for identity verification. It allows you to securely connect to GitHub via the SSH protocol, enabling you to perform actions such as git push and git pull.
    • How It Works: When you connect to GitHub using an SSH URL (like git@github.com:username/repo.git), GitHub authenticates you using your SSH public key. You need to add this public key to your GitHub account to authenticate your identity through SSH.
    • Usage Scenarios: All actions that require access to GitHub, such as cloning, pushing, and pulling code, use the Authentication Key.
  • Signing Key
    • Purpose: The Signing Key is used to digitally sign commits, ensuring they were created by a specific user and adding credibility to the code.
    • How It Works: When you make a commit, you can sign it using either a GPG or SSH key. This signature allows other developers to verify that the commit is genuinely from you and hasn’t been tampered with.
    • Usage Scenarios: In projects where commit verification is important, especially in open-source projects, using a Signing Key adds an extra layer of security.

To verify that the configuration is successful, try connecting. If this is your first connection, you’ll see a prompt saying, Are you sure you want to continue connecting (yes/no/[fingerprint])? Type yes and press Enter.

1
ssh -T git@github.com

If configured successfully, the system will display the message: Hi <Username>! You've successfully authenticated, but GitHub does not provide shell access.

Global Git Username and Email Address Configuration

Source: GitHub Docs

You can change the name that is associated with your Git commits using the git config command. The new name you set will be visible in any future commits you push to GitHub from the command line. If you’d like to keep your real name private, you can use any text as your Git username.Changing the name associated with your Git commits using git config will only affect future commits and will not change the name used for past commits.

You can use the git config command to change the email address you associate with your Git commits. The new email address you set will be visible in any future commits you push to GitHub from the command line. Any commits you made prior to changing your commit email address are still associated with your previous email address.

Set the global username by replacing <Username> with your GitHub username.

1
git config --global user.name "<Username>"

Set the global email address by replacing <your_email@example.com> with your primary GitHub email address.

1
git config --global user.email "<your_email@example.com>"

These settings can also be modified in the file located at C:\Users\<Username>\.gitconfig.

Commit Signature Verification

The SSH configuration is now complete, and you should be able to use commands like git push normally. However, without commit signature verification, our commits lack the “Verified” checkmark (the small green badge in the image below). Next, I’ll add a GPG key to my GitHub account and enable signature verification for my Git commits.

Purpose of Commit Signature Verification

Generated by ChatGPT:

Q: Since we’ve already verified our identity with SSH keys during push, why do we need to verify commit signatures separately?
A: Although SSH keys confirm your identity and authorize access to the remote repository, commit signatures serve distinct purposes:

  1. Identity Verification vs. Commit Source Authentication
    • SSH Key Verification: When you push using SSH, GitHub or GitLab checks if you have permission to push code. This process verifies identity but is focused on access control, ensuring you are an authorized user.
    • Commit Signature: Signing verifies the source of each individual commit. It ensures that a given commit was genuinely created by the holder of the private key. Even if a malicious user gains access to your remote repository (e.g., by obtaining your SSH key), they still can’t forge signatures on previous commits.
  2. Tamper Resistance
    • SSH Key Verification: Verifies only access rights to the repository but doesn’t guarantee the integrity of each commit.
    • Commit Signature: Guarantees the integrity of commit content. If a commit is altered, its signature will no longer be valid, alerting users to any modifications that compromise trustworthiness.
  3. Enhanced Security
    • Commit signatures provide an additional security layer to defend against malicious activities, both internal and external. Even if an SSH key is compromised, signature verification helps ensure that the code’s integrity and origin remain secure.

Using SSH Keys for Signing

There’s actually a shortcut: after adding your SSH key to your GitHub account, you can repeat the steps and change the key type from Authentication Key to Signing Key. This allows the SSH key to be used for commit signature verification without needing a separate GPG key—essentially one key serving dual roles. However, GPG keys offer a bit more functionality when used for signature verification, so I still opted to set up a GPG key.

To use an SSH key for signing, you’ll need to configure a few additional settings:

  • Use git config --global gpg.format ssh to configure Git to use SSH for commit and tag signatures.
  • Use git config --global user.signingkey ~/.ssh/<your_ssh_key.pub> to specify the signing key in Git.

GPG Key Generation

When Git is installed, GPG is typically included. You can check if GPG is installed by running the following command:

1
gpg --version

A successful check will return version information; if there’s an error, you may need to install GPG manually.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gpg (GnuPG) 2.4.5-unknown  
libgcrypt 1.9.4-unknown
Copyright (C) 2024 g10 Code GmbH
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /c/Users/<Username>/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

Now, generate a GPG key pair.

1
gpg --full-generate-key

The system will guide you through the key configuration:

  1. Choose encryption type: Press Enter to select the default ECC (Elliptic Curve Cryptography).
  2. Select the encryption curve: Press Enter to choose the default Curve25519.
  3. Choose the key expiration date: Press Enter to keep the default “No Expiration” setting.
  4. Press “y” and Enter to confirm these selections.

Next, provide key information:

  1. Enter a name (optional), which can simply be your GitHub username.
  2. Enter an email address—this must be the primary email linked to your GitHub account.
  3. Enter a comment if desired (optional).
  4. Press O and Enter to confirm.
  5. In the Pinentry window that appears, set a password to protect your key, then enter it again to confirm. You’ll need this password to sign commits. If you prefer not to set a password, you can leave it blank.

Add GPG Key to Your GitHub Account

Like SSH keys, we also need to add the GPG key’s public key to your GitHub account.

First, list the GPG keys on your system:

1
gpg --list-secret-keys --keyid-format=long

The output will look similar to this:

1
2
3
4
5
6
/c/Users/<Username>/.gnupg/pubring.kbx  
----------------------------------
sec ed25519/21231BBF246642D21 2024-10-01 [SC]
16534564331BBDF314564321231
uid [ultimate] Name (Comments) <your_email@example.com>
ssb cv25519/652153215AB215621 2024-10-01 [E]

It may look confusing, but the format is:

1
2
3
4
sec <Algorithm>/<Key ID> <Creation Date> [<Usage>]
<Public Key>
uid [ultimate] <Key Name> (<Comments>) <Email Address>
ssb <Algorithm>/<Key ID> <Creation Date> [<Usage>]

The key’s usage types are:

  • S: Signing
  • E: Encryption
  • C: Certification

We need to use the ID of the key with signing capabilities, which is the key ID from the line with the [SC] suffix, such as 21231BBF246642D21, or the email address, to export the ASCII-encoded public key and automatically copy it to the clipboard using the command below.

1
gpg --armor --export <Key ID or Email Address> | clip

Next, log in to your GitHub account and navigate as follows:

  1. Click on your profile photo in the upper-right corner → Settings
  2. In the left sidebar, click SSH and GPG keys
  3. Then click the New GPG key button on the right.

In the Title field, give your new signing key a label to help identify it across devices. In the Key field, paste the GPG public key you copied. Finally, click Add GPG key to complete the addition.

Commit Signature Verification Configuration

Finally, we need to configure the global GPG key.

1
git config --global user.signingkey <Key ID>

Additionally, to sign a commit, you need to add the -S option in the commit command. If you set a password for your GPG key earlier, a prompt will appear, asking you to enter it.

1
git commit -S -m "<Commit Message>"

(Optional) If you find it inconvenient to include the -S option each time, you can set Git to sign all commits by default, so you won’t need to use -S in each commit command.

1
git config --global commit.gpgsign true

(Optional) Set Up GPG-Agent Cache

If you find entering the password for each commit inconvenient, you can set up a GPG-Agent cache duration so that you only need to enter the password once within the cache period.

First, create the configuration file in the ~/.gnupg directory and open it with Notepad:

1
touch ~/.gnupg/gpg-agent.conf && notepad ~/.gnupg/gpg-agent.conf

Paste the following configuration, then save and close the file:

1
2
default-cache-ttl 604800
max-cache-ttl 604800
  • The numbers are in seconds. Set reasonable values; for instance, initially setting a one-month cache duration (2,592,000 seconds) didn’t work for me, but a one-week duration (604,800 seconds) did.
  • default-cache-ttl n: Set the time a cache entry is valid to n seconds. The default is 600 seconds. Each time a cache entry is accessed, the entry’s timer is reset. To set an entry’s maximum lifetime, use max-cache-ttl. Note that a cached passphrase may not be evicted immediately from memory if no client requests a cache operation. This is due to an internal housekeeping function which is only run every few seconds.
  • max-cache-ttl n: Set the maximum time a cache entry is valid to n seconds. After this time a cache entry will be expired even if it has been accessed recently or has been set using gpg-preset-passphrase. The default is 2 hours (7200 seconds).

(Optional) Import and Trust GitHub Public Key

When we commit directly on the GitHub website, the commit signature verification uses GitHub’s key rather than our own. If we don’t have GitHub’s signature verification public key locally, the related commits will display gpg: Can't check signature: No public key.

You can switch to a Git repository directory and use the following command to check:

1
git log --show-signature

The output will look like this, where you can see that the GitHub key ID is B5690EEEBB952194, highlighted in red.

This red box is quite uncomfortable for those with OCD, so I choose to import and trust the GitHub public key locally to get rid of this red box.

First, import GitHub’s public key locally:

1
curl https://github.com/web-flow.gpg | gpg --import

Then, check the existing keys:

1
gpg --list-keys --keyid-format=long

The output looks like this. The first one is your own GPG key, and the next two are GitHub’s (with one of them even expired).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ gpg --list-keys --keyid-format=long  
/c/Users/Sirius/.gnupg/pubring.kbx
----------------------------------
pub ed25519/21231BBF246642D21 2024-10-01 [SC]
16534564331BBDF314564321231
uid [ultimate] Name (Comments) <your_email@example.com>
sub cv25519/652153215AB215621 2024-10-01 [E]

pub rsa2048/4AEE18F83AFDEB23 2017-08-16 [SC] [expired: 2024-01-16]
5DE3E0509C47EA3CF04A42D34AEE18F83AFDEB23
uid [ expired] GitHub (web-flow commit signing) <noreply@github.com>

pub rsa4096/B5690EEEBB952194 2024-01-16 [SC]
968479A1AFF927E37D1A566BB5690EEEBB952194
uid [ unknown] GitHub <noreply@github.com>

Next, edit GitHub’s public key.

1
gpg --edit-key B5690EEEBB952194

Trust the key.

1
trust

You will be prompted to select the trust level. Enter 4 and press Enter.

Finally, save the changes.

1
save

The full output is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Sirius@Pikachu MINGW64 ~/.gnupg  
$ gpg --edit-key B5690EEEBB952194
gpg (GnuPG) 2.4.5-unknown; Copyright (C) 2024 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


pub rsa4096/B5690EEEBB952194
created: 2024-01-16 expires: never usage: SC
trust: unknown validity: unknown
[ unknown] (1). GitHub <noreply@github.com>

gpg> trust
pub rsa4096/B5690EEEBB952194
created: 2024-01-16 expires: never usage: SC
trust: unknown validity: unknown
[ unknown] (1). GitHub <noreply@github.com>

Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

1 = I don't know or won't say
2 = I do NOT trust
3 = I trust marginally
4 = I trust fully
5 = I trust ultimately
m = back to the main menu

Your decision? 4

pub rsa4096/B5690EEEBB952194
created: 2024-01-16 expires: never usage: SC
trust: full validity: unknown
[ unknown] (1). GitHub <noreply@github.com>
Please note that the shown key validity is not necessarily correct
unless you restart the program.

gpg> save
Key not changed so no update needed.

If this key expires in the future, simply delete it and re-import the latest version.

1
gpg --delete-key B5690EEEBB952194

(Optional) VS Code Configuration

If you want to use Git with commit signing in VS Code, open the settings by pressing ctrl+,, then search for git.enableCommitSigning, and check the option Allow GPG, X.509, or SSH commit signing.

Multiple Account Configuration

If you only need to set up one GitHub account, all the configurations are complete up to this point. Next, I will continue configuring the SSH and GPG keys for the second account. All files, paths, and suffixes related to the second account will be named second, but feel free to modify them according to your preferences.

SSH Key Configuration

Second SSH Key Generation

Let’s generate the second SSH key:

  • Replace <your_email@example.com> with the preferred email address for the second account.
  • Replace <username> with your Windows account name (i.e., the name of your user folder).
  • Replace <id_ed25519_second> with the filename for the second key.
    1
    ssh-keygen -t ed25519 -C "<your_email@example.com>" -f "/c/Users/<username>/.ssh/<id_ed25519_second>"

Since we specified the key file path in the command, the system will only prompt you to enter and confirm a passphrase. Simply press Enter twice to skip.

Generate SSH Configuration File

We can create a config file to differentiate Git operations for different accounts using distinct Hosts.

1
touch ~/.ssh/config && notepad ~/.ssh/config

The configuration file format is as follows:

1
2
3
4
Host <alias>
HostName <actual Host>
User <username>
IdentityFile <private key path>

Paste the following content, adjusting the Host alias and key file path to match your setup, then save and close the file:

1
2
3
4
5
6
7
8
9
10
11
# Default config for main account
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519

# Config for second account
Host github.com-second
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_second

The meaning of these two configuration blocks is:

  • Use the id_ed25519_second key for Git URLs with @github.com-second.
  • Use the id_ed25519 key for all other Git URLs.

Add SSH Key to GitHub Account

The process of adding the SSH key to the second GitHub account is essentially the same as for the first account. Just replace the name of the first key with the second one in the commands.

To verify if the configuration was successful, use the following command:

1
ssh -T git@github.com-second

Commit Signature Verification

The process of generating the second GPG key and adding it to the second GitHub account is identical to the first account. Follow the previous steps, but do not make global settings again.

Adjust Git Commands

For the second account, when using commands like git clone, you need to replace the default @github.com with the Host alias you configured earlier, i.e., @github.com-second. For example:

1
git clone git@github.com-second:Username/Repo.git

Multiple Accounts Distinction

I have already set the first account’s configuration as global settings. To avoid confusion between the two accounts, we need to separate their Git configurations. Below are two methods I’ve found for configuring them.

I will continue using second as the username/filename in the examples. Please make sure to replace it with your own second account’s name.

Fast Configuration with Batch Processing

This method involves a local Git setup for each Git repository for the second account. By using local settings with higher priority, we can differentiate the repositories for each account.

However, entering multiple commands each time can be cumbersome. Instead, you can add these commands to a batch file and then add the batch file to the system’s environment variables. This way, you can run the batch file once to complete the configuration.

First, create the ~/.gitswitch/second.bat file in the user directory and open it with Notepad:

1
mkdir -p ~/.gitswitch/ && touch ~/.gitswitch/second.bat && notepad ~/.gitswitch/second.bat

Then, paste and edit the following content, save, and close the file:

1
2
3
4
git config --local user.name <second account username>
git config --local user.email <second account email address>
git config --local user.signingkey <second account GPG key secret ID>
git config --local commit.gpgsign true

Next, run the following command in a terminal with administrator privileges to open the system environment variable settings:

1
rundll32.exe sysdm.cpl,EditEnvironmentVariables

Add C:\Users\<Username>\.gitswitch to the system environment variables. Replace <Username> with your Windows username:

  1. Double-click the Path under system variables.
  2. Click New at the top right.
  3. Paste the path.
  4. Click OK to save.
  5. Restart the terminal to apply the changes.

Afterward, if you need to configure the second account locally for a repository, simply enter and execute the script.

1
second.bat

Conditional Includes

This method uses the new conditional includes introduced in version 2.13, allowing for more flexible differentiation using conditionals.

Before configuring conditional settings, create the Git configuration file for the second account in the user directory and open it with Notepad:

1
mkdir -p ~/.gitswitch/second/ && touch ~/.gitswitch/second/.gitconfig && notepad ~/.gitswitch/second/.gitconfig

Then, paste and modify the following configuration, save, and close Notepad:

1
2
3
4
5
6
[user]
name = <second account username>
email = <second account email address>
signingkey = <second account GPG key secret ID>
[commit]
gpgsign = true

Finally, add the conditional includes at the end of the global Git configuration file ~/.gitconfig. I’ve summarized two common conditions: path matching and URL matching, which can be used in combination.

Note: Conditional includes should be placed below the global settings!

Path Matching

We can specify a parent directory for the second account’s Git repositories, and then all Git repositories under that directory will use the same specified configuration. Repositories outside this directory will use the global configuration by default. This eliminates the need for individually configuring each Git repository for the second account.

Add the following configuration at the end of the global Git configuration file ~/.gitconfig. On Windows, you must include /i to disable case sensitivity; otherwise, the configuration may not take effect due to path issues:

1
2
[includeIf "gitdir/i:D:/Project/Second/"]
path = ~/.gitswitch/second/.gitconfig

With this, all projects under the path D:/Project/Second/ will automatically use the configuration in ~/.gitswitch/second/.gitconfig, while projects in other paths will retain the original global configuration.

URL Matching

This method requires Git version 2.36 or higher and is used to set different Git configurations by determining the remote URL used. Similar to the previous method, it requires creating a separate configuration file for the second account, as outlined earlier. Then, add the following content to the global configuration file:

1
2
[includeIf "hasconfig:remote.*.url:git@github.com-second:*/**"]
path = ~/.gitswitch/second/.gitconfig

This configuration means that if the remote.*.url field in a repository’s local configuration corresponds to git@github.com-second:Username/Repo.git, the specified configuration file ~/.gitswitch/second/.gitconfig will be used. This method seamlessly integrates with the SSH Host configuration set earlier, making it very convenient.

The complete global Git configuration file ~/.gitconfig would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
[user]
name = <main account username>
email = <main account email address>
signingkey = <main account GPG key secret ID>
[commit]
gpgsign = true

[includeIf "gitdir/i:D:/Project/Second/"]
path = ~/.gitswitch/second/.gitconfig

[includeIf "hasconfig:remote.*.url:git@github.com-second:*/**"]
path = ~/.gitswitch/second/.gitconfig

Reference: