TL;DR
- SSH uses a public and private key pair instead of tokens, making it more secure and convenient for Git authentication.
- Generate keys with
ssh-keygen -t ed25519, then add the public key to our remote repository platform (GitHub, GitLab, Bitbucket). - Use
ssh-agentand an SSH config file withAddKeysToAgentandUseKeychainso we only enter our passphrase once. - Test the connection with
ssh -T git@github.comto verify everything is working.
GitHub, GitLab, Bitbucket and many more are platforms to host remote repositories using Git. The two most common ways to connect to a remote repository from local Git are HTTPS and SSH.
Each has its own advantages and disadvantages, but throughout this blog post we will focus on SSH since it is the most commonly used method and it offers many benefits compared to HTTPS.
Note: This post assumes git is installed on the local machine. It can also be used as a practical guide for setting up a new SSH connection to a remote repository. With existing SSH keys, jump straight to Generating SSH Keys.
SSH and Git
SSH is a protocol that allows us to securely communicate between a local machine and a remote host. Git is a distributed version control system designed to track changes in files and directories over time.
Git is a collaboration tool that allows us to work on projects collaboratively, and using SSH will help us communicate our changes to other people in a secure way.
Benefits of Using SSH over HTTPS
Security
When using HTTPS to connect to a remote repository, we typically use a Personal Access Token (PAT) for authentication. Platforms like GitHub deprecated password based HTTPS authentication back in 2021, so tokens are now the standard. The token and data are encrypted in transit, so they’re not easily readable by anyone on the network. However, we still need to enter or cache our token for each session, and if the token is leaked or stolen, anyone with it can access our repositories until we revoke it.
When using SSH, we use a private and public key pair for authentication, which works differently:
- Private Key is stored only on our local machine. Think of it as a digital signature unique to us. It is encrypted and often secured with a passphrase.
- Public Key is stored on the remote server. This is what our private key “unlocks” to prove it is really us.
sequenceDiagram
participant Local as Our Machine
participant Server as Remote Server
Local->>Server: Connection request
Server->>Local: Send challenge (newly generated)
Note over Local: Solve challenge using Private Key
Local->>Server: Send response
Note over Server: Verify response using Public Key
Server->>Local: Authenticated
Our local machine uses the private key to prove our identity without sending the private key itself over the network. Instead, the server sends a challenge to our local machine that only our private key can solve. This challenge is newly generated every time we try to connect to the remote server. After our machine solves the challenge, the server verifies the response using our public key. If it matches, we are authenticated. Because the challenge changes every time, intercepting the network traffic won’t help an attacker gain access.
Convenience
Using SSH is more convenient than HTTPS for connecting to a remote server because it eliminates the need to enter our token every time. Instead, we can use an SSH agent, which securely stores our passphrase in memory and handles the authentication process for subsequent connections.
Generate SSH Keys
There are several algorithms that can be used to generate SSH keys. In this blog post, we will use Ed25519 (Edwards-curve Digital Signature Algorithm) because it is modern and faster than other algorithms.
Check Existing SSH Keys
Before generating a new SSH key, we should check if we already have one because we don’t want to accidentally overwrite an existing key.
### inputls -al ~/.sshLook for id_ed25519 and id_ed25519.pub as these are the default file names for SSH key generation.
### outputtotal 40drwx------@ 7 username staff 224 Nov 5 15:45 .drwxr-x---+ 42 username staff 1344 Nov 5 19:18 ..-rw-r--r--@ 1 username staff 185 Sep 11 19:00 config-rw-------@ 1 username staff 464 Sep 11 18:24 id_ed25519-rw-r--r--@ 1 username staff 104 Sep 11 18:24 id_ed25519.pub-rw-------@ 1 username staff 1842 Oct 25 16:47 known_hosts-rw-------@ 1 username staff 1106 Oct 25 16:43 known_hosts.oldIf we see id_ed25519 and id_ed25519.pub then we already have SSH keys. If we don’t want to overwrite them, we will need to specify a different path for the next key generation. If we want to use our existing SSH keys, we can skip the creation step and continue to Connecting to a remote repository.
Create a New SSH Key
# inputssh-keygen -t ed25519 -C "your_email@example.com"-t is the algorithm type, -C (optional) is custom comment.
# outputGenerating public/private ed25519 key pair.Enter file in which to save the key (/Users/username/.ssh/id_ed25519):Press enter to accept the default path or provide a custom path with the file name.
# outputEnter passphrase (empty for no passphrase):Passphrase is optional, but I highly recommend entering one to protect the use of our private SSH key.
We will be using ssh-agent later so that we don’t have to enter the passphrase every time we make a connection.
# output# dummy dataYour identification has been saved in /Users/username/.ssh/id_ed25519Your public key has been saved in /Users/username/.ssh/id_ed25519.pubThe key fingerprint is:SHA256:nds92nn/dsNDsadbjndkansdjsknadkj your_email@example.comThe key's randomart image is:+--[ED25519 256]--+| o.**|| o**|| ++**|| . +=*|| S. . +o*o|| o..=.o.=|| --o+o+o|| .. o=*=|| o+E=|+----[SHA256]-----+Locate the Newly Generated SSH Key
Verify whether the key was generated successfully by running:
For public key:
# inputcat ~/.ssh/id_ed25519.pubAdjust the path if using a custom one.
# output# dummy datassh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB8Dkha0+XZ9sjd34fGFkeGZkHZck9TZx7Hnm0Dd9e2j bry@yourhostFor private key:
# inputcat ~/.ssh/id_ed25519Adjust the path if using a custom one.
# output# dummy data-----BEGIN OPENSSH PRIVATE KEY-----b3BlbnNzaC1rZXktZDI1NTE5AAAAIB8Dkha0+XZ9sjd34fGFkeGZkHZck9TZx7Hnm0Dd9e2j+AAAAAAAAAAAAAAAAAAEAAAAIB8Dkha0+XZ9sjd34fGFkeGZkHZck9TZx7Hnm0Dd9e2j+AAAAAlkdiJHDU6FOSZDa7yxptjMNxzHxgtid7YKm2GFjdjXaAAAAAE2jxKVmksl3Dbjoef1H5/tf1sgYnJe+JqfgvUhrBgkgmfhdAAAAAAEAAAXLS1mhdhdbHJdkfHZ9GFFX3NNMgVdk+KfbkhTgyg==-----END OPENSSH PRIVATE KEY-----If everything is done correctly, we should see our public and private key in the output as shown above.
Connect to a Remote Repository
Add Public Key to Remote Repository
We need to add our public key to the remote repository platform we are using. Visit one of these links:
- GitHub: https://github.com/settings/keys
- GitLab: https://gitlab.com/-/profile/keys
- Bitbucket: https://bitbucket.org/account/settings/ssh-keys/
Note: Links provided may become invalid in the future as I don’t have control over any changes the platforms might make.
Click New SSH Key or Add SSH Key.
Give the key a descriptive name.
Copy the public key from the local machine:
# inputpbcopy < ~/.ssh/id_ed25519.pubAdjust the path if using a custom one.
Paste the public key in the Key field and save.
Add Our Private SSH Key to the ssh-agent
First, we need to start the ssh-agent running in the background.
Mac:
exec ssh-agent zshWSL using bash:
exec ssh-agent bashWindows (I haven’t tested this):
CMD:
start-ssh-agentPowerShell:
Start-Service ssh-agentThen add the private key to the agent:
ssh-add C:\path\to\your\keySet Up the SSH Config File
Check if we have an existing ssh config file.
cat ~/.ssh/configIf we don’t have one, create a new config file.
touch ~/.ssh/configopen ~/.ssh/configUpdate our config file based on the remote host we are using.
For example:
# For GitHubHost github.com AddKeysToAgent yes UseKeychain yes # macOS only IdentityFile ~/.ssh/id_ed25519UseKeychain will save our passphrase in the keychain, which will be used to unlock our private key when we make a connection. This way, we don’t have to enter the passphrase again every time we start a new session or restart our computer.
Test the SSH Connection
# input# githubssh -T git@github.com# gitlabssh -T git@gitlab.com# bitbucketssh -T git@bitbucket.orgIf everything goes well, we should see the following output:
# outputHi username! You've successfully authenticated, but GitHub does not provide shell access.Now we are ready to connect, clone repositories, and push changes to our remote repository!
Wrapping Up
To recap what we covered in this post: we started by understanding why SSH is a better choice than HTTPS for connecting to remote repositories. Then we generated an Ed25519 key pair, added the public key to a remote repository platform, configured the SSH agent and SSH config file for convenience, and finally tested the connection. We should now be able to push, pull, and clone repositories over SSH without entering credentials every time.
Questions Worth Exploring
These topics go deeper into some of the concepts we touched on in this post. I will cover them in future posts.
- Why is a passphrase necessary? We mentioned that a passphrase protects our private key, but what exactly happens if someone gets access to our private key without a passphrase?
- What is ssh-agent and how does it work? We used
ssh-agentto avoid entering our passphrase repeatedly, but how does it actually store and manage our keys in memory?