Español

Codificación, escape y hash

Base64 no es cifrado, btoa no es UTF-8, encodeURI no es encodeURIComponent y SHA-256 no es un hash de contraseña. Esto es lo que realmente hace cada uno de ellos y los errores específicos que se derivan de asumir lo contrario.

Codificar no es cifrado y no es compresión.

La codificación cambia la forma en que se escriben los datos. El cifrado cambia quién puede leerlo. La compresión cambia la cantidad de espacio que ocupa. Estos son tres trabajos diferentes, y base64 solo hace el primero, mal si esperabas cualquiera de los otros dos.

Base64 toma tres bytes a la vez y los reescribe como cuatro caracteres extraídos de un alfabeto de símbolos 64. Cuatro caracteres con tres bytes significan que la salida siempre es aproximadamente un 33% mayor que la entrada, más el relleno. Existe porque una gran parte de la infraestructura (encabezados de correo electrónico, encabezados HTTP, valores de cadenas JSON, URL, atributos XML) fue diseñada para texto y destruye o rechaza bytes arbitrarios. Base64 es el adaptador que te permite enviar bytes a través de una tubería con forma de texto.

Cualquiera puede revertirlo instantáneamente, sin clave, porque no hay clave. Si utiliza una contraseña base64, la ha publicado en un formato levemente inconveniente. Esto es importante porque la salida de base64 parece codificada para el ojo humano, que es exactamente la propiedad que hace que la gente confíe en ella para cosas que no puede hacer.

Por qué se rompe btoa() y las dos formas diferentes en que se rompe

El navegador ofrece btoa() y atob(), y son más antiguas que las API de texto modernas. btoa se define sobre "cadenas binarias": cadenas en las que cada unidad de código es un solo byte, desde 0 hasta 255. El texto no es eso.

El primer fracaso es ruidoso. Llame a btoa("世界") y obtendrá un InvalidCharacterError, porque U+4E16 no cabe en un byte. Los fallos ruidosos son buenos: los notas inmediatamente y buscas una solución.

El segundo fallo es silencioso, y es el que llega a producción. El carácter é es U+00E9, que cabe en un byte. Entonces btoa("café") regresa felizmente, codificando é como un solo byte 0xE9. Pero é en UTF-8 tiene dos bytes, 0xC3 0xA9. El base64 que acaba de producir decodifica, en todos los demás sistemas del mundo, algo que no es su texto. Semanas más tarde descubrirás cuando un nombre en una base de datos se ha convertido en un carácter de reemplazo.

La solución es dejar de tratar el texto como bytes y convertirlo explícitamente. TextEncoder produce el UTF-8 bytes; codificarlos. TextDecoder vuelve a convertir bytes en texto, y al construirlo con { fatal: true } hace que se generen secuencias no válidas en lugar de sustituir silenciosamente U+FFFD, por lo que una decodificación que no puede ser correcta falla en lugar de devolver tonterías que parecen plausibles. Esa es la canalización que utiliza este kit de herramientas, razón por la cual los emoji, que combinan marcas y guiones de derecha a izquierda, son exactamente de ida y vuelta.

  1. Convierta texto a bytes con TextEncoder; nunca indexe en la cadena.
  2. Codifique los bytes en base64.
  3. Para revertir: decodifique base64 en bytes, luego decodifique los bytes como UTF-8 con fatal: true.
  4. Si el paso UTF-8 falla, la carga útil es binaria, no de texto. Muéstralo como maleficio en lugar de fingir.

base64 versus base64url y la pregunta de relleno

El estándar base64 usa + y / como sus dos últimos símbolos. Ambos son significativos en las URL: + se puede leer como un espacio codificado en cadenas de consulta y / es un separador de ruta. Entonces, RFC 4648 define un segundo alfabeto, base64url, que sustituye - y _ en su lugar. Los JWT lo utilizan, al igual que la mayoría de los formatos de tokens y muchas API.

El relleno es la otra variable. Almohadillas base64 estándar con =, por lo que la longitud de salida es siempre múltiplo de cuatro. base64url generalmente elimina el relleno, porque = es en sí mismo un carácter incómodo en una URL y la longitud se puede recuperar aritméticamente. Un decodificador que insiste en el relleno rechazará segmentos JWT perfectamente válidos.

Consejo práctico: su decodificador debe aceptar ambos alfabetos y tolerar la falta de relleno, porque rara vez controla lo que le entregan. Su codificador debe ser explícito sobre lo que emite, porque al destinatario probablemente sí le importe. La utilidad base64 aquí hace exactamente eso: acepta cualquier cosa razonable y le permite elegir exactamente lo que produce.

encodeURI y encodeURIComponent: la diferencia en una oración

Ambos codifican por ciento usando UTF-8. Sólo se diferencian en qué caracteres dejan en paz, y esa diferencia es toda la historia: encodeURIComponent escapa a los delimitadores reservados, encodeURI no.

Los delimitadores reservados son los caracteres que dan estructura a una URL: : /? # [ ] @ ! $&'()*+,; =. encodeURI supone que le entregó una URL que ya está correctamente estructurada y debe permanecer así, por lo que las conserva; no convertirá https:// en https%3A%2F%2F. encodeURIComponent supone que le entregó una pieza que se colocará en una ranura, por lo que se escapa, asegurando que la pieza no pueda salir de su ranura.

El error que esto produce es completamente mecánico. Tome un valor de búsqueda de a&b=c. Codifíquelo con encodeURI y agréguelo como ?q=a&b=c, y habrá creado silenciosamente dos parámetros: q ahora es solo "a", y ha aparecido un b=c perdido. Codifíquelo con encodeURIComponent y obtendrá ?q=a%26b%3Dc, un parámetro, valor correcto. La misma clase de error permite que un valor diseñado inyecte parámetros en una URL que crea su código, razón por la cual "usar el formulario del componente para los valores" es una regla de seguridad, no solo de corrección.

La codificación de formularios es una tercera regla que se parece a la segunda. application/x-www-form-urlencoded escribe un espacio como + en lugar de %20. Si decodifica el cuerpo de un formulario con decodeURIComponent simple, cada signo más en los datos se convierte en un espacio. Cada cuadro de búsqueda que alguna vez ha transformado "C++" en "C " es este error.

Entidades HTML y por qué decodificarlas con InnerHTML es un mal hábito

El escape para HTML es limitado y está bien definido: & se convierte en &amp;, < en &lt;, > en &gt; y, dentro de los valores de atributos, también hay que escapar " y '. Cinco caracteres. Escapar más —convertir cada letra acentuada en una entidad con nombre— era una solución para los tiempos de codificaciones de caracteres inciertas y hoy es un estilo opcional, no una medida de seguridad.

La decodificación es donde vive el mal hábito. El truco de una línea que aparece en cada respuesta es asignar la cadena al HTML interno de un elemento separado y volver a leer su contenido de texto. Funciona y es una mala idea. Ha entregado información que no es de confianza al analizador HTML, que construye nodos DOM reales a partir de ella. Un <img src=x onerror=...> en esa cadena se convierte en un elemento de imagen real con un controlador de errores real adjunto; si ese subárbol alguna vez se inserta en el documento, se ejecuta. También destruye silenciosamente sus datos: las etiquetas en la entrada desaparecen en lugar de ida y vuelta, porque el analizador las interpretó como marcas en lugar de texto.

Decodificar entidades correctamente no necesita ningún analizador: haga coincidir la referencia, busque el nombre en una tabla o haga la aritmética para una referencia numérica. Son unas pocas docenas de líneas, no puede ejecutar nada y realiza ida y vuelta fielmente. Este kit de herramientas lo hace de esa manera, por lo que al pegar una etiqueta de secuencia de comandos en el descodificador de entidades se muestra una etiqueta de secuencia de comandos.

Elegir un hash y las tres preguntas que lo deciden

Un hash criptográfico convierte cualquier entrada en un resumen de longitud fija, de modo que encontrar dos entradas con el mismo resumen no debería ser factible. Esa propiedad es la que permite que un resumen reemplace los datos: en una firma, una verificación de integridad o una dirección de contenido.

Primera pregunta: ¿estás protegiendo contra un accidente o contra un adversario? Una suma de comprobación que protege contra una descarga corrupta sólo necesita detectar cambios aleatorios; CRC32 está bien. Un resumen de que un atacante podría beneficiarse de la colisión necesita un hash que aún esté en pie. Esa distinción es la razón por la cual SHA-1 no es simplemente "old".

SHA-1 está roto, concretamente. En 2017, el trabajo SHAttered produjo dos archivos PDF diferentes con el mismo resumen SHA-1. En 2020, "SHA-1 is a Shambles" demostró una colisión de prefijo elegido, la variante más fuerte y mucho más peligrosa, porque permite a un atacante colisionar dos documentos significativamente diferentes en lugar de dos blobs cuidadosamente construidos. Si la seguridad de un sistema se basa en SHA-1 resistencia a colisiones, esa seguridad desaparece. SHA-1 permanece en este kit de herramientas porque los identificadores de objetos git y una larga lista de firmas de API heredadas todavía lo usan, y es necesario poder reproducir esos valores. Reproducir un valor no es lo mismo que confiar en él.

Segunda pregunta: ¿la entrada es una contraseña? Si es así, ninguna de estas es la respuesta. SHA-256 está diseñado para ser rápido, y rápido es precisamente incorrecto para las contraseñas: significa que un atacante con su base de datos puede intentar miles de millones de conjeturas por segundo. Las contraseñas necesitan una función deliberadamente lenta y que requiera mucha memoria con un valor por usuario: Argon2id, scrypt o bcrypt. Esto no es un matiz; El uso de SHA-256 para contraseñas es el error de hash grave más común.

Tercera pregunta: ¿necesita un resumen con clave? Si está autenticando un mensaje en lugar de tomarle huellas digitales, desea HMAC, no un hash simple. Concatenar un secreto y mezclarlo es un autogol clásico contra ataques de extensión de longitud; HMAC existe porque esa construcción es más difícil de hacer bien de lo que parece.

Para todo lo demás (toma de huellas digitales de un archivo, una dirección de contenido, un atributo de integridad), SHA-256 es el valor predeterminado sensato y SHA-512 suele ser más rápido en hardware de 64 bits y, al mismo tiempo, ofrece un resumen más amplio.

¿Por qué los hashes aquí provienen del navegador?

Los resúmenes de este kit de herramientas se calculan mediante SubtleCrypto, la implementación Web Crypto del navegador, no mediante JavaScript enviado desde este sitio. Se trata de una elección deliberada: la implementación del navegador se audita, se mantiene y, por lo general, se ejecuta como código nativo optimizado. Un SHA-256 escrito a mano en un paquete de páginas es más código en el que confiar sin ningún beneficio.

Tiene una consecuencia visible. Web Crypto solo se expone en un contexto seguro, es decir, https:// o localhost. Abra esta página a través de HTTP simple en una dirección LAN y crypto.subtle no estará definido, por lo que la utilidad hash se lo dirá claramente en lugar de fallar silenciosamente o sustituir algo más débil.

El mismo razonamiento impulsa el generador UUID. crypto.randomUUID() también es solo de contexto seguro, por lo que cuando no está disponible, el kit de herramientas recurre a crypto.getRandomValues(), que sigue siendo la misma fuente criptográficamente segura, y establece la versión y los bits de variante. Lo que nunca hará es recurrir a Math.random(). Se trata de un PRNG rápido y no criptográfico cuyo estado interno se puede recuperar con una breve ejecución de sus resultados, y los identificadores tienen la desafortunada costumbre de convertirse en claves de sesión y enlaces de restablecimiento de contraseña. Si no existe una fuente segura, esta herramienta no genera nada y dice por qué.

¿Qué pasa con lo que pegas?

  • Cada conversión, hash, decodificación y diferenciación se ejecuta en la pestaña de su navegador. Ninguna entrada se carga, registra o almacena en un servidor, porque no hay ningún servidor involucrado una vez que la página se ha cargado.
  • Los hashes provienen de la implementación Web Crypto del propio navegador y los UUID de su generador aleatorio criptográficamente seguro. Ninguno de los dos implica una llamada de red.
  • Nada de lo que escribe se escribe en el almacenamiento local o en una cookie. Al recargar la página se descarta; cerrar la pestaña la descarta.
  • La analítica de todo el sitio se ejecutan únicamente en el host de producción canónico configurado y se describen en la Política de Privacidad; Los hosts locales y de vista previa lo rechazan. Los valores, tokens, URL y contenidos de archivos pegados están excluidos de los eventos analíticos propios de ToolAcre. La publicidad está deshabilitada en la configuración actual.
  • Dicho esto: una clave JWT o API es una credencial activa. El hábito seguro es nunca pegar uno en una página web que usted no escribió, por muy confiables que sean sus afirmaciones, incluida ésta.

Preguntas

¿Base64 es una forma de ocultar datos?

No. Es una representación de texto reversible sin clave, descodificable por cualquiera en una fracción de segundo. Hace que los datos sobrevivan a los canales de sólo texto; no lo hace secreto. Cualquier cosa realmente sensible necesita cifrado, y el resultado cifrado a menudo se codifica en base64 para su transporte, lo cual es la fuente de la confusión.

¿Por qué mi base64 es más larga que la entrada?

Debido a que cuatro caracteres de salida transportan tres bytes de entrada, la salida tiene aproximadamente el tamaño 4/3, más hasta dos caracteres de relleno. Eso es inherente al formato. Si el tamaño importa, comprima antes de codificar, nunca después, porque la salida base64 se comprime mal.

¿Qué función de codificación de URL debo utilizar?

Utilice encodeURIComponent para cualquier pieza que esté insertando en una URL: un valor de consulta, un segmento de ruta, un fragmento. Utilice encodeURI solo cuando tenga una URL completa y ya estructurada que simplemente contenga espacios o que no sea ASCII. Si está creando una cadena de consulta, prefiera URLSearchParams, que aplica la regla correcta y maneja la diferencia de espacio como plus.

¿Por qué mi decodificador arroja "URI con formato incorrecto"?

Porque un % en la entrada no va seguido de dos dígitos hexadecimales. Por lo general, el texto contiene un signo de porcentaje literal: "50% de descuento", que nunca se codificó. Un porcentaje literal debe escribirse como %25. La utilidad URL aquí informa la posición exacta del escape infractor en lugar de simplemente rechazarlo.

¿Puedo usar SHA-256 para almacenar contraseñas?

No. SHA-256 es rápido por diseño, lo que significa que un atacante que roba su base de datos puede probar miles de millones de contraseñas candidatas por segundo en hardware básico. Las contraseñas necesitan una función salada, lenta y con mucha memoria: Argon2id, scrypt o bcrypt. Este es el error grave más común en este ámbito.

¿Por qué SHA-1 sigue aquí si está roto?

Porque aún necesita reproducir los valores SHA-1 que ya existen: identificadores de objetos git, huellas digitales de certificados TLS antiguos, firmas de solicitudes de API heredadas. Ser capaz de calcular un valor para la interoperabilidad es diferente a confiar en él por motivos de seguridad. Cada lugar donde aparece SHA-1 en este kit de herramientas está etiquetado en consecuencia.

¿Por qué dos herramientas dan hashes diferentes para el mismo texto?

Casi siempre hay una diferencia en los bytes, no en el algoritmo. Los culpables habituales son una nueva línea final (un archivo termina con una; un cuadro de texto puede no), una codificación de texto diferente o finales de línea CRLF versus LF. Esta herramienta codifica el UTF-8 bytes de exactamente lo que usted escribió y le muestra el recuento de bytes, lo que generalmente hace que la discrepancia sea obvia.

Limitaciones

  • La tabla de entidades HTML con nombre cubre el subconjunto práctico (caracteres críticos para el marcado, tipografía, moneda, flechas, matemáticas, griego y latín-1), no todas las referencias con nombre HTML5 2,231. Los nombres no reconocidos se informan y se dejan exactamente como están escritos en lugar de adivinarlos.
  • La decodificación de entidades requiere el punto y coma final. HTML5 tolera un puñado de referencias heredadas sin una, pero decodificarlas correctamente depende del contexto de marcado circundante, que una herramienta de texto independiente no tiene.
  • La generación de hash y UUID necesita un contexto seguro (https:// o localhost) porque Web Crypto no está expuesto de otra manera. La herramienta informa esto en lugar de sustituir una implementación más débil.
  • Solo SHA-1, SHA-256, SHA-384 y SHA-512 están disponibles, porque eso es lo que implementa SubtleCrypto. MD5 está ausente tanto por elección como por necesidad.
  • Aquí no hay HMAC, ni derivación de claves ni cifrado. Estos necesitan administración de claves, que no es algo que deba manejar una página que encontró en Internet.
  • Todo está limitado por la memoria de su dispositivo, ya que todo se ejecuta en una pestaña del navegador. Las entradas tienen un límite (unos pocos megabytes por utilidad) y la herramienta rechaza el trabajo de gran tamaño en lugar de congelarse.