You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/annex/recovery.md

14 KiB

Cómo abrir una cápsula DateKeys sin software de DateKeys

Este texto acompaña a una cápsula del tiempo de DateKeys, un fichero .dkc: dice cómo abrirla, llegada su fecha, sin ningún software de DateKeys, por si ya no existe. Es el anexo informativo §79 de la especificación del protocolo DateKeys v0.16, cuyo texto tiene el SHA-256 807d4fe85ac09ad6f97abc75ab3e2156bb2f3fb0dc589777f4420627fad545e1. Es el mismo para toda cápsula: no lleva ningún dato de esta. Su licencia es CC BY-ND 4.0, Atribución-SinDerivadas 4.0 Internacional (https://creativecommons.org/licenses/by-nd/4.0/deed.es): se puede copiar y compartir sin cambios, citando su origen.

79. Anexo informativo: recuperación sin software DateKeys

Este anexo no es normativo. Dice cómo abrir una cápsula de Quicknet sin ningún software de DateKeys, por si dentro de décadas no existe. Repite lo que fijan las secciones que cita, que deciden en caso de duda.

La regla 27 de §62.1 recomienda al SDK oficial guardar este anexo junto al .dkc. No contiene ningún dato de una cápsula.

Hace falta:

  • el .dkc;
  • el release de su ronda, de cualquier fuente: un relay de drand, un archivo de releases, un servicio de caché (§50) o cualquier copia. No hace falta confiar en quien lo da: se verifica con la clave pública de 79.1 (79.3);
  • en time_and_key, una credencial: la .dkk, la identity age de un recipient o las palabras de una llave de palabras (§38.1);
  • una librería de BLS12-381 con pairing y con el hash a G1 de RFC 9380, SHA-256, HMAC-SHA256, HKDF-SHA256 (RFC 5869), ChaCha20-Poly1305 (RFC 8439), un decodificador de CBOR y la herramienta age (§77) o una librería compatible;
  • con una llave de palabras, PBKDF2-HMAC-SHA256 (RFC 8018) y, si las palabras llevan otras letras que las de 79.7, UnicodeData.txt de Unicode 18.0.0.

No sirven las herramientas de drand: tle pide el release a la red y no acepta uno dado, y age no acepta una file key, que es lo que da el stanza tlock (79.4). Por eso este anexo describe esos dos pasos enteros (79.4 y 79.5).

La implementación de referencia lo sigue en scripts/recovery, un programa que no importa ningún paquete de DateKeys, tlock ni drand: solo la librería estándar de Go, golang.org/x/crypto, filippo.io/age y la librería BLS12-381 drand/kyber-bls12381, con las interfaces de drand/kyber. scripts/recovery_check.sh abre con él una cápsula time_only, otra time_and_key con su .dkk, otra con una llave de palabras y otra cuyo PAYLOAD_AGE acaba en un bloque completo, de los fixtures oficiales. Las palabras se le dan en un fichero de texto, y UnicodeData.txt, si hace falta, en otro.

79.1 Parámetros de Quicknet

Son los de §12, y pueden no estar ya en ningún otro sitio:

chain_hash   52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971
clave pública (G2, 96 bytes)
             83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c
             8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb
             5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a
genesis_time 1692803367 (segundos Unix de la ronda 1)
period       3 segundos
round_time(r) = genesis_time + (r − 1)·3
q            0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001
DST          BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_

Los puntos se codifican comprimidos, en el formato de ZCash (§12.2): 48 bytes en G1, la firma, y 96 en G2, la clave pública y U, con la coordenada c1 antes que c0.

79.2 La cápsula

El .dkc empieza por 16 bytes (§22):

0   4  "DKC1"
4   1  VERSION: el formato, 1, 2 o 3
5   1  0
6   2  0
8   4  PUBLIC_HEADER_LEN, entero big-endian
12  4  SEALED_CONTROL_LEN, entero big-endian

Le siguen PUBLIC_HEADER, de PUBLIC_HEADER_LEN bytes; SEALED_CONTROL, de SEALED_CONTROL_LEN bytes; y PAYLOAD_AGE, desde el byte 16 + PUBLIC_HEADER_LEN + SEALED_CONTROL_LEN hasta el final del fichero.

PUBLIC_HEADER es un mapa CBOR (§24). Su clave 2 es capsule_id, 16 bytes; su clave 4, la política, 0 para time_only y 1 para time_and_key; y su clave 3, la DateKey, un texto dk1_ seguido del Base64URL sin relleno de un JSON (§18):

{"version":1,"network":"datekeys:quicknet:v1","round":1000}

round es la ronda de la cápsula.

79.3 El release

Un objeto release es un mapa CBOR (§47.1): la clave 0 es "datekeys-release"; la 2, el chain_hash, que ha de ser el de 79.1; la 3, la ronda, que ha de ser la de la DateKey; y la 4, la firma, de 48 bytes. Así lo sirven la Release API y un servicio de caché. En un archivo de releases (§50), la firma de la ronda r son los 48 bytes que empiezan en |cabecera| + (r − primera ronda)·48, y 48 ceros si el archivo no la tiene. Un relay de drand la entrega como JSON, {"round": …, "signature": "…"}, con la firma en hexadecimal. Hoy se pide así, aunque las direcciones pueden cambiar:

GET https://api.drand.sh/v2/chains/<chain_hash>/rounds/<ronda>

La firma no necesita confianza: se verifica (§63, paso 10). Con M el SHA-256 de la ronda en 8 bytes big-endian, y H el hash a G1 de RFC 9380 con la suite BLS12381G1_XMD:SHA-256_SSWU_RO_ y el DST de 79.1:

e(H(M), clave_pública) == e(firma, G2)

con e el pairing de G1 × G2, el primer argumento en G1 y el segundo en G2, y G2 el generador de G2. La firma y la clave pública se decodifican como puntos comprimidos válidos del subgrupo, distintos del punto en el infinito. Para una ronda solo hay una firma válida: cualquier copia que verifique es el release.

79.4 El stanza tlock y FK_TIME

SEALED_CONTROL es un fichero age (79.5) cuya cabecera tiene un solo stanza:

-> tlock <ronda en decimal> <chain_hash en hexadecimal en minúsculas>
<cuerpo en Base64 estándar sin relleno, en líneas de 64 caracteres>

El cuerpo mide 128 bytes, U ‖ V ‖ W: U, de 96 bytes, un punto de G2, y V y W, de 16 bytes. Con la firma del release (§63, paso 11):

sigma   = V XOR H2(e(firma, U))
FK_TIME = W XOR H4(sigma)
r       = H3(sigma, FK_TIME)
comprobar r·G2 == U
  • H2(x) son los 16 primeros bytes de SHA-256(IBE-H2 ‖ x), con x los 576 bytes del elemento de GT en el orden de §63, «Serialización de GT en H2»: c1 antes que c0 en cada nivel de la torre, y c2, c1, c0 en Fp6, cada elemento de Fp en 48 bytes big-endian. Una librería que serializa con c0 primero da otro H2. El vector de testdata/vectors/tlock_ibe.json lo comprueba: H2(e(G1, G2)) = cb87319f24560b5231579a09ad79f12e, con G1 y G2 los generadores.
  • H4(sigma) son los 16 primeros bytes de SHA-256(IBE-H4 ‖ sigma).
  • H3(sigma, FK_TIME): base = SHA-256(IBE-H3 ‖ sigma ‖ FK_TIME); para i = 1, 2, … hasta 65534, d = SHA-256(uint16_le(i) ‖ base), con el contador en 2 bytes little-endian delante de base; se desplaza un bit a la derecha el primer byte de d, solo ese byte; y si d, como entero big-endian de 32 bytes, es menor que q, r = d.

Las etiquetas son los bytes ASCII, sin longitud ni terminador. FK_TIME, de 16 bytes, es la file key del fichero age de SEALED_CONTROL. testdata/vectors/tlock_steps.json da cada valor intermedio de cinco stanzas.

79.5 Abrir un fichero age con su file key

Es la especificación age v1 de C2SP (§77), resumida. Un fichero age es una cabecera de texto y un payload binario:

age-encryption.org/v1
-> <tipo> <argumentos…>
<cuerpo del stanza en Base64 sin relleno, líneas de 64 caracteres, la última más corta, quizá vacía>
--- <MAC en Base64 sin relleno, 43 caracteres>
<payload>

Con la file key FK, de 16 bytes:

  1. La cabecera: clave_mac = HKDF-SHA256(ikm = FK, salt = vacío, info = header), 32 bytes. El MAC es HMAC-SHA256(clave_mac, la cabecera desde age-encryption.org/v1 hasta --- inclusive, sin el espacio que lo sigue). Si no coincide con el de la línea ---, la file key o la cabecera son otras.
  2. El payload empieza tras el salto de línea del MAC por un nonce de 16 bytes. clave = HKDF-SHA256(ikm = FK, salt = nonce, info = payload), 32 bytes.
  3. Lo demás son bloques de ChaCha20-Poly1305 de 65 536 bytes de texto, 65 552 cifrados; el último puede ser más corto, o estar completo: un plaintext de 65 536 bytes es un solo bloque, completo y marcado como último. El nonce de 12 bytes del bloque n, desde 0, es n en 11 bytes big-endian seguido de 0x01 en el último bloque y de 0x00 en los demás. No hay datos asociados. El último bloque solo puede estar vacío si es el único, y nada sigue al último bloque.

79.6 Las capas siguientes

El plaintext de SEALED_CONTROL es:

  • en time_only, CONTROL_CBOR;
  • en time_and_key, otro fichero age, INNER_ACCESS_AGE, con stanzas X25519, uno por credencial y señuelos hasta 16 en los formatos 2 y 3. Se abre con age -d -i clave.txt, con la identity de la credencial en clave.txt. La de una .dkk es su access_material. La .dkk empieza por 12 bytes, DKK1, 01, 00, 00 00 y BODY_LEN en 4 bytes big-endian (§40), y le sigue un mapa CBOR cuya clave 5 es ese access_material, 32 bytes (§41), y cuya clave 3 es el capsule_id de su cápsula. Una llave de palabras da la identity con 79.7.

CONTROL_CBOR es un mapa CBOR (§31). Su clave 3 es I_PAYLOAD, otra identity X25519 de 32 bytes, y en los formatos 2 y 3 su clave 6 es L, una cadena de 8 bytes con un entero big-endian, no un entero CBOR. Con I_PAYLOAD, age -d -i payload.txt abre PAYLOAD_AGE.

Una identity X25519 de 32 bytes se escribe para age en Bech32 (BIP 173, §77), no Bech32m: el prefijo age-secret-key-, los 32 bytes reagrupados de 8 en 5 bits con ceros al final, que dan 52 caracteres, y la suma de comprobación de BIP 173, calculada con el prefijo en minúsculas. Después, todo en mayúsculas: AGE-SECRET-KEY-1….

79.7 La llave de palabras

Unas palabras dan la identity X25519 de una credencial (§38.1):

P  = las palabras normalizadas, en UTF-8, separadas por un espacio (0x20)
S  = "DateKeys llave de palabras v2|" || chain_hash || "|" || ronda || "|" || capsule_id
id = PBKDF2-HMAC-SHA256(P, S, 600000 iteraciones, 32 bytes)      ; RFC 8018

En S, chain_hash es el de 79.1 y capsule_id el de 79.2, en hexadecimal en minúsculas, y la ronda, la de la DateKey en decimal, sin ceros a la izquierda. id es la identity, que se escribe para age como dice 79.6.

Normalización sin tablas. Si el texto solo lleva caracteres ASCII imprimibles (U+0021 a U+007E), los espacios U+0009 a U+000D y U+0020, las letras á, é, í, ó, ú, ü y ñ y sus mayúsculas, y marcas de U+0300 a U+036F, que es lo que da cualquier palabra de las listas de DateKeys, las palabras normalizadas salen así:

  1. se quitan las marcas de U+0300 a U+036F;
  2. á y Á pasan a a; é y É, a e; í e Í, a i; ó y Ó, a o; ú, ü, Ú y Ü, a u; ñ y Ñ, a n;
  3. A a Z pasan a a a z;
  4. se parte el texto por los espacios, sin palabras vacías.

El resto se queda: la puntuación y las cifras cuentan, y «perro,» no es «perro».

Normalización completa. Para cualquier otro texto, en este orden: la NFD de UAX #15, con la descomposición canónica de cada punto de código (el campo 5 de UnicodeData.txt, sin las que llevan una etiqueta entre < y >, aplicada hasta el final), la de las sílabas Hangul, que se calcula, y la reordenación canónica por la clase de combinación (campo 3); se quitan los puntos de código de U+0300 a U+036F; cada punto de código pasa a su minúscula simple (campo 13), si la tiene; y se parte por los espacios U+0009 a U+000D, U+0020, U+0085, U+00A0, U+1680, U+2000 a U+200A, U+2028, U+2029, U+202F, U+205F y U+3000, sin palabras vacías. Sirve cualquier copia de UnicodeData.txt de Unicode 18.0.0 cuyo SHA-256 sea 0736451de439ae7baf1425136617da495e09ee5afbe6e394374db7009ea08950; unicode.org la publica en https://www.unicode.org/Public/18.0.0/ucd/UnicodeData.txt. Otra versión de Unicode puede dar otras palabras: una que asigne un punto de código nuevo, o que cambie una descomposición o una minúscula.

Vectores, con el chain hash de Quicknet, la ronda 1000 y capsule_id = 000102030405060708090a0b0c0d0e0f:

  • «perro luna casa verde tren mar» da id = fceec4d8ca8de86c85a1f26ed49f82a2b38431bd0ce36db995ae7dfd49b96e41;
  • el texto «Ñandú», dos espacios, «PINGÜINO», un tabulador (U+0009) y «camión árbol Éter ola» da «nandu pinguino camion arbol eter ola» e id = 273295d29370126a3be50b743132718d3cd9137fb3bb4cb20aa23163d2e19bb7, lo mismo que ese texto con las tildes, la diéresis y la tilde de la eñe escritas como marcas sueltas detrás de su letra (U+0301, U+0308 y U+0303).

79.8 El contenido

El plaintext de PAYLOAD_AGE es:

  • en formato 1, el contenido entero;
  • en formato 2, el contenido en sus L primeros bytes, seguido de ceros;
  • en formato 3, BODY en sus L primeros bytes, seguido de ceros (§29.2).

BODY empieza por tres enteros de 4 bytes big-endian: AREA_LEN, SECURITY_LEN y HEAD_LEN. Siguen el área de security, de AREA_LEN bytes, que se puede saltar: solo da los veredictos de la firma y del sello (§29.7); el head, un mapa CBOR de HEAD_LEN bytes que empieza en 12 + AREA_LEN; y los ficheros, desde 12 + AREA_LEN + HEAD_LEN, el origen de sus desplazamientos.

La clave 5 del head es la lista de ficheros (§29.4). Cada uno es un mapa:

0 → ruta, con "/" entre carpetas
1 → tamaño
2 → start
3 → end
4 → SHA-256 de sus bytes
5 → mtime, en segundos Unix, opcional

Sus bytes van de start a end, sin incluir end, contados desde el origen. Las claves 3 y 4 del head son el comentario y el autor declarado: textos del creador que no prueban nada (§29.7). Una ruta que saldría de la carpeta de destino no se escribe.

79.9 Lo que el anexo no comprueba

Este anexo comprueba lo que decide que el resultado es el correcto: la firma del release, r·G2 == U, los MAC de cada fichero age y el SHA-256 de cada fichero. No comprueba, entre otras cosas, la codificación canónica de cada objeto, header_binding (§26), los 16 stanzas de INNER_ACCESS_AGE (§39), los ceros del relleno (§29.1) ni las reglas de las rutas (§29.5). Una cápsula que el lector de §63 rechazaría puede abrirse siguiendo este anexo; su contenido es el que sellaron las MAC de age, pero no tiene la garantía de un lector conforme.

Powered by TurnKey Linux.