2 puntos por GN⁺ 2026-01-01 | 1 comentarios | Compartir por WhatsApp
  • Se descubrió un error de validación incorrecta de puntos en la curva Edwards25519 en la función de bajo nivel crypto_core_ed25519_is_valid_point() de libsodium
  • Esta función debe comprobar que un punto pertenezca al grupo criptográfico principal, pero deja pasar por error algunos puntos que forman parte de subgrupos de orden mixto (subgroup)
  • La causa fue un error de código en la validación interna de coordenadas: solo verificaba X=0 y omitía la comprobación de Y=Z, por lo que algunos puntos inválidos podían tratarse como válidos
  • En la versión corregida, se cambió para comprobar ambas condiciones (X=0 y Y=Z), y están afectadas las versiones 1.0.20 o anteriores o los lanzamientos previos al 30 de diciembre de 2025
  • La API de alto nivel (crypto_sign_*) no se ve afectada, y se recomienda usar el grupo Ristretto255 por seguridad y rendimiento

Resumen del proyecto libsodium

  • libsodium es un proyecto iniciado hace 13 años cuyo objetivo es ofrecer una API simple para facilitar el uso de criptografía
    • Está diseñado para que el usuario pueda realizar operaciones de alto nivel sin necesidad de conocer los algoritmos internos
  • Da mucha importancia a mantener la compatibilidad de la API, y ha conservado esa consistencia hasta hoy basándose en la API de NaCl
  • Como algunos usuarios empezaron a usar directamente funciones de bajo nivel más allá de las limitaciones indicadas en la documentación, aumentaron los casos en los que la librería se usa como si fuera un toolkit criptográfico

Causa del bug encontrado

  • Función afectada: crypto_core_ed25519_is_valid_point()
    • En la curva Edwards25519, debería rechazar los puntos que no pertenecen al grupo principal (de orden L)
    • Sin embargo, algunos puntos de orden mixto (2L, 4L, 8L, etc.) pasaban la validación
  • Internamente, para verificar el orden del punto, multiplica por L y luego comprueba si el resultado es el elemento identidad (identity)
    • El elemento identidad se representa como X=0, Y=Z, pero el código anterior solo comprobaba X=0
    • Por eso, algunos puntos inválidos con Y≠Z se trataban como válidos
  • Ejemplo: si al punto Q del grupo principal se le suma el punto de orden 2 (0, -1), entonces Q+(0, -1) es un punto inválido, pero antes de la corrección pasaba la validación

Contenido de la corrección

  • El commit del parche cambió lo siguiente
    • Código anterior: return fe25519_iszero(pl.X);
    • Código corregido: fe25519_sub(t, pl.Y, pl.Z); return fe25519_iszero(pl.X) & fe25519_iszero(t);
  • Ahora se comprueban ambas condiciones, X=0 y Y=Z, para realizar la validación correctamente

Alcance del impacto

  • Puede haber impacto si se cumple alguna de estas condiciones
    • Se usa la versión 1.0.20 o anterior o un lanzamiento previo al 30 de diciembre de 2025
    • Se valida un punto de entrada no confiable con crypto_core_ed25519_is_valid_point()
    • El usuario implementa directamente operaciones sobre la curva Edwards25519
  • Sin embargo, la mayoría de los usuarios no se ven afectados
    • La API de alto nivel (crypto_sign_*) no usa esa función
    • crypto_scalarmult_ed25519 no filtra información incluso con una clave pública inválida
    • Las claves generadas con crypto_sign_keypair y crypto_sign_seed_keypair pertenecen al grupo correcto

Medidas recomendadas

  • Se recomienda usar el grupo Ristretto255
    • Está incluido en libsodium desde 2019 y resuelve los problemas relacionados con el cofactor
    • Los puntos decodificados son seguros automáticamente, sin necesidad de validación adicional
    • Ofrece mejor rendimiento en las operaciones que Edwards25519
  • Si no es posible actualizar, se puede validar usando la función alternativa a nivel de aplicación (is_on_main_subgroup) propuesta

Distribuciones corregidas y soporte

  • El problema se corrigió de inmediato tras ser descubierto y está incluido en todas las versiones estables distribuidas después del 30 de diciembre de 2025
    • Incluye el tarball oficial, binarios para Visual Studio/MingW, paquete NuGet, compilaciones para Android, el xcframework de swift-sodium, Rust libsodium-sys-stable y libsodium.js
  • También está previsto un nuevo point release
  • El proyecto se mantiene por una sola persona, y se puede apoyar su mejora continua mediante patrocinios en OpenCollective

1 comentarios

 
GN⁺ 2026-01-01
Comentarios en Hacker News
  • La biblioteca de PHP sodium_compat también se vio afectada por este problema
    Se puede revisar información relacionada en security-advisories PR #756
    Esta noche planeo revisar todas las demás implementaciones de Ed25519 dentro del ecosistema open source para confirmar si tienen la misma omisión en la validación

    • Encontré que en algunas bibliotecas falta por completo la lógica de validación
      Pero no hubo casos implementados de forma incorrecta como la vulnerabilidad mencionada arriba
      Si no les envié un correo, lo más probable es que no estén en la lista de despliegues de Ed25519 de ianix, que se me hayan pasado por alto, o que sean implementaciones seguras
    • Quiero expresar mi agradecimiento por contribuir al open source
  • Llevo los últimos 4 meses desarrollando bindings de sodium para Lean4
    Ya llegué a la etapa de Ristretto255, y ahora entiendo por qué al autor le entusiasma tanto esta tecnología
    Ristretto es una API sofisticada para construir polinomios arbitrarios sobre Curve25519, y ha sido realmente divertido experimentar con ella
    Si el autor llega a ver esta publicación, quiero darle las gracias sinceramente

    • Me pregunto si existe un repositorio público de este proyecto
  • El objetivo de Libsodium era ofrecer una API de alto nivel, no funciones de bajo nivel
    Fue diseñado para que los usuarios no tuvieran que conocer los algoritmos internos, pero con el tiempo fueron aumentando los casos en que se usan directamente funciones de bajo nivel
    Al final, Libsodium terminó usándose como una especie de toolkit de algoritmos
    Lo importante es reconocer hacia dónde quieren ir los usuarios y no forzar el proyecto a una sola forma de uso
    Algunos proyectos se vuelven dogmáticos en este punto y fracasan

    • Es una observación interesante. Pero, por otro lado, lo que crees que quieren los usuarios y lo que realmente necesitan pueden ser cosas distintas
      Es peligroso que personas no expertas usen directamente primitivas criptográficas
      Libsodium fue diseñado para evitar que los usuarios se pusieran en riesgo por sí mismos
      Lo ideal es que la biblioteca haga imposible el uso incorrecto
      Recomiendo este texto relacionado: “If You're Typing The Letters A-E-S Into Your Code, You're Doing It Wrong”
    • Si se considera cada función interna como parte del contrato de la API, casi cualquier cambio termina siendo una ruptura de compatibilidad
      Por eso muchas veces es correcto limitar ciertas funciones como private o internal
      No estoy seguro de si Libsodium definió bien esa frontera, pero el equilibrio es importante
    • Hace tiempo hice un framework de pruebas en C++ llamado CeeFIT, y estaba orgulloso de registrar fixtures en tiempo de compilación
      Pero algunos usuarios lo usaban como si fuera un ejecutor por lotes
      Corregí algunos bugs para soportar sus necesidades
      Al final, me alegró simplemente que hubiera usuarios
  • Este bug es un error de validación criptográfica sutil pero importante
    La verificación aparentemente simple de “comprobar si es válido” en realidad es muy compleja
    Permitir puntos fuera del subgrupo de orden primo puede no parecer una vulnerabilidad inmediata, pero puede romper supuestos en capas superiores
    Además, las primitivas de bajo nivel se reutilizan mucho más ampliamente de lo previsto, así que una pequeña omisión de validación puede tener un impacto enorme

    • Sin embargo, X25519 y Ed25519 fueron diseñados precisamente para no requerir este tipo de validación
      Los problemas de subgrupos solo aparecen cuando se construyen protocolos más complejos sobre Curve25519
      Por eso yo remapeo, siempre que puedo, todos los puntos al subgrupo de orden primo
      Monocypher tiene este tipo de funciones avanzadas
      Por ejemplo, funciones como crypto_x25519_dirty_fast() o crypto_elligator_map()
      Estas funciones “dirty” generan claves públicas que cubren toda la curva, haciéndolas indistinguibles de la aleatoriedad
      Después, incluso en un intercambio de claves X25519, se puede obtener el mismo secreto compartido
      Esto es gracias al diseño de DJB, donde incluso si la clave pública es anómala, el secreto compartido se remapea al subgrupo de orden primo
      En última instancia, Ristretto solo es necesario cuando ese remapeo no es posible
      Claro, la abstracción de grupo de orden primo es útil, pero alguien capaz de diseñar ese tipo de protocolos también debería poder manejar un cofactor no trivial
  • Si trabajas en una gran empresa, te recomendaría considerar apoyar a Frank a nivel corporativo

    • Trabajo en Apple, pero no sé quién es Frank ni conozco el proceso para apoyarlo
      Y aunque lo supiera, probablemente tendría que apoyarlo con mi propio dinero, no con fondos de la empresa
  • Me pregunto si libnacl también está afectado
    Uso software compilado con libnacl todos los días, pero ninguno compilado con “libsodium”

  • Es una biblioteca realmente excelente
    Quiero expresar mi agradecimiento a Frank Denis