Skip to content
← Todas las publicaciones
· Delta1 Labs KeyrightLicenciamientoSeguridad

Elegir un algoritmo de firma para tus claves de licencia: Ed25519, ECDSA y RSA comparados

Una clave de licencia es un payload firmado, y el algoritmo de firma que eliges no es un detalle intercambiable: decide cuán larga es la clave que tu cliente tiene que teclear, cuán rápido la verifica cada arranque y si un solo error en tu código de emisión puede filtrar la clave privada. Las firmas RSA-2048 ocupan 256 bytes, lo que convierte una clave tecleable en un muro de base32; ECDSA P-256 ocupa un tercio de eso pero lleva un nonce que, reutilizado, entrega tu clave a un atacante; Ed25519 ocupa 64 bytes, es determinista y no tiene nonce que estropear. Este artículo sopesa los tres en los ejes que de verdad importan para el licenciamiento offline-first — tamaño de firma y de clave pública, coste de verificación en un arranque en frío, determinismo y modos de fallo, y qué aspecto tiene cada uno en System.Security.Cryptography — y explica por qué Keyright firma con Ed25519 por defecto.

Una clave de licencia de Keyright no es una contraseña ni un token aleatorio. Es un documento breve — el cliente, el nivel, el número de asientos, la caducidad — envuelto en una firma digital que tu aplicación verifica sin conexión contra una clave pública que lleva incorporada. La firma es toda la historia de seguridad: es la razón por la que un cliente no puede cambiar «Standard» por «Pro» en una clave y que siga validando, y por la que nadie puede acuñar una clave que tú no emitiste.

Lo que plantea una pregunta que suena a detalle de implementación y no lo es: ¿qué algoritmo de firma? La respuesta honesta es que esta elección se propaga a tres cosas que tus clientes y tu equipo de soporte sienten directamente: cuán larga es la clave, cuán rápido la verifica cada arranque y cuánto daño puede hacer un único bug en tu código de emisión. Este artículo sopesa las tres opciones realistas — RSA, ECDSA y Ed25519 — exactamente en esos ejes, y explica por qué Keyright firma con Ed25519 por defecto.

Qué acaba realmente dentro de la clave

Empieza por la forma de la clave, porque es donde aterriza el argumento del tamaño. Una clave de licencia es el payload canónico más una firma sobre él, codificados en caracteres que un humano puede teclear:

key = base32( canonical_payload_bytes || signature_bytes )

El payload es pequeño y más o menos fijo: unas pocas decenas de bytes de claims. La firma es la variable, y para la mayoría de algoritmos es el elemento individual más grande de la clave. Así que la longitud bruta en bytes de una firma es, en primera aproximación, la longitud de tu clave. Esa es la lente con la que leer la siguiente sección: cada byte de firma se convierte en alrededor de 1,6 caracteres base32 que tu cliente tiene que teclear o pegar sin una errata.

Tamaño de firma (azul) y de clave pública (verde), en bytesEd255196432ECDSA P-25664 (r‖s en bruto)33 (comprimida)RSA-2048256294 (SPKI)RSA-30723840~150~384 bytes

RSA: funciona, y tiene la forma equivocada para una clave

RSA es el algoritmo al que todo el mundo recurre primero, porque es el que aprendieron. Para firmar licencias es defendible y a la vez el que peor encaja de los tres.

El problema de tamaño es inmediato. Una firma RSA-2048 ocupa exactamente 256 bytes — el tamaño del módulo — por pequeño que sea tu payload. Codifícalo en base32 y habrás añadido unos 410 caracteres a la clave antes de los claims. Una clave así de larga no es algo que un cliente teclee; es algo que pega, y si alguna vez hay que leerla en voz alta, imprimirla en una tarjeta o introducirla en un dispositivo sin portapapeles, ya has perdido. Llevar RSA al nivel de seguridad de 128 bits que alcanzan las opciones de curva elíptica significa RSA-3072, cuya firma ocupa 384 bytes, lo que solo empeora la clave.

RSA tiene una fortaleza genuina que conviene nombrar con honestidad: la verificación es rápida, porque usa un exponente público diminuto (65537), así que la comprobación en el cliente — la operación que ocurre en cada arranque — es barata. Pero ECDSA y Ed25519 también verifican en mucho menos de un milisegundo, así que esta ventaja no te compra nada que un usuario pueda percibir, y viene unida a una firma cuatro veces mayor.

En .NET el lado de verificación tiene este aspecto:

using var rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(publicKeyDer, out _);

bool ok = rsa.VerifyData(
    payload, signature,
    HashAlgorithmName.SHA256,
    RSASignaturePadding.Pkcs1);

No hay nada malo en ese código. El problema son los 256 bytes que están en signature, y que ninguna astucia de codificación los hace más pequeños.

ECDSA: el tamaño correcto, con un nonce que puedes estropear

El DSA de curva elíptica sobre la curva NIST P-256 te da 128 bits de seguridad — comparable a RSA-3072 — con una firma en bruto de solo 64 bytes (dos enteros de 32 bytes, r y s). Es una cuarta parte del tamaño de RSA-2048 para una clave más fuerte, y está integrado directamente en System.Security.Cryptography en todas las versiones soportadas de .NET, lo que lo convierte en la elección obvia si tu restricción es «solo algoritmos integrados»:

using var ecdsa = ECDsa.Create();
ecdsa.ImportSubjectPublicKeyInfo(publicKeyDer, out _);

// Keyright stores the raw r‖s form to keep the key short; .NET verifies it directly.
bool ok = ecdsa.VerifyData(
    payload, signature,
    HashAlgorithmName.SHA256,
    DSASignatureFormat.IeeeP1363FixedFieldConcatenation);

Fíjate en ese último argumento. Las firmas ECDSA tienen dos codificaciones: la concatenación compacta de 64 bytes IEEE P1363 de r y s, y la forma ASN.1 DER, que las envuelve en una SEQUENCE de dos INTEGER y ocupa entre 70 y 72 bytes con longitud variable (las reglas sobre ceros iniciales hacen que las firmas DER difieran en tamaño de una firma a otra). Para una clave de licencia quieres la forma fija de 64 bytes; una firma DER de longitud variable hace que la longitud de tu clave oscile y complica el análisis de anchura fija. Equivocarse aquí es un bug de interoperabilidad habitual: un firmante que emite DER y un verificador que espera P1363 rechazará todas las firmas válidas.

El problema más profundo de ECDSA no es el tamaño, es el nonce. Cada firma ECDSA consume un valor secreto por firma k. Si k se reutiliza alguna vez en dos mensajes distintos, o es siquiera ligeramente predecible, la clave privada sale de las dos firmas con unas pocas líneas de álgebra. No es una hipótesis: es el mecanismo exacto detrás de varias extracciones de claves famosas. El ECDsa de .NET genera k correctamente y lo mantiene interno, así que firmar dentro del framework es seguro. Pero en cuanto tu ruta de emisión involucra código personalizado, otro lenguaje o un RNG prestado — y un backend de licenciamiento a menudo crece exactamente así — ECDSA está a una fuente de aleatoriedad débil de filtrar la clave que protege todo tu producto. El algoritmo te entrega una pistola cargada y confía en que la mantengas apuntando lejos.

Ed25519: pequeño, rápido y sin nada que estropear

Ed25519 — EdDSA sobre la curva de Edwards derivada de Curve25519 — se diseñó con las lecciones de RSA y ECDSA incorporadas, y para claves de licencia gana en todos los ejes que importan.

Los tamaños son los mejores de los tres: una firma de 64 bytes y una clave pública de 32 bytes, con 128 bits de seguridad. La clave pública también importa, porque es el valor que tu aplicación incrusta y (si lo endureces) fija; un ancla de 32 bytes es mucho más fácil de tomar como huella y proteger que el SubjectPublicKeyInfo de ~294 bytes de RSA. Su clave pública DER empieza por el prefijo fijo MCowBQYDK2Vw en base64, una señal inequívoca de que estás mirando una clave Ed25519.

Fundamentalmente, Ed25519 es determinista. El nonce por firma se deriva calculando un hash de la clave privada junto con el mensaje, así que es único por mensaje sin necesitar ninguna aleatoriedad en el momento de firmar. No hay k que reutilizar, ni RNG que configurar mal, ni sesgo de nonce que filtre la clave: el filo más afilado de ECDSA simplemente no existe. Firmar el mismo payload dos veces produce una salida idéntica byte a byte, lo que además hace que la emisión sea idempotente y los fixtures de prueba estables. La verificación es en tiempo constante por construcción, evitando una clase de problemas de temporización sobre los que de otro modo tendrías que razonar.

La única pega práctica en .NET: Ed25519 no se expone a través de la superficie integrada de System.Security.Cryptography como RSA y ECDsa, así que la verificación pasa por una implementación incluida en lugar de por la BCL. Desde el punto de vista del integrador eso es invisible: el SDK de Keyright verifica por ti:

var verifier = new LicenseVerifier(new LicenseVerifierOptions
{
    // 32-byte Ed25519 public key, base64 SubjectPublicKeyInfo — safe to ship, worth pinning.
    PublicKeyBase64 = "MCowBQYDK2VwAyEA9mF3rQ2pxK8vN1cWbYh6Zt0uQ4sJ7eD3rL5nX8kH2A=",
});

LicenseResult result = verifier.Verify(licenseKey);
if (result.IsValid && result.Claims.Tier == "Pro")
{
    // honor the entitlements
}

La firma de 64 bytes mantiene la clave corta, el diseño determinista mantiene tu lado emisor fuera del campo de minas del nonce, y la llamada de verificación es una operación de menos de un milisegundo que puedes ejecutar en cada arranque sin un servidor en el camino.

Cómo quedan los tres en los ejes que importan

RSA-2048ECDSA P-256Ed25519Firma256 bytes64 bytes64 bytesSeguridad~112 bits128 bits128 bitsLongitudno tecleablecortacortaDeterminismosí (relleno)no (k aleatorio)sí (por diseño)Peligro nonceningunoreúso filtra claveninguno

Lee la tabla tal y como la experimenta un sistema de licenciamiento. La fila que decide si tu clave es utilizable siquiera es la longitud de la clave, y elimina a RSA para cualquier cosa que toque un humano. La fila que decide si un único mal día en tu código de emisión te cuesta todo el producto es el peligro del nonce, y separa a Ed25519 de ECDSA. En las dos filas donde RSA es competitivo — determinismo y velocidad de verificación — las opciones de curva elíptica lo igualan o lo superan de todos modos. No hay eje en el que RSA-2048 sea la respuesta correcta para claves de licencia, y el único eje en el que ECDSA supera a Ed25519 es «ya viene integrado en todos los targets antiguos».

Qué significa esto en la práctica

Si empiezas desde cero, firma con Ed25519. Produce las claves más cortas, alcanza un nivel de seguridad de 128 bits, verifica lo bastante rápido como para ejecutarse en cada arranque y — la parte que más importa cuando tu ruta de firma crece más allá de una función ordenada — no tiene nonce por firma que filtre la clave privada. Por eso es el valor por defecto de Keyright: el SDK incrusta una clave pública de 32 bytes que puedes fijar, verifica la firma de 64 bytes sin conexión y nunca pide a tu lado emisor que aporte una aleatoriedad que podría generar mal.

Recurre a ECDSA P-256 solo cuando una restricción dura te obligue a algoritmos integrados en un target donde Ed25519 no esté disponible — y si lo haces, fija la codificación IEEE P1363 de 64 bytes, deja que el framework genere el nonce y nunca dejes que un firmante personalizado se acerque a k. Recurre a RSA solo cuando interoperes con un sistema que ya lo exige, y acepta que tus claves serán solo para pegar.

El algoritmo de firma es el cimiento sobre el que se sostiene todo el esquema de licenciamiento. Elige el que sea lo bastante pequeño para teclearse, lo bastante fuerte para durar y con una forma tal que el camino más fácil a través de tu propio código sea también el seguro.

Prueba Nebula.NET

Endurece tu código .NET en minutos — empieza con la edición gratuita.