Uma arquitetura de conhecimento zero: o servidor não tem chaves de decifragem para nada além dos identificadores técnicos. Todos os campos com significado — números de telefone, endereços, coordenadas, segredos de webhook, BSSID de Wi-Fi, saudações — são cifrados no dispositivo do proprietário e ficam no servidor como um blob opaco. A chave de decifragem chega ao convidado dentro da ligação de convite, no fragmento do endereço, que o navegador nunca envia ao servidor.
Algorithm: AES-256-GCM. Disponível de origem na JVM (através de javax.crypto),
e suportado pelo Android Keystore. Dá em simultâneo confidencialidade e autenticação.
v1:<base64url(nonce || ciphertext || tag)>
v1: — um prefixo de versão, para mudar o formato mais tarde.nonce — 12 bytes aleatórios, novos a cada cifragem.ciphertext+tag — tudo o que devolve Cipher.doFinal; a etiqueta de 128 bits está incluída.SecureRandom() (o do sistema). No Android é /dev/urandom + o prng do Linux.
EncryptedSharedPreferences com o nome master_key e nunca sai do dispositivo em claro.K_obj para o armazenamento local.encrypt_k_master(K_obj) sob a chave obj_key_{id}.data_cipher no servidor).K_obj_new → voltar a cifrar o data_cipher e todos os pacotes que o continham.{obj_id: K_obj} com os objetos permitidos ao convidado.bundle_cipher. O K_guest em si o servidor nunca vê.entrixy.com/key#<user_key><base64url(K_guest)> — um único bloco de 75 caracteres.
O fragmento não segue para o servidor com o pedido HTTP: é uma garantia básica do navegador.K_guest só é possível emitindo uma nova ligação. É nisso que assenta a revogação:
o servidor apaga o registo da chave e o convidado deixa de receber um bundle_cipher atual.| Key | Valor |
|---|---|
master_key | 32 bytes em claro (o SharedPreferences cifra-os de qualquer modo). |
obj_key_{id} | A K_obj do objeto, envolvida com K_master. |
obj_plain_{id} | (opcional) uma cache do JSON decifrado, para a interface desenhar depressa. |
| Tabela / campo | Conteúdo |
|---|---|
numbers.data_cipher | JSON cifrado com K_obj: {phone, url, secret, geo_lat, geo_lon, share_lat, share_lon, snapshot, welcome, label}. |
numbers.id / type | Em claro — necessário para o encaminhamento e para mostrar o tipo de ícone. |
keys.bundle_cipher | JSON cifrado com K_guest: {obj_ids: [1,2,3], obj_keys: {1: K_obj1, 2: K_obj2, ...}, welcome_cipher: ..., ...}. |
keys.user_key / mode / force_when_busy | Em claro — para o encaminhamento e uma verificação rápida do acesso. |
https://entrixy.com/key#<user_key(32)><K_guest_base64url(43)>
user_key e 43 caracteres da chave de convidado.user_key — o identificador do lado do servidor. Sem ele o servidor não entrega o pacote.#...) nunca chega ao registo do servidor — é uma garantia do navegador./key — JS comum: lê location.hash, analisa o bloco e entrega a chave
à aplicação ou à versão web /app/. O servidor nunca vê a chave./download/android,
de onde a aplicação apanha a chave sozinha.K_obj.K_obj → data_cipher.POST /api/number_add com data_cipher mais o tipo. O servidor devolve um id.obj_key_{id} = encrypt_k_master(K_obj).Construir o novo JSON localmente, cifrá-lo com a K_obj → POST /api/number_update.
K_obj existente, que não muda.
[obj1, obj2, obj3]. Gera-se K_guest.{obj_ids: [...], obj_keys: {1: K_obj1, ...}, welcome_cipher: ...}.K_guest → bundle_cipher.POST /api/key_create com bundle_cipher, obj_ids (em claro — para a matriz de acesso).user_key.entrixy.com/key#{user_key}{base64url(K_guest)}./key reads location.hash e entrega a chave à aplicação ou à versão web.POST /api/key_bundle.php com user_key → recebe bundle_cipher e a lista de obj_ids.K_guest→ obtendo {obj_keys, welcome}.K_guest e os decifrados obj_keys são guardados em EncryptedSharedPreferences do cliente convidado.data_cipher do objeto → decifragem com a obj_key_{id}.O proprietário carrega em Apagar → POST /api/key_revoke.php → o servidor apaga o registo da chave.
No pedido seguinte do pacote o convidado é recusado e perde o acesso.
O proprietário carrega em Regenerar chave nas definições do objeto. Gera-se K_obj_new,
e voltam a cifrar-se todos os bundle_cipher que contêm esse objeto. O cliente não os sabe de cor,
por isso o cliente do proprietário reconstrói os pacotes: vai buscar o bundle_cipher de cada uma das
suas chaves, decifra-o, insere K_obj_newe volta a cifrar.
Os novos blobs seguem para o servidor num só lote.
Losing K_master significa perder o acesso a todos os objetos e chaves, e por isso a exportação é indispensável.
K_master → produzindo master_backup_blob.obj_keys do servidor — estão no pacote das próprias chaves do proprietário — e volta a cifrá-los localmente.K_master, por isso a frase-passe é obrigatória.Add Crypto.kt: um invólucro AES-256-GCM, geração e armazenamento de K_master,
auxiliares para K_obj, serialização do formato v1:<base64url>.
Testes unitários: cifrar → decifrar → comparar.
Para o servidor ainda não vai nada — a canalização está apenas pronta.
Todos os campos sensíveis mudaram-se para numbers.data_cipher e user_keys.bundle_cipher.
As antigas colunas em claro (phone, label, radius, time_*, geo_*, wifi_*, share_*, has_avatar, security_level, user_keys.label, hosts.label, pending_actions.phone, hosts.last_ip) foram removidas da base (fase 7).
webhook_url e webhook_secret ficam em claro SÓ quando webhook_mode='server' — sem eles o servidor não consegue enviar o pedido HTTP. Com webhook_mode='phone' são cifrados em data_cipher.
Ao criar ou atualizar, o cliente cifra o JSON e envia o data_cipher. Ao sincronizar lê o blob e decifra-o. Os objetos novos vivem inteiramente no blob; os antigos sem K_obj continuam a usar os campos em claro por retrocompatibilidade.
Criar uma chave gera K_guest, monta o pacote e envia bundle_cipherpara o servidor.
A ligação leva a chave no fragmento; a página /key lê o hash e passa a chave à aplicação.
Um ecrã de definições com Exportar chave-mestra: frase-passe → Argon2id → código QR. Restauro pelo leitor, mais testes de mudança entre dispositivos. Não implementado.
Uma rotina única no cliente no primeiro arranque da nova versão:
K_obj para cada um deles.data_cipher.phone=NULL, …) — mas só após confirmação.Uma auditoria de segurança, externa ou nossa. Verificamos que o servidor realmente não vê dados sensíveis: um despejo da base não deve conter nada além de blobs.
| Risk | Measure |
|---|---|
| Perda de K_master | Um fluxo de cópia de segurança obrigatório (fase 5). Enquanto não houver cópia, mostra-se um aviso. |
| Comprometimento do dispositivo do proprietário | Não é totalmente evitável: a entrada na aplicação é protegida por PIN ou biometria, com EncryptedSharedPreferences e o Android Keystore. |
| Rodar a chave de um objeto obriga a reconstruir todos os pacotes | O cliente do proprietário pega em todos os bundle_cipher, locais ou do servidor, reconstrói-os e envia-os num só lote. É uma operação rara. |
| O servidor adultera um pacote | A etiqueta AES-GCM não confere e o cliente avisa de uma chave danificada. |
| Fuga de K_guest por uma captura de ecrã do código QR | Tecnicamente inevitável: a chave está no próprio código. Mostre o código QR apenas a um convidado de confiança. |
| O fragmento do endereço fica no histórico do navegador | A página /key chama, logo após ler o hash, history.replaceState(..., '#'), o que apaga o fragmento. |
Última atualização: 19 de abril de 2026