
RNG da ColdCard: e quem tinha passphrase?
Table of Contents
Um complemento ao post do Akita sobre o problema do RNG da Coinkite, voltado para quem usava uma passphrase BIP39 e para a pergunta que fica no ar: isso me protegeu?
O Akita fez um trabalho excelente explicando como o gerador de números aleatórios de alguns firmwares da ColdCard falhou, e como isso permite reconstruir a seed de uma carteira por força bruta. Se você ainda não leu, leia primeiro. Este texto começa onde o dele termina.
Mas tem uma frase que aparece em quase toda discussão sobre o caso e que raramente vem acompanhada da conta certa:
"Ah, mas eu usava passphrase, então estou seguro."
Depende. E o "depende" é mais interessante do que parece. Vou mostrar exatamente do que depende, com um experimento que você pode rodar na sua máquina.
O bug, em um parágrafo
O firmware passou a gerar a seed usando o gerador de software do MicroPython (um PRNG chamado Yasmarang) em vez do gerador de hardware. Um PRNG desses é determinístico: se você conhece o estado inicial, reproduz toda a sequência. E o estado inicial vinha de fontes fraquíssimas: o número de série do chip (fixo), o relógio (zerado no boot frio) e um contador que, pelo instante da inicialização, tinha só uns 16 a 24 bits úteis. O resultado é que, num ColdCard Mk2/Mk3, a entropia efetiva desabou de 2²⁵⁶ para cerca de 2⁴⁰. Isso é enumerável: uma GPU comum varre esse espaço em cerca de nove dias, e um farm de placas, em minutos. (Os Mk4/Q/Mk5 se saíram melhor, mas não escaparam; volto a eles adiante.)
Repare no que foi comprometido: a entropia e, portanto, a mnemonic (as 24 palavras). Guarde isso, porque é onde a passphrase entra na história.
Onde a passphrase se encaixa

A passphrase do BIP39, a famosa "25ª palavra", não é uma palavra da lista. É um texto livre que você digita, e ela entra num único lugar do processo: no salt da função que transforma a mnemonic em seed.
seed = PBKDF2(HMAC-SHA512,
senha = mnemonic,
salt = "mnemonic" + passphrase,
2048 iterações, 64 bytes)Duas coisas saltam dessa fórmula. A primeira é que a passphrase não passa pelo RNG: ela é digitada por você depois, e o bug do gerador não a tocou. Ela tem a entropia que você deu a ela, nem mais, nem menos. A segunda é que trocar a passphrase muda a seed inteira. O salt muda, o PBKDF2 cospe outros 64 bytes, e você cai numa carteira completamente diferente, com outros endereços. É por isso que a passphrase serve para "carteiras ocultas": a mesma mnemonic, com passphrases diferentes, são carteiras distintas e independentes.
A virada de chave: ela não soma, ela multiplica
Aqui está o ponto que quase todo mundo erra.
Num cenário normal, a mnemonic carrega 128 a 256 bits de segurança e a passphrase é um bônus por cima. Você pensa nela como "+alguns bits". Ela apenas soma.
Só que o atacante, para chegar nos seus endereços, precisa acertar a mnemonic e a passphrase ao mesmo tempo. Ele não tem como testar a passphrase isolada: o único jeito de saber se um par (mnemonic, passphrase) está certo é derivar o endereço e conferir se ele tem saldo na blockchain. Não existe atalho, não existe oráculo para a passphrase sozinha.
Isso significa que os dois espaços de busca se multiplicam:
trabalho total = (espaço da mnemonic) × (espaço da passphrase)E é aqui que o caso da ColdCard fica peculiar. Como a mnemonic colapsou para ~2⁴⁰, a passphrase deixou de ser um bônus e virou o orçamento inteiro de segurança da sua carteira. Os papéis se inverteram: quem estava segurando o forte era a mnemonic, e agora é a passphrase, sozinha.
Se ela for forte, os 40 bits quebrados da mnemonic não importam. Se ela for fraca, ela é a única coisa entre o atacante e suas moedas, e não foi projetada para esse trabalho.
Mostrando na prática
Para não ficar no campo das ideias, montei uma reprodução offline do ataque contra uma carteira que eu mesmo gero, sem tocar em nenhuma carteira real (o código está no fim). O script gera uma vítima com o RNG quebrado, mas com uma passphrase, e depois tenta quebrá-la de dois jeitos.
A vítima desta rodada:
SysTick secreto : 165
passphrase secreta: 'wallet'
endereço público : bc1q3nm6hrr5dn3z07j04vl9g4xemqvt9l96k0x3emAtaque 1: o atacante assume que não há passphrase
É o ataque do post do Akita: varrer o espaço do SysTick e derivar cada carteira sem passphrase.
0 acertos em 300 tentativas. A passphrase BLOQUEOU o ataque.
mesmo no SysTick correto (165), sem passphrase o endereço vira:
bc1qaleaf0rc0hw6wzp4dpeukan4yyd6es8cs4vgxf != bc1q3nm6hrr5dn3z07j04vl9g4xemqvt9l96k0x3emOlha que interessante: mesmo acertando o SysTick exato (ou seja, mesmo tendo reconstruído a mnemonic inteira, perfeitamente), o endereço derivado sem passphrase é outro. O atacante passa reto. Foi literalmente isso que salvou muita gente que usava passphrase: o ataque de massa deriva endereços sem passphrase e simplesmente não encontra as carteiras protegidas.
Ataque 2: o atacante inclui uma wordlist de passphrases fracas
Agora o atacante testa o produto passphrases × SysTick, com uma lista de palavras comuns:
>>> CHAVE RECUPERADA <<<
passphrase : 'wallet' (era a #5 da wordlist)
SysTick : 165
mnemonic : define test follow embark deliver toast rally harsh napkin sleep ...
tentativas : 1.366 (vs ~300 do ataque sem passphrase)
custo multiplicado por : ~5x (= nº de passphrases testadas)A carteira caiu. E o custo foi exatamente o previsto pela teoria: o número de passphrases testadas vezes o trabalho original. Com uma lista de 20 palavras, o custo multiplicaria por até 20. Com uma lista de milhões de palavras e regras (o que qualquer atacante sério usa), multiplica por milhões.
O ponto do experimento não é o número absoluto. É ver a multiplicação acontecendo. É o mesmo mecanismo, na mesma escala, que decide se sua carteira real está segura.
O gradiente de risco
Agora dá pra colocar números em cima da sua situação. Partindo dos ~2⁴⁰ da mnemonic quebrada (Mk2/Mk3), e usando uma taxa medida de verdade: uma RTX 5090 faz cerca de 1,4 milhão de derivações BIP39 por segundo. (O benchmark do hashcat numa RTX 5090 marca ~4,2 milhões/s para o PBKDF2-HMAC-SHA512 a 999 iterações; nas 2048 iterações do BIP39 isso cai para ~2 milhões/s, e a derivação da chave e do endereço puxa para a casa de 1,4 milhão.) A tabela mostra o tempo para uma placa dessas, para dez, e para um farm de mil.
| Sua passphrase | total | 1 GPU | 10 GPUs | farm (1000 GPUs) |
|---|---|---|---|---|
| nenhuma | 2⁴⁰ | ~9 dias | ~22 horas | ~13 min |
| PIN de 4 dígitos (~13 bits) | 2⁵³ | ~200 anos | ~20 anos | ~75 dias |
| 1 palavra comum (~13 a 17 bits) | 2⁵⁵ | ~800 anos | ~80 anos | ~10 meses |
| 2 palavras diceware (~26 bits) | 2⁶⁶ | inviável | inviável | ~1.700 anos |
| 3 palavras diceware (~39 bits) | 2⁷⁹ | inviável | inviável | inviável |
| 4+ palavras diceware (~52 bits) | 2⁹² | inviável | inviável | inviável |
| aleatória de 128 bits | 2¹⁶⁸ | inviável | inviável | inviável |
Como ler: cada tempo é de lote, não por carteira. Uma passada acha de uma vez todas as carteiras daquela linha (a do PIN pega todos os PINs de 4 dígitos juntos). Sem passphrase, "todas" é literal; nas outras linhas, "todas as que usaram aquele tipo de passphrase".
Leia o formato da tabela, não os números soltos. Sem passphrase, a carteira cai em horas para qualquer um com um punhado de placas, e foi assim que o roubo em massa drenou milhares de endereços numa tarde. Um PIN ou uma palavra resiste a uma máquina só, mas não a um farm, e as 1000+ BTC que sumiram nesse caso pagam um farm de mil placas sem suar. Já duas palavras aleatórias de verdade jogam o tempo para mais de mil anos mesmo contra esse farm. É essa a fronteira que importa.
Essa tabela é o pior caso (Mk2/Mk3, base 2⁴⁰). Num Mk4/Q/Mk5 que fez o reseed, some ~32 bits em cada linha. Melhora bastante, mas o ponto de partida (~2⁷² sem passphrase) ainda está na lista de "migre assim mesmo" da Coinkite, e um Mk4 sem reseed volta para ~2⁴¹. A passphrase segue sendo o mesmo multiplicador em todos eles.
A leitura importante fica em dois extremos.
Se a sua passphrase é forte (3 ou mais palavras aleatórias de verdade), você está bem. Os 40 bits quebrados da mnemonic são irrelevantes perto dos 39 ou mais bits que a sua passphrase acrescenta. Foi o "plus" que segurou tudo.
Se ela é fraca (um PIN, um nome, uma data, uma palavra), você está na zona de perigo. Olhe a coluna do farm: meses ou pouco mais para um atacante com mil placas, e é o tipo de investimento que 1000+ BTC justificam. Numa máquina só parece confortável, mas é o farm que decide, não o seu notebook. E lembre: normalmente essa passphrase fraca ficaria escondida atrás de uma mnemonic de 128 bits. Aqui não tem mnemonic segurando nada atrás dela.
Uma regra de bolso: a passphrase precisa carregar sozinha uns 40 bits, mais ou menos 3 a 4 palavras genuinamente aleatórias, para recompor um espaço de busca seguro. E cuidado, porque "memorável" quase nunca é "aleatório". Uma frase de música, uma citação, o nome do seu cachorro com o ano, tudo isso é entropia baixíssima para um atacante com boas wordlists.
Aqui vale isolar uma armadilha: "inventei uma palavra que não está em dicionário nenhum" não é a mesma coisa que "aleatória". Quem quebra senhas não testa só listas de palavras. Testa também máscaras de caracteres (todas as sequências até certo tamanho) e modelos estatísticos que geram texto pronunciável, imitando como cada língua encadeia letras. Uma palavra inventada que você consegue decorar é, por construção, pronunciável e parecida com a sua língua, que é justamente o que esses modelos acertam cedo. Um "brixanto" de oito letras não vale os quase 38 bits de oito letras sorteadas ao acaso; vale bem menos. Inventar a palavra dá uma vantagem pequena sobre uma de dicionário, mas nem perto do suficiente. O que conta é o processo que gerou a passphrase: caracteres realmente sorteados, por dados ou por um gerador, não a criatividade do seu cérebro.
O que ainda falta pra quebrar
Se o cenário parece assustador, vale entender por que na prática ele é mais difícil do que uma tabela sugere:
- As reproduções (a do Akita e a minha) simplificam a sequência de chamadas ao gerador. Um ataque real ainda precisa reproduzir bit a bit o que o firmware fazia no boot.
- A passphrase não vaza de lugar nenhum: não está no dispositivo nem na blockchain. O atacante só pode adivinhá-la, e só valida um palpite junto com um palpite de mnemonic. É produto cartesiano, sem oráculo.
- O custo por tentativa é alto de propósito: cada palpite exige um PBKDF2 de 2048 iterações mais a derivação das chaves, e o BIP39 usa essa função lenta exatamente para isso. Vale com ou sem passphrase, então a passphrase multiplica qualquer velocidade que você assuma, seja a GPU a 1e9/s ou a 1e6/s.
- Os modelos mais novos não estão a salvo: só são mais caros de quebrar. Nos Mk4/Q/Mk5, o firmware às vezes misturava no estado do PRNG o TRNG dos dois secure elements (o "reseed", que a própria Coinkite chama de "a backup to a backup"), elevando o espaço para ~2⁷². Mas com duas ressalvas grandes. A primeira: o reseed nem sempre acontecia, e um Mk4 que não o fez fica em ~41 bits, tão frágil quanto um Mk3. A segunda: 72 bits está muito abaixo do alvo de 128 e não é "seguro". É uma zona cinzenta, ao alcance de um adversário muito bem financiado, ainda mais porque os fundos ficam num registro permanente. Dá pra atacar hoje ou daqui a anos, quando computar ficar mais barato (harvest now, crack later). Por isso a própria Coinkite orienta migrar também nesses modelos, a menos que você tenha adicionado ao menos 50 rolagens de dado independentes na geração da seed. O dilema da passphrase, então, vale para todos os modelos afetados. Nos mais novos ele só parte de um patamar mais alto.
O outro lado: a passphrase não foi feita pra isso
Aqui vem a parte que acho mais importante, e que é fácil concluir errado depois de ler tudo acima.
A tentação natural é: "então vou botar uma passphrase gigante e complicadíssima". Não faça isso como reação a esse bug. Por dois motivos.
Primeiro, porque a passphrase nunca foi uma defesa contra falha de RNG. Ela foi projetada para proteger de roubo físico da seed. Se alguém encontra suas 24 palavras anotadas num papel, sem a passphrase elas abrem uma carteira (a "duress wallet"), e não a de verdade. É uma proteção de plausibilidade e de acesso físico. No caso da ColdCard ela funcionou como defesa contra o RNG por acidente, um efeito colateral feliz. O conserto de verdade para o bug não é a passphrase.
Segundo, e mais perigoso: não existe "esqueci minha passphrase". Não há recuperação. Se você digita a passphrase errada, o software não reclama. Ele silenciosamente deriva outra carteira, vazia, e você olha para um saldo zerado sem entender o que aconteceu. Uma passphrase complexa demais, que você não consegue reproduzir com 100% de fidelidade e que não está guardada de forma robusta, é uma forma quase garantida de perder seus próprios fundos. Nesse jogo, você é uma ameaça maior à sua carteira do que o atacante.
O equilíbrio, então:
- Ela é anti-roubo, não anti-RNG.
- Forte o bastante para não ser adivinhada, mas anotada ou memorável o bastante para você nunca perdê-la.
- Para o bug em questão, ela compra tempo proporcional à entropia dela. Não te dá imunidade.
O que fazer, na prática
Se você teve uma ColdCard afetada, a resposta é a mesma independente da força da sua passphrase:
- Gere uma seed nova em um dispositivo ou firmware sabidamente corrigido, e mova os fundos para lá. A passphrase não conserta uma mnemonic quebrada; ela só atrasa o atacante na medida da própria entropia.
- Estime honestamente a entropia da sua passphrase antiga. A maioria das pessoas superestima grosseiramente. Se era um PIN, uma palavra ou uma frase reconhecível, trate como comprometida e trate a migração como urgente.
- Não reaproveite a passphrase antiga como se ela fosse o problema. O problema é a entropia da seed; a passphrase é um controle separado.
Reproduza você mesmo
Todo o experimento é offline e roda contra uma carteira sintética gerada na hora. Nada de varrer a blockchain atrás de fundos de terceiros. O código está no GitHub, em romulostorel/coldcard-rng-demo (em inglês). O bruteforce.py reproduz o ataque original (recuperar a seed via SysTick), e o bruteforce_passphrase.py acrescenta a dimensão da passphrase: o bloqueio do Ataque 1, a multiplicação medida do Ataque 2, e a tabela de extrapolação.
git clone https://github.com/romulostorel/coldcard-rng-demo
cd coldcard-rng-demo
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python bruteforce_passphrase.py --seed 7A conclusão em uma linha: a passphrase transformou os 40 bits quebrados da mnemonic no seu problema, mas só te salva de verdade se ela carregar, sozinha, a segurança que a mnemonic deveria ter carregado. Para muita gente ela carregou. Para quem usou um PIN ou uma palavra, ela é um cronômetro, não um cofre.
We want to work with you. Check out our Services page!



