Saltar al contenido principal

Almacenamiento de claves y autenticación

BitTool Signer guarda la frase de recuperación de cada billetera (y la passphrase BIP39, si se configuró una) en su propio archivo cifrado. Para abrirlo se necesitan dos claves independientes: una derivada de la contraseña de la billetera y otra que iOS guarda solo en ese dispositivo.

En Android

Esta página describe la app de iPhone. BitTool Signer para Android usa las mismas dos capas con el almacenamiento de claves propio de Android; consulta En Android.

La bóveda​

Cada billetera es un archivo Vaults/<UUID>.bsvault en el directorio privado Library/Application Support/BitToolSigner/ de la app. Se crea así:

  1. Se genera una sal nueva de 32 bytes con SecRandomCopyBytes.
  2. Se deriva una clave de contraseña de 256 bits: PBKDF2-HMAC-SHA256, 600,000 iteraciones (CommonCrypto).
  3. Se cifra el secreto (frase + passphrase, como JSON) con AES-256-GCM usando la clave de contraseña: nonce nuevo de 96 bits, tag de 128 bits.
  4. Se vuelve a cifrar ese texto cifrado con AES-256-GCM usando una clave de dispositivo aleatoria de 256 bits.
  5. Se guarda la clave de dispositivo en el Keychain con kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly y la sincronización con iCloud desactivada. Si el teléfono no tiene código de acceso, la creación de la billetera falla; no hay una alternativa más débil.
  6. Se escribe el archivo de forma atómica, con protección completa de archivos, permisos solo para el propietario y exclusión de los respaldos.
vault file
├── header (not secret, but authenticated)
│ format version 2 · KDF parameters · salt · wallet ID · name · created at
└── AES-GCM(device key,
AES-GCM(PBKDF2-SHA256(password, salt, 600 000),
{ mnemonic, passphrase }))

Ambas capas AES-GCM autentican los bytes del encabezado almacenado como datos asociados, precedidos por una etiqueta de dominio (com.bittool.signer.vault.v2/<layer>). Editar, volver a codificar o reordenar cualquier byte del encabezado hace que el descifrado falle.

La contraseña de la billetera cifra la bóveda local. No es la passphrase BIP39 y no tiene ningún efecto en las direcciones. Reglas: de 8 a 64 caracteres, con al menos tres de estos tipos: mayúsculas, minúsculas, dígitos y símbolos; sin caracteres de control; se usa exactamente como se escribe (sin recortar espacios ni normalizar).

La bóveda no es un respaldo

La clave del dispositivo nunca sale del teléfono y se excluye de los respaldos, así que un archivo de bóveda copiado no se puede abrir en otro lugar. Perder el teléfono, eliminar la app o quitar el código de acceso puede dejar la bóveda ilegible. La frase de recuperación escrita (y la passphrase) es el único respaldo.

¿Por qué PBKDF2 y no Argon2id?​

Argon2id exige mucha memoria (memory-hard) y sería una KDF de contraseñas más robusta. Esta versión usa el PBKDF2 de CommonCrypto (con la recomendación actual de OWASP de 600,000 iteraciones para HMAC-SHA256) para no agregar una dependencia criptográfica nativa. Los parámetros de la KDF están en el encabezado versionado, así que una migración futura no necesita un nuevo formato de archivo.

La revisión de seguridad de Opus 5 calificó esta decisión como Informativa (BTS-OP5-03) y no como una debilidad: gracias a la segunda capa con la clave del dispositivo, alguien que copie solo el archivo de la bóveda ni siquiera puede empezar a buscar la contraseña. La revisión sugiere Argon2id para un formato de bóveda futuro, como defensa en profundidad en caso de que la clave del dispositivo también se vea comprometida.

Intentos de contraseña​

Los intentos fallidos de contraseña se cuentan por billetera en un archivo .attempts cifrado y autenticado. El contador se guarda antes de la derivación de clave lenta, así que forzar el cierre de la app no lo reinicia.

Intentos fallidosEspera antes del siguiente intento
1–4ninguna
52 s
64 s
nmin(2n−4 s, 5 min)

Una contraseña correcta reinicia el contador. La app nunca elimina una billetera por contraseñas incorrectas. Se trata de un control de la aplicación, no de un contador de hardware: alguien que pueda revertir los archivos de la app puede reiniciarlo. La protección criptográfica es la KDF.

Datos públicos sin la contraseña​

Las direcciones, los códigos QR de emparejamiento y la actividad están disponibles sin la contraseña, porque solo necesitan claves públicas. Cuando se crea o se importa una billetera, el motor deriva una sola vez un índice público y lo guarda fuera de la bóveda:

  • seis nodos de cuenta de Bitcoin (BIP84, BIP86, BIP49, BIP44, y BIP48 multisig nativo y anidado)
  • los nodos de cuenta de EVM y TRON, además de las direcciones de TRON 0–99
  • las tres claves públicas de Solana (consulta Rutas de derivación)

El archivo del índice público lleva un HMAC-SHA256 calculado con otra clave aleatoria del Keychain. Si no pasa la verificación (alguien lo editó o pertenece a otra billetera), la app lo trata como inexistente y pide la contraseña una vez para reconstruirlo. Por lo tanto, un atacante que pueda escribir en los archivos de la app no puede sustituir las direcciones por las suyas.

Autorización​

  • No existe un estado "desbloqueado" ni una sesión global. Cada tarea de firma pide la contraseña de esa billetera, y lo mismo ocurre al mostrar la frase de recuperación, cambiar la contraseña y eliminar la billetera.
  • App Lock (bloqueo de la app, opcional, con Face ID) protege solo el acceso a la app. Nunca autoriza una firma. Con App Lock activado, la app se bloquea al iniciarse en frío y después de 120 segundos en segundo plano, medidos con un reloj que sigue contando mientras el teléfono está en reposo.
  • Cuando la app pasa a segundo plano, descarta de inmediato cualquier contraseña ingresada, confirmación de riesgo y frase de recuperación mostrada. Una cubierta de privacidad oculta la pantalla en el selector de apps y durante la grabación de pantalla.

Higiene de memoria​

La semilla BIP39 se calcula en código nativo y los búferes temporales se sobrescriben con memset_s. Cada operación que toca un secreto o una entrada no confiable se ejecuta en un contexto nuevo de JavaScriptCore que luego se descarta. Esto acorta el tiempo de vida de los secretos en memoria, pero no se puede garantizar que las cadenas de Swift o de JavaScript se borren, así que la app no afirma que el borrado (zeroization) sea completo.

En Android​

La bóveda tiene las mismas dos capas y el mismo encabezado autenticado. Lo que cambia es dónde vive la clave del dispositivo y cómo protege la plataforma a la app.

  • Capa de la contraseña. PBKDF2-HMAC-SHA256 con 600.000 iteraciones, calculado en código nativo (con las instrucciones SHA-256 de ARMv8 si el procesador las tiene), y después AES-256-GCM con un nonce nuevo de 12 bytes y una sal nueva de 32 bytes en cada cifrado.
  • Capa del dispositivo. Una clave AES-GCM por cartera, generada dentro del Keystore de Android, que nunca se puede exportar, en StrongBox si el teléfono lo tiene. La bóveda exige un bloqueo de pantalla.
  • Archivos. En el almacenamiento privado de la app, escritos de forma atómica y excluidos de las copias de seguridad en la nube y de la transferencia entre dispositivos.
  • Intentos de contraseña. Se cuentan por cartera antes de la derivación lenta, como en iPhone; un contador dañado rechaza todos los intentos.
  • Autorización. Cada firma necesita la contraseña de la cartera. El bloqueo de la app (opcional, huella o rostro) nunca autoriza una. Ninguna ventana aparece en capturas, grabaciones de pantalla ni en Recientes, las superposiciones de otras apps se ocultan en Android 12 y posteriores, y el botón Firmar y la confirmación del riesgo rechazan los toques mientras otra ventana los cubre.
  • Memoria. Los secretos se guardan en contenedores que se cierran y se borran tras su uso, también cuando se cancela una operación, y cada operación que toca un secreto se ejecuta en un entorno QuickJS nuevo. La frase de recuperación se escribe en el teclado propio de la app, que ningún método de entrada ve. La JVM no puede garantizar que se borre cada copia, así que aquí tampoco la app afirma un borrado completo.

La revisión de Opus 5.5 incluye dos hallazgos de severidad baja sobre esta capa: la clave que autentica los datos públicos de las carteras puede regenerarse tras un error del Keystore en Android 8–11 (BTS-13), y dos claves auxiliares no comprueban si hay bloqueo de pantalla (BTS-14). Consulta Revisiones de seguridad.