Como manter suas chaves SSH seguras no Linux, macOS e Windows
· 17 min de leitura
- linux
- security
- ssh
Antes de começar
Este artigo é a parte prática de Chaves SSH por dentro: como funcionam, os tipos e os riscos. Lá estão o porquê de cada recomendação: o que a passphrase protege, o risco do agent forwarding, por que oferecer todas as chaves a todo servidor é ruim. Aqui está o que fazer, sistema por sistema.
Os comandos de Linux foram rodados com o OpenSSH 10.2p1 no Ubuntu 26.04, e os outputs são reais, com o nome de usuário trocado por um genérico, user. Os de macOS e Windows seguem a documentação da Apple, da Microsoft e dos projetos citados, mas não foram capturados aqui: compare cada passo com o que o seu sistema mostrar.
O que vale em todo sistema
- Use Ed25519, ou uma chave em hardware (
ed25519-sk) se você tem uma chave FIDO2. - Ponha passphrase em toda chave de uso pessoal. Sem ela, o arquivo é a credencial inteira. O outro artigo mostra como conferir as suas.
- Use uma chave por dispositivo. Notebook, desktop e servidor de CI, cada um com a sua.
- Deixe o agente guardar a chave por tempo limitado, como um dia de trabalho, e peça confirmação a cada uso se agentes de IA rodam comandos no seu terminal.
- Diga qual chave usar em cada servidor e não faça agent forwarding.
- Confira a host key na primeira conexão e quando ela mudar.
- Criptografe o disco: LUKS, FileVault ou BitLocker.
- Revise e revogue: as chaves cadastradas no GitHub e no GitLab, e o
authorized_keysdos servidores.
Um ~/.ssh/config de partida
Host github.com
User git
IdentityFile ~/.ssh/id_ed25519
Host servidor
HostName 203.0.113.10
User deploy
IdentityFile ~/.ssh/id_ed25519
Host interno
HostName 10.0.0.5
ProxyJump servidor
Host *
IdentitiesOnly yes
ForwardAgent no
AddKeysToAgent 8h
IdentitiesOnly yesfaz o cliente oferecer só a chave configurada para aquele servidor (ou as padrão, se nenhuma for), em vez de todas as que estão no agente.ForwardAgent nojá é o padrão, mas deixar escrito evita que um bloco anterior ligue sem você perceber.ProxyJump servidorfaz ossh internoatravessar oservidorsem levar o seu agente até ele.AddKeysToAgent 8hpõe a chave no agente na primeira vez que você digita a passphrase, e tira depois de oito horas: você digita uma vez por dia de trabalho, e a chave não passa a noite no agente.
O Host * fica no fim porque, no ssh_config, vale o primeiro valor encontrado para cada opção: os blocos específicos vêm antes, o genérico completa o que faltou. Para ver o resultado final de um host, use ssh -G:
ssh -G servidor | grep -E '^(hostname|user|identityfile|identitiesonly|forwardagent|addkeystoagent) '
user deploy
hostname 203.0.113.10
identitiesonly yes
identityfile ~/.ssh/id_ed25519
addkeystoagent 28800
forwardagent no
Uma chave por dispositivo, e nada de backup
Diferente de uma chave de criptografia, perder uma chave SSH não faz você perder dados, só acesso. Por isso, em vez de fazer backup da chave (e espalhar mais cópias dela), cadastre uma segunda chave nos lugares importantes: a de outro computador, ou uma chave em hardware guardada numa gaveta. Se o notebook for roubado, você remove só a chave dele de todo lugar e entra com a outra.
Para servidores, garanta um segundo caminho: o console web do provedor, por exemplo. Ficar trancado do lado de fora é o risco real de quem tem uma chave só.
Disco criptografado
A passphrase protege o arquivo da chave, mas o disco tem mais coisa: o known_hosts (onde você conecta), o config, o histórico do shell. Com o disco criptografado, um notebook perdido desligado não entrega nada disso. Linux: LUKS, oferecido pelo instalador da maioria das distribuições. macOS: FileVault. Windows: BitLocker, ou a "Criptografia do dispositivo" nas edições Home.
Linux
Permissões
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519 ~/.ssh/config ~/.ssh/authorized_keys
chmod 644 ~/.ssh/id_ed25519.pub
ls -la ~/.ssh
total 20
drwx------ 2 user user 140 Oct 15 13:14 .
drwxrwxr-x 3 user user 60 Oct 15 13:14 ..
-rw------- 1 user user 101 Oct 15 13:14 authorized_keys
-rw------- 1 user user 28 Oct 15 13:14 config
-rw------- 1 user user 464 Oct 15 13:14 id_ed25519
-rw-r--r-- 1 user user 101 Oct 15 13:14 id_ed25519.pub
-rw-r--r-- 1 user user 37 Oct 15 13:14 known_hosts
| Arquivo | Permissão | Quem acessa |
|---|---|---|
~/.ssh/ | drwx------ (700) | Só você entra na pasta |
id_ed25519, config, authorized_keys | -rw------- (600) | Só você lê e escreve |
id_ed25519.pub, known_hosts | -rw-r--r-- (644) | Você escreve, todos leem |
Os três grupos de letras são o que o dono, o grupo e os outros podem fazer: r ler, w escrever, x executar (numa pasta, entrar). O outro artigo explica como ler isso em detalhe, e mostra o aviso de "UNPROTECTED PRIVATE KEY FILE" que o cliente dá quando a chave privada está aberta demais.
O known_hosts não tem segredo, mas mostra onde você conecta. Se a máquina tem outros usuários, um chmod 600 nele também não faz mal. No servidor, o sshd recusa um authorized_keys, ou uma pasta ~/.ssh, em que outros usuários possam escrever.
Qual agente está rodando
Em quase todo desktop Linux já tem um agente. Descubra qual:
echo $SSH_AUTH_SOCK
/run/user/1000/gcr/ssh
Esse é o do GNOME (gcr-ssh-agent), que usa o ssh-agent do OpenSSH por baixo e pede a passphrase numa janela. No KDE, costuma ser o ssh-agent com o ksshaskpass. Se a variável vier vazia, não tem agente, e você pode iniciar um na sessão do terminal com eval "$(ssh-agent -s)".
A passphrase no chaveiro do sistema
No macOS, o Keychain guarda a passphrase e devolve a chave ao agente sozinho depois do login. No Linux, dá para ter o mesmo de algumas formas, todas com a troca da seção do macOS: a passphrase deixa de ser digitada e passa a ser protegida pela senha do seu login, ou do seu cofre.
- GNOME: quando o
gcr-ssh-agentpede a passphrase, a janela tem a opção Automatically unlock this key whenever I'm logged in (o texto pode aparecer traduzido). Marcada, a passphrase vai para o chaveiro de login do GNOME Keyring, que é destravado junto com a sua sessão. Para ver ou apagar depois, use o app Senhas e chaves (Seahorse). - KDE: o
ksshaskpasspode guardar a passphrase no KWallet, que também é destravado no login. - KeePassXC: a integração com o agente SSH adiciona a chave quando você destrava o banco e a remove quando você trava, com a opção de pedir confirmação a cada uso. Veja gerenciadores de senha como agente.
- O programa
keychain: apesar do nome, não guarda nada. É um script, disponível no pacotekeychainda maioria das distribuições, que reaproveita um únicossh-agententre os terminais, então a passphrase é pedida uma vez por boot. Serve para servidores e para o WSL, onde não tem um desktop com agente.
E se a chave não tem passphrase
Aí não tem o que guardar: o agente carrega uma chave sem passphrase sem perguntar nada, e o chaveiro nunca entra em jogo. É justamente o caso em que o arquivo sozinho já é a credencial. O caminho é pôr uma passphrase primeiro e deixar o chaveiro guardá-la:
- Confira se a chave tem passphrase, com o teste do outro artigo.
- Adicione uma. Numa chave sem passphrase, o
ssh-keygen -psó pede a nova, duas vezes:
ssh-keygen -p -f ~/.ssh/id_ed25519
Key has comment '[email protected]'
Enter new passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved with the new passphrase.
A chave continua a mesma, só passa a ficar criptografada no disco, então não precisa mudar nada no GitHub nem nos servidores.
- Tire do agente a versão sem passphrase, que continua carregada:
ssh-add -D. Ogcr-ssh-agentvolta a oferecer as chaves de~/.sshsozinho, agora pedindo a passphrase. - Use a chave normalmente, com um
git pushou umssh servidor. O GNOME mostra a janela pedindo a passphrase: digite e marque Automatically unlock this key whenever I'm logged in. Daí em diante, ela vem do chaveiro.
A janela só aparece quando é o próprio agente que precisa da passphrase. Se você rodar ssh-add no terminal, a passphrase é pedida ali mesmo, e a opção de guardar não aparece. Mantenha também o .pub ao lado da chave: é por ele que o gcr-ssh-agent encontra as chaves de ~/.ssh. No KDE e no KeePassXC, o passo 2 é o mesmo; depois, quem guarda a passphrase é o ksshaskpass ou o cofre.
Com a passphrase no chaveiro, o tempo de vida do agente perde força: a chave volta sozinha no próximo uso. O que continua protegendo durante o uso é a confirmação, abaixo.
Tempo de vida e confirmação
O AddKeysToAgent 8h do config de partida resolve o tempo de vida. Na mão, o equivalente é:
ssh-add -t 8h ~/.ssh/id_ed25519
Identity added: /home/user/.ssh/id_ed25519 ([email protected])
Lifetime set to 08:00:00
Por que oito horas, e não uma: o tempo de vida protege o momento em que você esquece a chave carregada, como o computador que fica ligado à noite. Enquanto você está usando o computador, ele não protege nada, e uma hora só faz você digitar a passphrase várias vezes por dia. Um dia de trabalho é o meio-termo: você digita de manhã, e a chave não passa a noite no agente. Quem protege durante o uso é a confirmação, ou o toque numa chave em hardware.
Para pedir confirmação a cada uso:
ssh-add -c -t 8h ~/.ssh/id_ed25519
Identity added: /home/user/.ssh/id_ed25519 ([email protected])
Lifetime set to 08:00:00
The user must confirm each use of the key
Cada assinatura abre uma janela perguntando se você permite. Negando, quem pediu recebe isto:
sign_and_send_pubkey: signing failed for ED25519 "[email protected]" from agent: agent refused operation
user@localhost: Permission denied (publickey).
A janela vem de um programa ssh-askpass: no GNOME, o próprio gcr-ssh-agent traz o dele; no KDE, é o ksshaskpass. Sem nenhum, a confirmação não tem como acontecer e todo uso é negado, então em servidores e no WSL sem interface gráfica fique só com o tempo de vida. No config, o equivalente é AddKeysToAgent confirm 8h.
Para tirar tudo do agente ao sair de perto do computador:
ssh-add -D
Chaves em hardware (FIDO2)
Com uma chave FIDO2, como um YubiKey (série 5) ou um Nitrokey 3, a chave privada é gerada dentro do dispositivo e não sai de lá. O OpenSSH das distribuições principais já vem com suporte:
ssh-keygen -t ed25519-sk -O verify-required -C "[email protected]"
- O dispositivo pede um toque a cada uso, por padrão.
- O
-O verify-requiredpede também o PIN do dispositivo a cada uso, então um dispositivo roubado não serve sozinho. - O arquivo gerado em
~/.ssh/id_ed25519_sknão é a chave, só uma referência a ela: sem o dispositivo, ele não serve para nada.
Se você alterna entre computadores, o -O resident guarda essa referência no próprio dispositivo, e o ssh-keygen -K a recupera em qualquer máquina. O manual avisa da troca: com tudo no dispositivo, quem o rouba fica mais perto de usá-lo, e por isso chaves residentes normalmente exigem um PIN configurado.
Não tenho um dispositivo FIDO2 neste laboratório, então os outputs desta seção não foram capturados. Algumas chaves mais antigas não suportam Ed25519: nesse caso, use -t ecdsa-sk.
Se você também administra o servidor
Quando todas as suas chaves estiverem funcionando, desligue a entrada por senha no servidor. Crie um arquivo em /etc/ssh/sshd_config.d/:
# /etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
O nome começa com 10- de propósito. No sshd_config também vale o primeiro valor encontrado, os arquivos dessa pasta são lidos em ordem alfabética, e imagens de nuvem costumam trazer um 50-cloud-init.conf com PasswordAuthentication yes. Confira o valor efetivo com sshd -T. Num laboratório com os dois arquivos:
sudo sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin)'
permitrootlogin prohibit-password
passwordauthentication no
kbdinteractiveauthentication no
prohibit-password é o padrão: o root só entra com chave. Se você nunca entra como root, troque por PermitRootLogin no.
Valide a sintaxe com sudo sshd -t e reinicie o serviço (sudo systemctl restart ssh no Debian e no Ubuntu, sshd no Fedora e no Arch). Mantenha a sessão atual aberta e teste uma conexão nova em outro terminal antes de sair: se algo deu errado, é por essa sessão que você conserta.
macOS
A passphrase no Keychain
O macOS já traz o OpenSSH e um agente iniciado pelo sistema. A integração própria da Apple guarda a passphrase no Keychain:
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
E no ~/.ssh/config:
Host *
IgnoreUnknown UseKeychain
UseKeychain yes
AddKeysToAgent yes
Com isso, a passphrase fica no Keychain, que é destravado quando você faz login, e a chave volta ao agente sozinha depois de reiniciar. É cômodo, mas troca a passphrase pela senha da sua conta do macOS: quem estiver com a sua sessão aberta usa a chave. Bloqueie a tela ao sair de perto.
O IgnoreUnknown UseKeychain é para o caso de você usar também o OpenSSH do Homebrew: o UseKeychain só existe no OpenSSH da Apple, e o do Homebrew recusa o config inteiro com "Bad configuration option" sem essa linha.
Chaves no Secure Enclave
Macs com chip Apple ou com o chip T2 têm o Secure Enclave, um chip separado de onde chaves não podem ser extraídas. O Secretive é um app de código aberto que cria chaves SSH lá dentro e funciona como agente, com Touch ID por uso, se você quiser. É o equivalente a ter uma chave em hardware sem comprar um dispositivo, com uma diferença: a chave fica presa àquele Mac, então ela é a chave daquele dispositivo, e você precisa de outra para os demais.
O Secure Enclave só gera chaves ECDSA P-256, não Ed25519. Tudo bem: a fragilidade do ECDSA está no número aleatório de cada assinatura, e quem gera esse número é o próprio chip.
Chaves FIDO2 no macOS
O OpenSSH que vem com o macOS é compilado sem suporte a chaves FIDO, e o ssh-keygen -t ed25519-sk falha. O caminho mais comum é instalar o OpenSSH do Homebrew (brew install openssh), que inclui esse suporte, e conferir com which ssh e ssh -V qual dos dois está sendo usado. O Secretive costuma ser a opção mais simples no Mac.
FileVault
Em Ajustes do Sistema > Privacidade e Segurança > FileVault. O assistente de configuração do Mac oferece ligá-lo, mas vale conferir.
Windows
O OpenSSH já vem no sistema
O Windows 10 (desde a versão 1809) e o Windows 11 já trazem o cliente OpenSSH. No PowerShell:
ssh -V
As chaves ficam em C:\Users\VOCÊ\.ssh, ou $env:USERPROFILE\.ssh. O ssh-keygen é o mesmo, e o padrão também é Ed25519:
ssh-keygen -t ed25519 -C "[email protected]"
Permissões
No Windows, as permissões são ACLs, e o OpenSSH do Windows também recusa uma chave privada que outros usuários conseguem ler. Para deixar o arquivo acessível só para você:
icacls $env:USERPROFILE\.ssh\id_ed25519 /inheritance:r
icacls $env:USERPROFILE\.ssh\id_ed25519 /grant:r "$($env:USERNAME):F"
O primeiro comando remove as permissões herdadas da pasta; o segundo dá controle total só ao seu usuário.
O serviço ssh-agent
O agente do Windows é um serviço, que vem desativado. Num PowerShell como administrador:
Get-Service ssh-agent | Set-Service -StartupType Automatic
Start-Service ssh-agent
E, como o seu usuário:
ssh-add $env:USERPROFILE\.ssh\id_ed25519
Aqui o Windows é bem diferente do Linux e do macOS: o agente guarda a chave de forma persistente, associada à sua conta do Windows, e ela continua lá depois de reiniciar. A documentação da Microsoft chega a sugerir apagar o arquivo da chave depois de adicioná-la. Na prática, você digita a passphrase uma vez, e daí em diante a proteção da chave é a proteção da sua conta do Windows. Então:
- Proteja a conta: Windows Hello ou uma senha forte, e tela bloqueada ao sair de perto.
- Use BitLocker.
- Remova chaves que não usa mais com
ssh-add -D, oussh-add -dcom o arquivo. - Para uma chave mais sensível, prefira uma chave em hardware ou um gerenciador de senhas como agente (abaixo).
Git for Windows
O Git for Windows traz o próprio ssh.exe, que não conversa com o serviço ssh-agent do Windows. O sintoma é o Git pedir a passphrase toda vez, mesmo com a chave no agente. Aponte o Git para o OpenSSH do sistema:
git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe"
PuTTY
Se você usa o PuTTY, ou programas que o embutem (FileZilla, WinSCP, TortoiseGit), atualize: as versões 0.68 a 0.80 vazavam chaves ECDSA P-521 (CVE-2024-31497). Se você usou uma chave P-521 nessas versões, troque a chave, porque atualizar não desfaz as assinaturas já feitas. O PuTTY usa o formato .ppk e o próprio agente, o Pageant. O PuTTYgen converte chaves entre os formatos do PuTTY e do OpenSSH.
Chaves FIDO2 no Windows
O OpenSSH do Windows usa a API WebAuthn do próprio sistema, então chaves físicas FIDO2 funcionam sem instalar nada:
ssh-keygen -t ed25519-sk -O verify-required -C "[email protected]"
O Windows mostra a própria janela pedindo o PIN e o toque. Usar o Windows Hello do computador como autenticador, em vez de um dispositivo físico, é instável: há relatos de a janela trocar o Windows Hello por "celular ou chave USB" depois de atualizações.
WSL
O WSL é um Linux à parte, com o próprio ~/.ssh: siga a seção de Linux lá dentro. Não guarde as chaves em /mnt/c/...: por padrão, os arquivos do disco do Windows aparecem no WSL com permissões abertas, e o ssh se recusa a usá-los.
Agentes de IA e as suas chaves
Agentes de IA que trabalham no seu terminal, como o Claude Code, o Codex CLI ou o modo agente dos editores, rodam comandos com o seu usuário e herdam as suas variáveis de ambiente. Isso inclui o SSH_AUTH_SOCK: se a sua chave está no agente, um git push, um ssh servidor ou um commit assinado que eles rodem funciona sem pedir nada. É o mesmo risco do agent forwarding: ninguém copia a chave, mas ela é usada sem você ver. E um agente de IA pode ser levado a isso por um texto que ele leu, como um README, uma issue ou uma página com instruções escondidas (prompt injection).
Não é motivo para tirar as chaves do agente, e sim para decidir o que um comando que você não digitou pode fazer:
- Confirmação a cada uso, com
AddKeysToAgent confirm 8hou a aprovação do 1Password, do Bitwarden ou do KeePassXC. Cadagit pushdo agente de IA abre uma janela na sua tela, e você vê o que ia acontecer. Negado, ele recebe oagent refused operationmostrado acima. - Toque numa chave em hardware. Um processo não aperta um botão físico.
- Chaves separadas e limitadas para o que o agente de IA faz sozinho. No GitHub, uma deploy key dá acesso a um repositório só, e um token fine-grained, usado via HTTPS, pode ser limitado a alguns repositórios e permissões. Se for mal usada, o estrago fica ali.
- A chave de produção fora do alcance, ou com confirmação, enquanto um agente de IA trabalha na mesma sessão.
- Isolamento. Num container ou numa VM, o agente de IA só vê o que você monta lá dentro: não monte o socket do seu agente nem a pasta
~/.ssh.
Uma combinação que funciona bem: a chave pessoal com confirm 8h e, se o agente de IA precisa fazer push sozinho, uma deploy key restrita àquele repositório.
Gerenciadores de senha como agente
O 1Password, o Bitwarden e o KeePassXC podem funcionar como agente SSH: a chave fica criptografada no cofre, e só fica disponível enquanto o cofre está destravado. O 1Password e o Bitwarden ainda pedem aprovação a cada uso, com Touch ID, Windows Hello ou a senha mestra. Funcionam nos três sistemas.
A troca: o cofre sincroniza entre os seus dispositivos, então a mesma chave passa a existir em todos eles, ao contrário do "uma chave por dispositivo". Para a maioria das pessoas, ter a chave protegida pelo cofre e com aprovação por uso compensa. Se você prefere manter uma chave por dispositivo, crie uma chave para cada um dentro do cofre.
Se uma chave vazar
- Remova a chave pública de todo lugar em que ela entra: GitHub, GitLab e o
authorized_keysde cada servidor. Para achar a linha, liste os fingerprints do arquivo e compare com o da chave vazada:
ssh-keygen -lf ~/.ssh/authorized_keys
256 SHA256:4+ja/K0i3L7pStIZYZVOnEvEd0YEFPjedY/2GlUWgFA [email protected] (ED25519)
256 SHA256:cycriPjeX1MyKaDUPvNH4ySmfg8RVZe+EGnV/EImFnQ [email protected] (ED25519)
3072 SHA256:ypI93OhP6BnAPzWA7Ut6fzAxERVrO8R1Ot19RxPG204 [email protected] (RSA)
256 SHA256:uhpYBP/1S7pAB7Agqm4u9GbKMzEld7i7qIWH5Su8PH0 [email protected] (ECDSA)
- Gere uma chave nova e cadastre no lugar da antiga.
- Procure se ela foi usada. Nos servidores, cada entrada fica no log com o fingerprint da chave (
journalctl -u ssh | grep 'Accepted publickey', ou-u sshdfora do Debian e do Ubuntu). No GitHub, o log de segurança da conta mostra as ações recentes. - Revise o
authorized_keysdos servidores em que ela entrava. Quem entrou com uma chave roubada pode ter deixado outra chave lá, para voltar depois.
Checklist
- Chaves Ed25519 (ou
-skem hardware); nenhuma DSA, nenhuma RSA abaixo de 3072 bits - Passphrase em toda chave de uso pessoal (confira com
ssh-keygen -y -P '') - Uma chave por dispositivo, e uma segunda chave cadastrada nos lugares importantes
- Agente com tempo de vida (
AddKeysToAgent 8houssh-add -t 8h) - Confirmação a cada uso, ou chave em hardware, se agentes de IA rodam comandos no seu terminal
-
IdentitiesOnly yese umaIdentityFilepor servidor - Sem agent forwarding;
ProxyJumppara passar por um bastion - Host keys conferidas na primeira conexão e quando mudam
- Permissões corretas (
chmod 600, ouicaclsno Windows) - Disco criptografado (LUKS, FileVault ou BitLocker)
- Chaves revisadas no GitHub, no GitLab e no
authorized_keysdos servidores - Entrada por senha desligada nos servidores que você administra
- PuTTY e derivados na versão 0.81 ou mais nova
- Um plano para chave vazada: remover, trocar, procurar nos logs