Todo lo que debes saber sobre criptografía de clave pública – claves RSA

Apuesto a que has creado al menos una vez un par de claves RSA. Generalmente porque necesitabas conectarte a GitHub y querías evitar escribir tu contraseña cada vez que iniciabas sesión.

Seguramente seguiste diligentemente la documentación sobre cómo crear claves SSH y después de unos minutos tu configuración se completó.

¿Pero sabes lo que hiciste realmente?

¿Sabes lo que el archivo ~/.ssh/id_rsa contiene realmente? ¿Por qué ssh creó dos archivos con un formato tan diferente? ¿Notaste que un archivo comienza con ssh-rsa, mientras que el otro comienza con —–BEGIN RSA PRIVATE KEY—–?

¿Has notado que a veces el encabezado del segundo archivo pierde la parte RSA y solo dice BEGIN PRIVATE KEY?

Creo que un nivel mínimo de conocimiento con respecto a los diversos formatos de claves RSA es obligatorio para cada desarrollador hoy en día. Esto sin mencionar la importancia de comprenderlos profundamente si deseas seguir una carrera en el mundo de la administración de infraestructura.

Algoritmo RSA y pares de claves

Desde la invención de la criptografía de clave pública, se han diseñado varios sistemas para crear el par de claves. Uno de los primeros es RSA, la creación de tres criptógrafos brillantes, que data de 1977.

La historia de RSA es bastante interesante, ya que fue inventada por un matemático inglés, Clifford Cocks. Quién, sin embargo, se vio obligado a mantenerlo en secreto por la oficina de inteligencia británica para la que trabajaba.

Teniendo en cuenta que RSA no es sinónimo de criptografía de clave pública, sino solo una de las implementaciones posibles, quería escribir una publicación sobre él. Esto porque todavía es, más de 40 años después de su publicación, uno de los algoritmos más extendidos.

En particular, es el algoritmo estándar utilizado para generar pares de claves SSH. Dado que hoy en día cada desarrollador tiene su clave pública en GitHub, BitBucket o sistemas similares, podría decirse que RSA es bastante ubicuo.

Sin embargo, no cubriré las partes internas del algoritmo RSA en este artículo. Si estás interesado en los detalles sangrientos del framework matemático, puedes encontrar muchos recursos tanto en Internet como en libros de texto.

La teoría detrás de esto no es trivial, pero definitivamente vale la pena si quieres tomar en serio la parte matemática de la criptografía.

Creación de claves

En este artículo, exploraré dos formas de crear pares de claves RSA y los formatos utilizados para almacenarlos. La criptografía aplicada es, como muchos otros temas en informática, un objetivo en movimiento, y las herramientas cambian a menudo.

A veces es bastante fácil descubrir *cómo* hacer algo (StackOverflow ayuda), pero es menos fácil tener una idea clara de lo que está sucediendo.

Todos los ejemplos que se muestran en esta publicación usan una clave RSA de 2048 bits creada para este propósito. Por lo tanto, todos los números que ves provienen de un ejemplo real. La clave obviamente ha sido destruida después de que escribí el artículo.

El formato PEM

Comencemos la discusión sobre los pares de claves con el formato utilizado para almacenarlos. Hoy en día, el formato de almacenamiento más ampliamente aceptado se llama PEM (Privacy-enhanced Electronic Mail – correo electrónico con privacidad mejorada).

Como su nombre lo indica, este formato se creó inicialmente para el cifrado de correo electrónico. Empero luego se convirtió en un formato general para almacenar datos criptográficos como claves y certificados. Se describe en RFC 7468 (“Codificaciones textuales de estructuras PKIX, PKCS y CMS”).

Un ejemplo de clave privada en formato PEM es el siguiente:

Básicamente, puedes decir que estás tratando con un formato PEM del encabezado y pie de página típico que identifica el contenido. Mientras que los guiones y las dos palabras BEGIN y END siempre están presentes. La parte PRIVATE KEY describe el contenido y puede cambiar si el archivo PEM contiene algo diferente de una clave. Por ejemplo, un certificado X.509 para SSL.

El formato PEM especifica que el cuerpo del contenido (la parte entre el encabezado y el pie de página) se codifica utilizando Base64.

Si la clave privada se ha cifrado con una contraseña, el encabezado y el pie de página son diferentes:

Cuando el formato PEM se usa para almacenar claves criptográficas, el cuerpo del contenido está en un formato llamado PKCS #8. Inicialmente un estándar creado por una empresa privada (RSA Laboratories), se convirtió en un estándar de facto.  Por lo que se ha descrito en varios RFC, especialmente RFC 5208 (“Estándares de criptografía de clave pública (PKCS) # 8. Especificación de sintaxis de información de clave privada Versión 1.2”).

El formato PKCS #8 describe el contenido utilizando el lenguaje de descripción ASN.1 (Notación de sintaxis abstracta uno). También utiliza DER (Reglas de codificación distinguidas) relativas para serializar la estructura resultante. Esto significa que la decodificación Base64 del contenido devolverá algo de contenido binario que solo puede ser procesado por un analizador ASN.1.

Déjame recapitular visualmente la estructura:

Ten en cuenta que, debido a la estructura de la estructura ASN.1 subyacente, cada cuerpo PEM comienza con los caracteres MII.

OpenSSL y ASN.1

OpenSSL puede decodificar directamente una clave en formato PEM y mostrar la estructura ASN.1 subyacente con el módulo asn1parse:

Esto que ves en el fragmento de código es la clave privada en formato ASN.1. Recuerda que DER solo se usa para pasar de la representación de texto de ASN.1 a datos binarios. Por lo tanto, no lo vemos a menos que decodifiquemos el contenido de Base64 en un archivo y lo abramos con un editor binario.

Ten en cuenta que la estructura ASN.1 contiene el tipo del objeto (rsaEncryption, en este caso). Puedes decodificar más el campo OCTET STRING, que es la clave real, que especifica el desplazamiento:

Siendo esta una clave RSA, los campos representan componentes específicos del algoritmo. Encontramos en orden el módulo n = pq, el exponente público e, el exponente privado d, los dos números primos q y los valores d_pd_qq_inv (para el acelerador del teorema del resto chino).

Si la clave se ha cifrado, hay campos con información sobre el cifrado, y el campo OCTET STRING no se pueden analizar más a fondo debido al cifrado:

Claves OpenSSL y RSA

Otra forma de buscar una clave privada con OpenSSL es usar el módulo rsa. Mientras que el módulo asn1parse es un analizador ASN.1 genérico, el módulo rsa conoce la estructura de una clave RSA. Este puede generar correctamente los nombres de campo:

Los campos son los mismos que encontramos en la estructura ASN.1. Empero en esta representación tenemos una mejor vista de los valores específicos de la clave RSA. Puedes comparar los dos y ver que el valor de los campos es el mismo.

Si desea aprender algo sobre RSA, intenta investigar las razones históricas detrás de la elección de 65537 como un exponente público común (como puedes ver en la sección publicExponent aquí).

PKCS #8 vs PKCS #1

La primera versión del estándar PKCS (PKCS # 1) se diseñó específicamente para contener una clave RSA. Su definición ASN.1 se puede encontrar en RFC 8017 (“PKCS #1: RSA Cryptography Specifications Version 2.2”):

Posteriormente, a medida que aumentó la necesidad de describir nuevos tipos de algoritmos, se desarrolló el estándar PKCS #8. Esto puede contener diferentes tipos de claves y define un campo específico para el identificador de algoritmo. La definición ASN.1 se puede encontrar en RFC 5958 (“Asymmetric Key Packages”):

La definición del campo PrivateKey para el algoritmo RSA es el mismo utilizado en PKCS # 1.

Si el formato PEM usa PKCS # 8, su encabezado y pie de página son:

Sin embargo, si usas PKCS #1, debe haber una identificación externa del algoritmo, por lo que el encabezado y el pie de página son:

La estructura de PKCS #8 es la razón por la que tuvimos que analizar el campo en el desplazamiento 22. Esto para acceder a los parámetros de RSA al usar el módulo asn1parse de OpenSSL.

Si estás analizando una clave PKCS #1 en formato PEM, no necesitas este segundo paso.

Clave privada y pública

En el algoritmo RSA, la clave pública se construye utilizando el módulo y el exponente público. Esto significa que siempre podemos derivar la clave pública de la clave privada. OpenSSL puede hacer esto fácilmente con el módulo rsa, produciendo la clave pública en formato PEM:

Puedes recuperar la información en la clave pública especificando el indicador –pubin:

Generando pares de claves con OpenSSL

Si deseas generar una clave privada RSA, puedes hacerlo con OpenSSL:

Como OpenSSL es una colección de módulos, especificamos genpkey para generar una clave privada. La opción –algorithm especifica qué algoritmo queremos usar para generar la clave (RSA en este caso). -out especifica el nombre del archivo de salida y –pkeyopt nos permite establecer el valor para opciones clave específicas.

En este caso, la longitud de la clave RSA en bits.

Si deseas una clave cifrada, puedes generar una especificando el cifrado (por ejemplo: -aes-256-cbc):

Puedes ver la lista de cifrados compatibles con openssl list-cipher-algorithms. En ambos casos, puedes extraer la clave pública con el método mostrado anteriormente.

Las claves privadas de OpenSSL se crean utilizando PKCS #8, por lo que las claves no cifradas tendrán el formato:

y cifrados en el formulario:

Generando pares de claves con OpenSSH

Otra herramienta que puedes usar para generar pares de claves es ssh-keygen, que es una herramienta incluida en el conjunto SSH. Se usa específicamente para crear y administrar claves SSH. Como las claves SSH son claves asimétricas estándar, podemos usar la herramienta para crear claves para otros fines.

Para crear un par de claves simplemente debes ejecutar:

La opción -t especifica el algoritmo de generación de claves (RSA en este caso), mientras que la opción –b especifica la longitud de la clave en bits.

La opción –f establece el nombre del archivo de salida. Si no está presente, ssh-keygen te preguntará el nombre del archivo, ofreciendo guardarlo en el archivo predeterminado ~/.ssh/id_rsa.

La herramienta siempre solicita una contraseña para cifrar la clave, pero se te permite ingresar una vacía para omitir el cifrado.

Esta herramienta crea dos archivos. Uno es el archivo de clave privada, nombrado como lo solicitaste. El segundo es el archivo de clave pública, nombrado como el de clave privada, pero con una extensión .pub.

Las claves privadas de OpenSSH se generan utilizando el formato PKCS #1, por lo que la clave tendrá el formato:

El formato de clave pública OpenSSH

La clave pública guardada por ssh-keygen está escrita en el llamado formato SSH, que no es un estándar en el mundo de la criptografía. Tu estructura es <algorithm> <key> <comment>, donde la parte <key> del formato está codificado con Base64.

Por ejemplo:

Para decodificar manualmente la parte central de la clave, puedes ejecutar el siguiente código:

En el caso anterior genera algo como:

La estructura de este archivo binario es bastante simple y se describe en dos RFC diferentes. RFC 4253 (“SSH Transport Layer Protocol”) establece en la sección 6.6 que:

Mientras que la definición de los tipos stringmpint se pueden encontrar en RFC 4251 (“SSH Protocol Architecture”), sección 5:

Esto significa que la secuencia de bytes anterior se interpreta como 4 bytes de longitud (32 bits del tipo uint32). Esto seguido de ese número de bytes de contenido:

Ten en cuenta que, dado que creamos una clave de 2048 bits, deberíamos tener un módulo de 256 bytes. En cambio, esta clave usa 257 bytes con el prefijo del número con un byte 00 para evitar que se interprete como negativo. Formato de complemento a dos.

La estructura que se muestra arriba es la razón por la cual todas las claves SSH públicas de RSA comienzan con los mismos 12 caracteres AAAAB3NzaC1y.

Esta cadena, convertida en Base64 da los 9 bytes iniciales 00 00 00 07 73 73 68 2d 72. Los caracteres Base64 no son una asignación uno a uno de los bytes de origen.

Si el exponente es el estándar 65537, la clave comienza con:

Codificado da justamente 18 bytes:

Conversión entre formato PEM y OpenSSH

A menudo necesitamos convertir archivos creados con una herramienta a un formato diferente. Por lo tanto, esta es una lista de las conversiones más comunes que puedas necesitar.

Prefiero considerar el formato de clave en lugar de la herramienta de origen. Empero doy una breve descripción de la razón por la que debería realizar la conversión.

PEM/PKCS #1 a PEM/PKCS #8

Esto es útil para convertir las claves privadas de OpenSSH a un formato más nuevo:

OpenSSH público a PEM/PKCS #8

Para convertir claves públicas OpenSSH en un formato PEM adecuado (se imprime en stdout):

Esto es fácil de recordar porque -e significa exportación.

PEM/PKCS #8 a OpenSSH público

Si necesitas usar en SSH un par de claves creado con otro sistema:

Esto es fácil de recordar porque –i significa importación.

Lectura de claves RSA en Python

En Python puedes usar el paquete pycrypto para acceder a un archivo PEM que contiene una clave RSA con la función RSA.importKey. Ahora con suerte puedes entender la documentación que dice:

En la práctica, lo qué puedes hacer con un archivo private.pem es:

Y la variable key contendrá una instancia de _RSAobj (No es un nombre muy pitónico, para ser sincero). Esta instancia contiene los parámetros RSA como atributos como se indica en la documentación:

Conclusiones

Sigo encontrando en StackOverflow (y en otros sitios) mensajes de usuarios que están confundidos por las claves RSA. Esto es resultado de las diversas herramientas y las diferencias sutiles pero importantes entre los formatos. Por lo tanto, espero que esta publicación te haya ayudado a mejorar la comprensión del asunto.