I have used pass for years. It is a
hundred lines of shell around gpg and git, the store is a directory
of .gpg files, and that design is why it is still here while a dozen password
managers with venture funding are not. What I wanted was not a replacement for it. I wanted
the parts it leaves to extensions, without giving up the store.
Eu uso o pass há anos. São umas cem
linhas de shell em volta do gpg e do git, a store é um diretório de
arquivos .gpg, e é esse design que explica por que ele continua aqui enquanto uma
dúzia de gerenciadores de senha com capital de risco não está. O que eu queria não era um
substituto pra ele. Eu queria as partes que ele deixa pras extensões, sem abrir mão da
store.
So hidePass reads and writes the store you already have: the same
~/.password-store, the same gpg keys, the same git history. Rust, MIT,
v0.2.0 tagged today.
Então o hidePass lê e escreve a store que você já tem: o mesmo
~/.password-store, as mesmas chaves gpg, o mesmo histórico git. Rust, MIT,
v0.2.0 marcado hoje.
$ hidepass
Password Store
├── Email
│ └── work
└── Web
└── github
$ hidepass -c Web/github
Copied Web/github to clipboard. Will clear in 45 seconds.
$ hidepass otp Web/github
482915
Compatible instead of rewritten
Compatível em vez de reescrito
The whole design rests on one decision: hidePass does not own the format. It writes
.gpg files, honours .gpg-id and .gpg-id.sig, respects
every PASSWORD_STORE_* variable, and makes the same git commits. pass itself keeps
working on the same store, and so do the iOS and Android apps and the browser extensions. You
can alias pass=hidepass and also not do that, and nothing cares either way.
O design inteiro se apoia numa decisão: o hidePass não é dono do formato. Ele
escreve arquivos .gpg, respeita o .gpg-id e o
.gpg-id.sig, respeita toda variável PASSWORD_STORE_*, e faz os mesmos
commits git. O próprio pass continua funcionando na mesma store, e os apps de iOS e Android e
as extensões de navegador também. Você pode dar alias pass=hidepass e também pode
não dar, e nada se importa com a escolha.
Claiming compatibility is cheap, so it is a test rather than a sentence in a README.
CI installs the real pass and the real pass-extension-otp on every
push and runs a script in which each tool has to read what the other wrote. If I break the
format, the build tells me — not somebody whose passwords are in there.
Afirmar compatibilidade é barato, então ela é um teste em vez de uma frase num
README. O CI instala o pass de verdade e o pass-extension-otp de
verdade a cada push e roda um script em que cada ferramenta tem que ler o que a outra escreveu.
Se eu quebrar o formato, o build me avisa — e não alguém cujas senhas estão lá dentro.
A compatibility promise nothing re-runs is a compatibility promise you have
already broken and not noticed.
Uma promessa de compatibilidade que nada re-executa é uma promessa de
compatibilidade que você já quebrou e não percebeu.
Your clipboard manager is keeping your passwords
Seu gerenciador de clipboard está guardando suas senhas
This is the bug that annoyed me into writing the thing. Copy a secret on macOS and every clipboard manager on the machine — Maccy, Raycast, Alfred, Paste — writes it into its own searchable history, where it outlives the forty-five seconds you thought you were getting. The password manager cleared the pasteboard. The history did not.
Esse é o bug que me irritou o suficiente pra eu escrever a coisa. Copie um segredo no macOS e todo gerenciador de clipboard da máquina — Maccy, Raycast, Alfred, Paste — escreve o segredo no próprio histórico pesquisável, onde ele sobrevive aos quarenta e cinco segundos que você pensou que tinha. O gerenciador de senhas limpou o pasteboard. O histórico, não.
There is a convention for this — the concealed and
transient pasteboard types from nspasteboard.org — and clipboard managers do
honour it. hidePass marks every copied secret with both, so they decline to record it, and the
previous clipboard contents come back after forty-five seconds instead of being replaced by
nothing.
Existe uma convenção pra isso — os tipos de pasteboard concealed e
transient do nspasteboard.org — e os gerenciadores de clipboard respeitam. O
hidePass marca todo segredo copiado com os dois, então eles se recusam a registrar, e o
conteúdo anterior do clipboard volta depois de quarenta e cinco segundos em vez de ser
substituído por nada.
The things pass leaves to extensions
As coisas que o pass deixa pras extensões
One-time codes, passphrases, named fields, QR codes and a store audit are built in
rather than installed. The first line of an entry is the password; everything after it is
yours, key: value lines are readable with --field, and an
otpauth:// URI is all hidepass otp needs:
Códigos de uso único, passphrases, campos nomeados, QR codes e uma auditoria da
store são embutidos em vez de instalados. A primeira linha de uma entrada é a senha; tudo
depois dela é seu, linhas key: value são legíveis com --field, e uma
URI otpauth:// é tudo que o hidepass otp precisa:
correct horse battery staple user: alice@example.com url: https://example.com otpauth://totp/Example:alice?secret=JBSWY3DPEHPK3PXP&issuer=Example
It is the pass-otp format, deliberately, and HOTP is where that matters most. A HOTP code is only valid once, so reading one has to advance the counter: hidePass uses counter N+1, re-encrypts the entry with the new value and commits it, exactly as pass-otp does. Two tools can therefore share a HOTP entry without ever handing out the same code twice — which is the only version of "compatible" that means anything for a counter.
É o formato do pass-otp, de propósito, e o HOTP é onde isso mais importa. Um código HOTP só vale uma vez, então ler um tem que avançar o contador: o hidePass usa o contador N+1, re-encripta a entrada com o valor novo e faz o commit, exatamente como o pass-otp faz. Duas ferramentas podem então compartilhar uma entrada HOTP sem nunca entregar o mesmo código duas vezes — que é a única versão de "compatível" que significa algo para um contador.
Passphrases come from the EFF long wordlist, which has exactly 7776 words — five rolls of a six-sided die, and 12.925 bits per word. Six words is about 77 bits, which is the number I care about rather than how many symbols the thing contains:
As passphrases saem da lista longa de palavras da EFF, que tem exatamente 7776 palavras — cinco lançamentos de um dado de seis faces, e 12,925 bits por palavra. Seis palavras dão cerca de 77 bits, que é o número com que eu me importo, e não quantos símbolos a coisa tem:
$ hidepass generate --words 6 Email/work cubicle-unfold-dripping-tribune-ample-reveal
Plaintext and the disk it should never touch
O texto puro e o disco em que ele não deveria encostar
Editing an entry means decrypting it somewhere, and "somewhere" must not be a file
on a disk that keeps it after the editor exits. pass solves this with a ramdisk built by
hdiutil — and on macOS 27 I watched hdiutil attach -nomount print a
deprecation warning at me. hidePass uses diskutil image for
edit instead.
Editar uma entrada significa descriptografar em algum lugar, e esse "algum lugar"
não pode ser um arquivo num disco que o guarda depois que o editor fecha. O pass resolve isso
com um ramdisk construído pelo hdiutil — e no macOS 27 eu vi o hdiutil
attach -nomount cuspir um aviso de deprecação na minha cara. O hidePass usa o
diskutil image para o edit.
The better answer, though, was to need the ramdisk less often. hidepass
git does not use one at all, because git only ever sees encrypted files — there is no
plaintext in that path to protect. And when a ramdisk genuinely cannot be had, hidePass asks
before falling back to an ordinary temp directory rather than quietly deciding for you.
A resposta melhor, porém, foi precisar do ramdisk menos vezes. O hidepass
git não usa nenhum, porque o git só vê arquivos encriptados — não existe texto puro
nesse caminho pra proteger. E quando um ramdisk realmente não está disponível, o hidePass
pergunta antes de cair num diretório temporário comum, em vez de decidir por você em
silêncio.
Boring defaults, on purpose
Defaults chatos, de propósito
Four decisions that cost nothing to make and are miserable to retrofit:
Quatro decisões que não custam nada pra tomar e são miseráveis de colocar depois:
- Entries are written atomically. A gpg interrupted halfway through cannot leave a truncated entry where a password used to be.
- Recipients are validated before anything is re-encrypted. Re-encrypting to a key that turns out not to exist is how you lock yourself out of your own store.
- Passwords come from the OS CSPRNG, without modulo bias. Taking a random byte modulo the alphabet length makes some characters likelier than others; it is a one-line bug and it quietly costs entropy.
- Decrypted data is zeroed once it is no longer needed, rather than left in a freed allocation for whatever reads it next.
- As entradas são escritas atomicamente. Um gpg interrompido no meio não consegue deixar uma entrada truncada onde antes havia uma senha.
- Os recipients são validados antes de qualquer coisa ser re-encriptada. Re-encriptar para uma chave que acaba não existindo é assim que você se tranca fora da sua própria store.
- As senhas vêm do CSPRNG do sistema, sem viés de módulo. Pegar um byte aleatório módulo o tamanho do alfabeto torna alguns caracteres mais prováveis que outros; é um bug de uma linha e custa entropia em silêncio.
- Os dados descriptografados são zerados quando não são mais necessários, em vez de ficarem numa alocação liberada à espera de quem ler depois.
What it deliberately does not implement
O que ele deliberadamente não implementa
hidePass shells out to your gpg and your git. It does not
link a crypto library and it does not speak the git protocol, which means gpg-agent, pinentry,
smartcards and YubiKeys, SSH remotes and signed commits all work exactly as they already do —
because they are exactly what they already were. The temptation to reimplement either
one is the temptation to own a second, worse copy of a problem other people have been fixing
for decades.
O hidePass chama o seu gpg e o seu git. Ele não linka
uma biblioteca de criptografia e não fala o protocolo do git, o que significa que gpg-agent,
pinentry, smartcards e YubiKeys, remotes SSH e commits assinados funcionam exatamente como já
funcionavam — porque são exatamente o que já eram. A tentação de reimplementar
qualquer um dos dois é a tentação de ser dono de uma segunda cópia, pior, de um problema que
outras pessoas vêm consertando há décadas.
Three things do differ from pass, and they are in the README rather than in a
footnote: pass extensions in .extensions are not run, git's commit summaries go to
stderr so stdout carries only what you asked for, and listings show entries and folders rather
than every file in the store.
Três coisas diferem do pass, e elas estão no README em vez de numa nota de rodapé:
extensões do pass em .extensions não são executadas, os resumos de commit do git
vão para o stderr de modo que o stdout carrega só o que você pediu, e as listagens mostram
entradas e pastas em vez de todo arquivo da store.
Where it is now
Onde está agora
It is new — v0.2.0 is from today. Twelve modules, about 3,100 lines of
Rust, 26 #[test] functions plus the end-to-end suite and the pass cross-check, and
a minimum Rust of 1.88. The release pipeline builds macOS and Linux binaries and updates the
formula, so installing is one line:
Ele é novo — o v0.2.0 é de hoje. Doze módulos, cerca de 3.100 linhas
de Rust, 26 funções #[test] mais a suíte ponta a ponta e o cross-check com o pass,
e um Rust mínimo de 1.88. O pipeline de release compila binários de macOS e Linux e atualiza a
formula, então instalar é uma linha:
brew install youhide/youhide/hidepass
The source is at github.com/youhide/hidePass. Point it at a copy of your store before you point it at the real one — not because I expect it to eat anything, but because that is the advice I would want from someone whose password manager is two commits old.
O código está em github.com/youhide/hidePass. Aponte ele para uma cópia da sua store antes de apontar para a de verdade — não porque eu espere que ele coma alguma coisa, mas porque é o conselho que eu ia querer de alguém cujo gerenciador de senhas tem dois commits de idade.