Stan Drapkin. Trampas de criptografía de alto nivel en .NET

Stan Drapkin es un experto en seguridad y cumplimiento con más de 16 años de experiencia con .NET Framework (comenzando con .NET 1.0-beta en 2001). Desafortunadamente, él mismo no escribe artículos en ruso, por lo que acordamos con él publicar una traducción de su informe con DotNext Piter . ¡Este informe ganó el primer lugar en la conferencia!

Criptografía simétrica, criptografía elíptica moderna, asimétrica, híbrida, de alto nivel, bajo nivel, corriente y moderna. Cincuenta y seis minutos de video sobre criptografía, y mucho más rápido, en forma de texto.



Debajo del corte: videos, diapositivas y traducción. ¡Disfruta leyendo!


Diapositivas

Mi nombre es Stan Drapkin, soy el director técnico de una empresa especializada en seguridad de la información y cumplimiento normativo. Además, soy el autor de varias bibliotecas de código abierto, que son muy bien recibidas por la comunidad. ¿Cuántos oyeron hablar de Inferno ? Esta biblioteca muestra el enfoque correcto de la criptografía en .NET, y TinyORM implementa micro-ORM para .NET. Además, he escrito varios libros que pueden ser relevantes para el tema del artículo de hoy. Una de ellas, la edición de 2014, es "Security Driven .NET", la otra de 2017 es "Application Security in .NET, Succinctly".

Primero, hablaremos sobre lo que yo llamo las cuatro etapas de la iluminación criptográfica. Luego seguirán dos temas principales, en el primero hablaremos sobre criptografía simétrica, en el segundo, sobre asimétrico e híbrido. En la primera parte, comparamos la criptografía de alto y bajo nivel y echamos un vistazo a un ejemplo de criptografía de transmisión. En la segunda parte, tendremos muchas "aventuras" con RSA, después de lo cual nos familiarizaremos con la criptografía elíptica moderna.

Entonces, ¿cómo son estas etapas de la iluminación criptográfica? La primera etapa: "XOR es genial, mira, mamá, ¿cómo puedo?" Seguramente muchos de ustedes estén familiarizados con esta etapa y conozcan las maravillas de la función XOR. Pero, espero, la mayor parte de esta etapa ha crecido y se ha movido a la siguiente, es decir, aprendió a realizar el cifrado y descifrado utilizando AES (Advanced Encryption Standard), un algoritmo bien conocido y altamente considerado. La mayoría de los desarrolladores que no visitan DotNext están en esta etapa. Pero, dado que sigue DotNext y está familiarizado con los informes sobre los peligros de las API de bajo nivel, lo más probable es que se encuentre en la siguiente etapa: "Hice todo (a) incorrectamente, necesito cambiar a API de alto nivel". Bueno, para completar la imagen, también mencionaré la última etapa: el entendimiento de que con la mejor solución al problema, la criptografía puede no ser necesaria en absoluto. Esta etapa es la más difícil de alcanzar y hay pocas personas en ella. Un ejemplo es Peter G. Neumann, quien dijo lo siguiente: "Si crees que la solución a tu problema radica en la criptografía, entonces no entiendes cuál es exactamente tu problema".

El hecho de que la criptografía de bajo nivel es peligrosa se ha discutido en muchos informes sobre .NET. Puede consultar el informe de Vladimir Kochetkov en 2015, "Errores del sistema. Seguridad . Criptografía" . Su idea principal es que en cada etapa de trabajo con API criptográficas de bajo nivel, sin saberlo, tomamos muchas decisiones, para muchas de las cuales simplemente no tenemos el conocimiento adecuado. La conclusión principal es que, idealmente, se debe usar criptografía de alto nivel en lugar de criptografía de bajo nivel. Esta es una conclusión maravillosa, pero nos lleva a otro problema: ¿sabemos exactamente cómo debería ser la criptografía de alto nivel? Hablemos un poco al respecto.

Defina los atributos de una API criptográfica que no sea de alto nivel. Para empezar, una API de este tipo no dará la impresión de ser nativa de .NET, sino que se verá como un shell de bajo nivel. Además, dicha API será fácil de usar incorrectamente, es decir, No como debería. Además, te obligará a generar muchas cosas extrañas de bajo nivel: nonce, vectores de inicialización y similares. Dicha API lo obligará a tomar decisiones desagradables para las que quizás no esté preparado: elija algoritmos, modos de relleno, tamaños de clave, nonce, etc. Tampoco tendrá la API correcta para la transmisión (API de transmisión): hablaremos sobre cómo debería ser esta última.

En contraste, ¿cómo debería ser una API criptográfica de alto nivel? Creo que, en primer lugar, debe ser intuitivo y conciso tanto para el lector del código como para el escritor. Además, dicha API debería ser fácil de aprender y usar, y debería ser extremadamente difícil de aplicar de manera incorrecta. También debe ser poderoso, es decir, debe permitirnos alcanzar nuestro objetivo con un poco de esfuerzo, una pequeña cantidad de código. Finalmente, una API de este tipo no debe tener una larga lista de restricciones, precauciones, casos especiales, en general, debe haber un mínimo de cosas que deben recordarse al trabajar con ella, en otras palabras, debe caracterizarse por un bajo nivel de interferencia (baja fricción), debe solo trabaja sin ninguna reserva.

Habiendo tratado los requisitos para una API criptográfica de alto nivel para .NET, ¿cómo podemos encontrarla ahora? Puede probar solo google, pero eso sería demasiado primitivo: somos desarrolladores profesionales y este no es nuestro método. Por lo tanto, estamos investigando este problema y probando varias alternativas. Pero para esto necesitamos primero inventarnos la idea correcta de lo que es el cifrado autenticado, y para esto necesitamos entender los conceptos básicos. Son los siguientes: el texto sin formato P (texto sin formato), que convertiremos al texto cifrado C (texto cifrado) de la misma longitud utilizando alguna clave secreta K (clave). Como puede ver, hasta ahora estamos trabajando con un esquema muy simple. Además, también tenemos una etiqueta de autenticación T y nonce N. Un parámetro importante es N̅, es decir, reutilizar nonce con una clave. Como muchos de ustedes probablemente saben, conduce a una violación de la confidencialidad del texto, lo que obviamente es indeseable. Otro concepto importante es AD (datos asociados), es decir, datos asociados. Estos son datos opcionales que se autentican pero no participan en el cifrado y descifrado.



Habiendo entendido los conceptos básicos, echemos un vistazo a las diversas opciones de bibliotecas criptográficas para .NET. Comencemos con el análisis de Libsodium.NET. ¿Cuántos de ustedes la conocen? Como veo, algunos son familiares.

nonce = SecretAeadAes.GenerateNonce(); c = SecretAeadAes.Encrypt(p, nonce, key, ad); d = SecretAeadAes.Decrypt(c, nonce, key, ad); 

Aquí está el código C # con el que se realiza el cifrado con Libsodium.NET . A primera vista, es bastante simple y conciso: en la primera línea, se genera nonce, que luego se usa en la segunda línea, donde tiene lugar el cifrado, y en la tercera, donde se descifra el texto. Al parecer, ¿qué dificultades podría haber? Para empezar, Libsodium.NET ofrece no uno, sino tres métodos diferentes de cifrado simétrico:

Tiempos

 nonce = SecretAeadAes.GenerateNonce(); c = SecretAeadAes.Encrypt(p, nonce, key, ad); d = SecretAeadAes.Decrypt(c, nonce, key, ad); 

Dos

 nonce = SecretAead.GenerateNonce(); c = SecretAead.Encrypt(p, nonce, key, ad); d = SecretAead.Decrypt(c, nonce, key. ad); 

Tres

 nonce = SecretBox.GenerateNonce(); c = SecretBox.Create(p, nonce, key); d = SecretBox.Open(c, nonce, key); 

Obviamente, surge la pregunta: ¿cuál de ellos es mejor en su situación específica? Para responderlo, debe entrar en estos métodos, que haremos ahora.

El primer método, SecretAeadAes , utiliza AES-GCM con un nonce de 96 bits. Es importante que tenga una lista bastante larga de restricciones. Por ejemplo, al usarlo, no debe cifrar más de 550 gigabytes con una clave, y no debe haber más de 64 gigabytes en un mensaje con un máximo de 2 32 mensajes. Además, la biblioteca no advierte acerca de estas restricciones, debe realizar un seguimiento usted mismo, lo que crea una carga adicional para usted como desarrollador.

El segundo método, SecretAead , utiliza un conjunto de cifrado diferente, ChaCha20/Poly1305 con un nonce significativamente más pequeño de 64 bits. Una falta tan pequeña hace que las colisiones sean extremadamente probables, y solo por esta razón, no debe usar este método, excepto en casos bastante raros y siempre que esté muy versado en el tema.

Finalmente, el tercer método, SecretBox . Cabe señalar de inmediato que no hay datos asociados en los argumentos de esta API. Si necesita cifrado autenticado con AD, este método no es adecuado para usted. El algoritmo de cifrado utilizado aquí se llama xSalsa20/Poly1305 , nonce es lo suficientemente grande: 192 bits. Sin embargo, la falta de EA es una limitación significativa.

Cuando se usa Libsodium.NET , surgen algunas preguntas. Por ejemplo, ¿qué deberíamos hacer exactamente con el nonce generado por la primera línea de código en los ejemplos anteriores? La biblioteca no nos dice nada sobre esto, tenemos que resolverlo por nuestra cuenta. Lo más probable es que agreguemos manualmente este nonce al principio o al final del texto cifrado. Además, podríamos tener la impresión de que AD en los primeros dos métodos puede ser de cualquier longitud. Pero, de hecho, la biblioteca admite AD no más de 16 bytes de largo; después de todo, 16 bytes serán suficientes para todos, ¿verdad? Sigamos adelante. ¿Qué sucede con los errores de descifrado? En esta biblioteca, se decidió en estos casos lanzar excepciones. Si en su entorno durante el descifrado se puede violar la integridad de los datos, tendrá muchas excepciones que deberán manejarse. ¿Qué sucede si el tamaño de su clave no es exactamente 32 bytes? La biblioteca no nos dice nada sobre esto, estos son sus problemas que no le interesan. Otro tema importante es la reutilización de las matrices de bytes para reducir la carga en el recolector de basura en escenarios intensivos. Por ejemplo, en el código vimos una matriz que nos devolvió el generador nonce. Me gustaría no crear un nuevo búfer cada vez, sino reutilizar el existente. Esto no es posible en esta biblioteca, una matriz de bytes se regenerará cada vez.

Usando el esquema que ya hemos visto, intentaremos comparar varios algoritmos de Libsodium.NET .



El primer algoritmo, AES-GCM, utiliza una longitud de 96 bits (columna amarilla en la imagen). Tiene menos de 128 bits, lo que crea algunas molestias, pero no demasiado significativas. La siguiente columna es azul, este es el lugar ocupado por la etiqueta de autenticación, con AES-GCM es de 16 bytes o 128 bits. El segundo dígito azul, entre paréntesis, significa la cantidad de entropía, o aleatoriedad, contenida en esta etiqueta, menos de 128 bits. Cuánto menos: en este algoritmo depende de la cantidad de datos cifrados. Cuanto más encriptado, más débil es la etiqueta. Esto solo debería generar dudas sobre este algoritmo, que solo aumentará si miramos la columna blanca. Dice que las repeticiones (colisiones) de nonce conducirán a la falsificación de todos los textos cifrados creados por la misma clave. Si fuera de, por ejemplo, 100 de sus textos cifrados creados por una clave común en dos, hay una colisión nonce, este nonce conducirá a una fuga interna de la clave de autenticación y permitirá que un atacante falsifique cualquier otro texto cifrado creado por esta clave. Esta es una limitación muy significativa.

Pasemos al segundo método Libsodium.NET . Como dije, aquí para nonce, se usa muy poco espacio, solo 64 bits. La etiqueta ocupa 128 bits, pero contiene solo 106 bits de entropía o menos, en otras palabras, significativamente más bajo que el nivel de seguridad de 128 bits, que en la mayoría de los casos intentan alcanzar. En cuanto a la falsificación, la situación aquí es ligeramente mejor que en el caso de AES-GCM. La colisión de nonce conduce a la falsificación de textos cifrados, pero solo para aquellos bloques en los que se produjeron colisiones. En el ejemplo anterior, habríamos falsificado 2 textos cifrados, no 100.

Finalmente, en el caso del algoritmo xSalsa / Poly, tenemos un nonce muy grande de 192 bits, lo que hace que las colisiones sean extremadamente improbables. El método de autenticación es el mismo que en el método anterior, por lo que la etiqueta nuevamente toma 128 bits y tiene 106 bits de entropía o menos.

Compare todas estas cifras con los indicadores correspondientes de la biblioteca Inferno . En él, nonce ocupa un espacio colosal, 320 bits, lo que hace que las colisiones sean casi imposibles. En cuanto a la etiqueta, todo es simple: ocupa exactamente 128 bits y tiene exactamente 128 bits de entropía, nada menos. Este es un ejemplo de un enfoque confiable y seguro.

Antes de conocer Libsodium.NET con más detalle, debemos comprender su propósito; desafortunadamente, no todos los que usan esta biblioteca lo saben. Para hacer esto, consulte su documentación, que establece que Libsodium.NET es un contenedor C # para libsodium . Este es otro proyecto de código abierto, cuya documentación dice que es una bifurcación de NaCl con una API compatible. Bueno, recurra a la documentación de NaCl , otro proyecto de código abierto. En él, como objetivo, NaCl se postula para proporcionar todas las operaciones necesarias para crear herramientas criptográficas de alto nivel. Es aquí donde está enterrado el perro: la tarea de NaCl y todos sus caparazones es proporcionar elementos de bajo nivel, a partir de los cuales alguien más ya puede ensamblar API criptográficas de alto nivel. Estas conchas en sí mismas como bibliotecas de alto nivel no fueron concebidas. De ahí la moraleja: si necesita una API criptográfica de alto nivel, necesita encontrar una biblioteca de alto nivel, en lugar de usar un contenedor de bajo nivel y pretender que está trabajando con una de alto nivel.

Veamos cómo funciona el cifrado en Inferno .



Aquí hay un código de ejemplo en el cual, como en el caso de Libsodium , cada encriptación y desencriptación toma solo una línea. Los argumentos son clave, texto y datos asociados opcionales. Cabe señalar que no hay nonce, no hay necesidad de tomar decisiones, en caso de un error de descifrado, simplemente devuelve nulo, sin lanzar excepciones. Dado que la creación de excepciones aumenta significativamente la carga en el recolector de basura, su ausencia es muy importante para los scripts que procesan grandes flujos de datos. Espero haber logrado convencerlo de que este enfoque es óptimo.

Por interés, intentemos encriptar alguna cadena. Este debería ser el escenario más simple que todos puedan implementar. Supongamos que solo tenemos dos valores de cadena diferentes posibles: "IZQUIERDA" y "DERECHA".



En la imagen, puede ver el cifrado de estas líneas usando Inferno (aunque para este ejemplo no importa qué biblioteca se use). Ciframos dos líneas con una clave y obtenemos dos textos cifrados, c1 y c2 . ¿Está todo correcto en este código? ¿Está listo para la producción? Alguien puede decir que el problema es posible de una manera corta, pero está lejos de ser el principal, por lo que asumiremos que la clave se usa de la misma manera y que tiene una longitud suficiente. Quiero decir algo más: con los enfoques criptográficos convencionales, c1 en nuestro ejemplo será más corto que c2 . Esto se denomina fuga de longitud: en muchos casos, c2 será un byte más largo que c1 . Esto puede permitir que un atacante entienda qué cadena está representada por este texto cifrado, "IZQUIERDA" o "DERECHA". La forma más fácil de resolver este problema es hacer que ambas líneas tengan la misma longitud; por ejemplo, agregue un carácter al final de la línea "IZQUIERDA".

A primera vista, la fuga de longitud se percibe como un problema un tanto exagerado que no se puede encontrar en aplicaciones reales. Pero en enero de 2018, se publicó un artículo en la revista Wired con un estudio realizado por la compañía israelí Checkmarx, bajo el título "La falta de encriptación en Tinder permite a los extraños rastrear cuando deslizas la pantalla". Relataré brevemente el contenido, pero primero una descripción aproximada de la funcionalidad de Tinder. Tinder es una aplicación que recibe una transmisión con fotos, y luego el usuario desliza la pantalla hacia la derecha o hacia la izquierda, dependiendo de si le gusta la foto o no. Los investigadores descubrieron que aunque los comandos en sí mismos estaban encriptados correctamente usando TLS y HTTPS, los datos para el comando correcto tomaron una cantidad diferente de bytes que los datos para el izquierdo. Esto, por supuesto, es una vulnerabilidad, pero en sí mismo no es demasiado significativo. Más significativo para Tinder fue el hecho de que enviaron las transmisiones con fotos a través de HTTP regular, sin ningún cifrado. Por lo tanto, el atacante podría obtener acceso no solo a las reacciones de los usuarios a las fotos, sino también a las fotos mismas. Entonces, como puede ver, la fuga de longitud es un problema muy real.

Ahora intentemos encriptar el archivo. Inmediatamente debo decir que en el cifrado de archivos Libsodium.NET o, en términos más generales, el cifrado de flujo no se implementa de manera predeterminada, debe hacerse allí manualmente, lo cual, créanme, es muy difícil de hacer correctamente. En Inferno , las cosas están mucho mejor con esto.



Arriba, puede ver un ejemplo tomado prácticamente sin cambios de MSDN. Es muy simple, aquí vemos una secuencia para el archivo de origen y otra para el archivo de destino, así como una secuencia de cifrado que convierte la primera en la segunda. En este código, Inferno se usa solo en una línea, en la que tiene lugar la conversión. Entonces, ante nosotros hay una solución simple y al mismo tiempo totalmente funcional y probada para el cifrado de flujo.

Debe recordarse que al cifrar con la misma clave, tenemos un límite en la cantidad de mensajes. Existen en Inferno , y en esta biblioteca están claramente escritos en la pantalla. Pero al mismo tiempo, son tan grandes en Inferno que en la práctica nunca los alcanzarás. En Libsodium.NET, las restricciones son diferentes para diferentes algoritmos, pero en todos los casos son lo suficientemente bajas como para ser superadas. Por lo tanto, debe verificar si se lograrán en cada escenario individual.

También deberíamos hablar sobre la autenticación de datos asociados, ya que este es un tema que a menudo no se trata. Los AD pueden ser "débiles": esto significa que están autenticados, pero no están involucrados en el proceso de cifrado y descifrado. Por el contrario, los AD "fuertes" alteran este proceso en sí. La mayoría de las bibliotecas de AD que conozco son débiles, mientras que Inferno usa el segundo enfoque, donde los AD se usan en el proceso de cifrado / descifrado en sí ...

También debería detenerse en qué nivel de seguridad debería esforzarse por la criptografía de alto nivel. En resumen, mi respuesta es: cifrado de 256 bits con una etiqueta de autenticación de 128 bits. ¿Por qué es una llave tan grande? Hay muchas razones para esto, cada una de las cuales es importante en sí misma, pero ahora me gustaría que recuerde una cosa: debemos protegernos de posibles sesgos al generar claves criptográficas. Déjame explicarte qué se entiende por sesgo. Para un generador de bits aleatorio sin sesgo, para cada bit, las probabilidades de aceptar el valor 0 o 1 son iguales. Pero supongamos que en nuestro generador el bit tomará el valor 1 con una probabilidad del 56%, no del 50%. A primera vista, este sesgo es pequeño, pero de hecho es significativo: 25%. Ahora intentemos calcular cuánta entropía obtenemos al generar un cierto número de bits con nuestro generador.



En la imagen puede ver la fórmula por la cual se realizará este cálculo. Es importante que solo contenga dos variables: el sesgo del que ya hemos hablado (sesgo) y el número de bits creados por el generador. Suponemos que el sesgo es del 25%; este es un caso bastante extremo, en la práctica lo más probable es que no funcione en sistemas con un generador de números aleatorios tan distorsionado. De todos modos, con un 25% de sesgo y una clave de 128 bits, obtenemos solo 53 bits de entropía. En primer lugar, es significativamente menor que 128 bits, que generalmente se esperan de un generador de números aleatorios, y en segundo lugar, con las tecnologías modernas, dicha clave puede ser simplemente la fuerza bruta. Pero si en lugar de la clave de 128 bits usamos 256 bits, obtenemos 106 bits de entropía. Esto ya es bastante bueno, aunque menos de lo esperado 256. Con las tecnologías modernas, es casi imposible descifrar esa clave.

Al final de la primera parte del informe resumiré los resultados provisionales. Recomiendo a todos que utilicen API criptográficas bien escritas. Encuentre el que más le convenga o envíe una petición a Microsoft para que le escriba. Además, al elegir una API, debe prestar atención a la disponibilidad de soporte para trabajar con subprocesos. Por las razones ya explicadas, la longitud mínima de la clave debe ser de 256 bits. Finalmente, debe tenerse en cuenta que la criptografía de alto nivel, como cualquier otra, no es ideal. Pueden ocurrir fugas, y en la mayoría de los escenarios se deben tener en cuenta sus capacidades.

Hablemos de criptografía asimétrica o híbrida. Haré una pregunta capciosa: ¿puedes usar RSA en .NET? No se apresure a responder afirmativamente, como muchos lo hacen. Primero, analicemos sus conocimientos en esta área. Las siguientes diapositivas serán diseñadas específicamente para personas que ya están familiarizadas con este tema. Pero primero, echemos un vistazo a Wikipedia y recordemos qué es RSA exactamente en caso de que alguien haya olvidado o no haya utilizado este algoritmo durante mucho tiempo.



Supongamos que hay una Alice que, utilizando un generador de números aleatorios, crea un par de claves que incluye una privada y una pública. A continuación, hay algunos Bob que quieren cifrar un mensaje para Alice: "¡Hola, Alice!" Usando su clave pública, genera un texto cifrado, que luego le envía. Ella descifra este texto cifrado utilizando la parte privada de su clave.

Intentemos reproducir este escenario en la práctica.



Como puede ver arriba, creamos una instancia de RSA y encriptamos algo de texto. Preste atención de inmediato .NET nos obliga a elegir el modo de relleno. Hay cinco de ellos, todos con nombres oscuros. Si los probamos todos a su vez, descubriremos que los tres últimos simplemente lanzan una excepción y no funcionan. Utilizaremos uno de los dos restantes: OaepSHA1 . Aquí, la clave tendrá un tamaño de 1 kilobit, que es demasiado pequeña para RSA, es prácticamente una clave pirateada. Por lo tanto, tenemos que establecer el tamaño de la clave manualmente. De la documentación aprendemos que existe una propiedad especial .KeySize , que recibe o establece el tamaño de la clave.



A primera vista, esto es exactamente lo que necesitamos, así que escribimos: rsa.KeySize = 3072 . Pero si, guiados por una vaga sospecha, después de eso verificamos a qué tamaño de clave es ahora igual, descubrimos que todavía se necesita 1 kilobit. No importa, verificaremos este parámetro utilizando el WriteLine(rsa.KeySize) o rsa.ExportParameters(false).Modulus.Length * 8 : en este último caso, el componente público de la clave RSA se exporta, para esto necesitamos el argumento "false". El módulo de esta clave es una matriz, que multiplicamos por 8 y obtenemos el tamaño en bits, que nuevamente será de 1 kilobit. Como puede ver, este algoritmo aún es demasiado temprano para enviarlo a producción.

No perderemos el tiempo para descubrir por qué esta API no funciona, en su lugar, pruebe con otra implementación RSA proporcionada por Microsoft en .NET 4.6, es decir, una completamente nueva. Se llama RSACng , y Cng significa Criptografía de próxima generación. Genial, ¿quién no quiere trabajar con herramientas de próxima generación? Seguramente aquí encontraremos una solución mágica a todos nuestros problemas.



Solicitamos una instancia de RSACng, nuevamente establecemos el tamaño de la clave en 3 kilobits, nuevamente verificamos el tamaño de la clave a través de WriteLine(rsa.KeySize) y nuevamente descubrimos que el tamaño de la clave sigue siendo de un kilobit. Además, si solicitamos el tipo de objeto que generó la clave, como recordamos, solicitamos una instancia de RSACng, descubrimos que es RSACryptoServiceProvider. Solo quiero compartir mi sensación personal de desesperación aquí y gritar: "¿Por qué, Microsoft?"

Después de un tormento y un tormento prolongados, descubrimos que, de hecho, debe usar el diseñador, no la fábrica.



Aquí el valor de tamaño de clave predeterminado es 2048 bits, que ya es mucho mejor. Lo que es aún mejor: aquí finalmente logramos establecer el tamaño de la clave en 3 kilobits. Como dicen, logro desbloqueado.

Permítame recordarle que todos nuestros esfuerzos hasta ahora se han reducido solo a la creación de RSA, aún no hemos comenzado el cifrado. Todavía hay preguntas que primero debemos responder. Para empezar, ¿en qué medida puede confiar en los tamaños de clave predeterminados? La implementación de la fábrica RSA puede ser anulada machine.config, por lo tanto, puede cambiar sin su conocimiento (por ejemplo, un administrador del sistema puede cambiarla). Y esto significa que el tamaño de clave predeterminado también puede cambiar. Por lo tanto, nunca debe confiar en los valores proporcionados por defecto, el tamaño de la clave siempre debe establecerse de forma independiente. A continuación, ¿qué tan buenos son los tamaños de clave RSA predeterminados? Hay dos implementaciones de RSA en .NET, una basada RSACryptoServiceProvider, la otra basadaRSACng . 1 , . Bitcoin (BCN). , Bitcoin , . hashrate, 2 64 . 2 90 . , — , . , , , , 2 70 ( BCN) , 1- RSA, 2 90(un año BCN) - para descifrar una clave de 2 kilobits. Ambos valores deberían causarnos ansiedad: esto es lo que se puede lograr con las tecnologías existentes. Es por eso que le recomiendo que siempre configure el tamaño de la clave usted mismo, y que tenga un tamaño de al menos 3 kilobits, y si el rendimiento lo permite, entonces 4.

En .NET, no es tan fácil descubrir cómo exportar claves públicas y privadas.



En la parte superior de la diapositiva, verá dos instancias de la clave RSA, la primera de RSACryptoServiceProvider, la segunda deRSACngcada 4 kilobits. El siguiente código se utiliza para extraer las claves públicas y privadas de ambas instancias. Cabe señalar que ambas API son bastante diferentes entre sí: código diferente, métodos diferentes, parámetros diferentes. Además, si comparamos los tamaños de las claves públicas de la primera y segunda copia, veremos que son comparables, aproximadamente medio kilobyte cada una. Pero la clave privada para la nueva implementación de RSA es mucho más pequeña que la anterior. Es necesario tener esto en cuenta y observar la uniformidad, no interferir con estas dos API entre sí.

Todo lo que hemos hecho con RSA hasta ahora se ha reducido a tratar de obtener una copia que funcione; Ahora intenta encriptar algo.



Cree una matriz de bytes, que será nuestro texto sin formato (data), y luego lo encriptaremos utilizando uno de esos modos de adición que no arrojó una excepción. Pero esta vez tenemos una excepción. Esta es una excepción a un parámetro no válido; pero de que parámetro estamos hablando? No tengo idea, y Microsoft, muy probablemente, también. Si intentamos ejecutar el mismo método con otros modos de suplemento, en cada caso obtenemos la misma excepción. Entonces el punto no está en modo suplemento. Entonces el problema está en el código fuente mismo. Es difícil decir qué le pasa, así que intentemos reducirlo a la mitad por si acaso. Esta vez, el cifrado es exitoso. Estamos perplejos

¿Quizás el punto es que usamos el suplemento SHA-1? SHA-1, como sabemos, ya no es una función criptográficamente fuerte, por lo que nuestros auditores y el departamento de cumplimiento insisten en que nos deshagamos de ella. Reemplace OaepSHA1con OaepSHA256, al menos, tranquilizará a los auditores.



Pero cuando intentamos cifrar, nuevamente tenemos la excepción del parámetro incorrecto. Toda esta situación es causada por el hecho de que la restricción en el tamaño del texto que se puede transferir a la función criptográfica depende no solo del modo de suplemento, sino también del tamaño de la clave.

Intentemos averiguar exactamente cómo se ve esa fórmula mágica, que determina la cantidad máxima de datos cifrados. Debe estar en el métodoint GetMaxDataSizeForEnc(RSAEncryptionPadding pad), que calcula este volumen, después de haber recibido el modo de suplemento en la entrada. La principal desventaja de este método es que no existe, lo inventé. Estoy tratando de transmitir la idea de que incluso la información más básica que un desarrollador necesita para usar RSA correctamente no está disponible para nosotros. Gracias Microsoft.

Estas son las razones por las cuales se debe evitar el RSA, incluso para la firma. Como espero haber logrado mostrar, las API para RSA en .NET son extremadamente insatisfactorias. Usted está obligado a tomar muchas decisiones con respecto al modo de suplemento, el tamaño de los datos y similares, lo cual es indeseable. Además, para un nivel de seguridad de 128 bits, necesitará al menos una clave de 4 kilobytes muy voluminosa. Le dará una clave privada de kilobyte, una clave pública de medio kilobyte y una firma de medio kilobyte. Para muchos escenarios, tales valores pueden no ser deseables. Y si intenta alcanzar un nivel de seguridad de 256 bits, necesitará una clave enorme: 15360 bits. En RSA, usar tal clave es casi imposible. En mi computadora portátil, una de esas claves se genera un minuto y medio.Además de esto, el RSA en un nivel fundamental, como algoritmo, implementa muy lentamente una firma, independientemente de la implementación. ¿Por qué la velocidad de la firma es importante para nosotros? Si usa TLS con certificados RSA, la firma se realiza en el servidor. Y nosotros, como desarrolladores, somos los más afectados exactamente por lo que sucede en el servidor, somos responsables de ello, su rendimiento es importante para nosotros. En resumen, quiero recomendar una vez más que no use RSA.Quiero recomendar nuevamente no usar RSA.Quiero recomendar nuevamente no usar RSA.

En este caso, ¿qué puede reemplazar el RSA? Me gustaría presentarles las primitivas criptográficas elípticas modernas. En primer lugar, debe tener en cuenta el ECDSA (Algoritmo de firma digital), que se puede usar en lugar de RSA para las firmas. En esta y las siguientes abreviaturas, EC es un prefijo genérico que significa curva elíptica ("elíptica"). En securitydriven.net/inferno/#DSA Signatures, puede encontrar un código ECDSA de muestra que, por cierto, es nativo de .NET. Otro algoritmo importante es ECIES (Esquema de cifrado integrado, "esquema de cifrado integrado elíptico"). Este algoritmo puede realizar cifrado híbrido en lugar de RSA, es decir, donde genera una clave simétrica, cifra los datos con ella y luego cifra la clave en sí.El código de muestra está disponible en securitydriven.net/inferno/#ECIES ejemplo. Finalmente, otro algoritmo muy importante es ECDH (intercambio de claves Diffie-Hellman, "intercambio de claves Diffie-Hellman"). Le permite crear claves para el cifrado simétrico entre dos partes con claves públicas conocidas. En algunas situaciones y métodos de uso, permite el secreto directo (secreto hacia adelante ). El enlace securitydriven.net/inferno/#DHM clave del código disponible muestra del intercambio.

Para resumir la conversación sobre el cifrado asimétrico. Siempre debe usar API de alto nivel que no lo obliguen a tomar decisiones para las que no está preparado. También recomendaría dejar de usar RSA. Por supuesto, esto es más fácil decirlo que hacerlo, ya que todos trabajamos con aplicaciones grandes ya creadas, que pueden no ser refactorizadas por completo. En este caso, al menos debe aprender a usar RSA correctamente. Además, le aconsejo que se familiarice con los modernos algoritmos criptográficos elípticos (ECDSA, ECDH, ECIES). Finalmente, es importante que la criptografía de alto nivel no resuelva mágicamente todos los problemas, por lo que debe recordar los objetivos que persigue. Citaré de StackOverflow, con lo cual estoy completamente de acuerdo: “La criptografía por sí sola no resuelve los problemas.El cifrado simétrico solo convierte la privacidad de los datos en un problema de administración de claves ".

Diré algunas palabras sobre recursos que pueden serle útiles. Existe una biblioteca SecurityDriven.Inferno de alto nivel relativamente aceptable con buena documentación. Hay un libro maravilloso, Criptografía seria de Jean-Philippe Aumasson, Criptografía seria. Proporciona una visión general del estado actual de la criptografía, teniendo en cuenta las últimas innovaciones. Además, escribí el libro ya mencionado Application Security en .NET, sucintamente, que es de dominio público. Tiene aún más información sobre trampas de seguridad .NET. Finalmente, Slideshare tiene una excelente presentación de Vladimir Kochetkov, que describe los conceptos básicos de la teoría de seguridad de aplicaciones de una manera algo simplista pero muy sólida y explica varias fuentes de peligros.

Como conclusión, veamos algunos ejemplos adicionales que he preparado. Al principio, hablé sobre la cuarta etapa de la iluminación criptográfica, en la que nos damos cuenta de que la mejor solución puede no necesitar criptografía en absoluto. Veamos un ejemplo de tal solución. Echemos un vistazo al clásico mecanismo .NET: CSRF (falsificación de solicitudes entre sitios, "falsificación de solicitudes entre sitios"), diseñado para proteger contra una clase de ataques, incluida la falsificación de solicitudes entre sitios. En este modelo, tenemos un agente de usuario, generalmente un navegador. Intenta establecer una conexión con el servidor enviando una solicitud GET. En respuesta, el servidor envía un token CSRF, que está oculto en el campo HTML "oculto". Además, el mismo token se adjunta a la respuesta como una cookie, como un encabezado.El usuario procesa algún formulario y realiza una POST, que regresa al servidor con ambos tokens. El servidor comprueba, en primer lugar, si se enviaron ambos tokens y, en segundo lugar, si coinciden. Es esta comparación de identidad la que permite al servidor protegerse de un atacante. Este es un mecanismo clásico integrado en ASP.NET y ASP.NET Core. Mikhail Shcherbakov hizo un excelente informe en el que se investigó en detalle el trabajo de CSRF.

, CSRF . , — , , , . . , (injection) , . — , AJAX, — . , , , .



, . , , . , .

. DotNext. DotNext 2018 Moscow — 22-23 2018 - « ».

. , , . ! .

Source: https://habr.com/ru/post/es428121/


All Articles