Evaluación de métodos para generar las claves RSA

La suerte (?)

En Benchmarking RSA Key Generation no hablan mucho de la evaluación en sí, pero me pareció un recurso valioso porque hablan un poco de los problemas relacionados con la generación de claves (aunque tampoco entra en mucho detalle).

Como sabemos, se trata de generar dos números primos grandes (se habla de 1024 bits en la entrada), multiplicarlos y hacer algunos cálculos más.

The idea is that you generate random 1024-bit numbers until you find two that are prime, you call them p and q, and compute N = p × q and d = 65537⁻¹ mod φ(N)1

Para elegir los primos se suelen utililzar métodos probabilistas, generando un número grande con un generador seudoaleatorio. Se usan algunos trucos, como cambiar el último bit (para que el número sea impar) y los dos primeros (para que el número sea grande).

There is almost nothing special to selecting prime candidates. You draw an appropriately sized random number from a CSPRNG, and to avoid wasting time, you set the least significant bit and the two most significant bits: large even numbers are not prime, and setting the top two guarantees N won’t come out too small.

Luego hay que verificar si efectivamente es primo, normalmente con la prueba de Miller-Rabin, que es un algoritmo probabilista.

Checking if a number x is prime is generally done with the Miller-Rabin test2, a probabilistic algorithm where you pick a “base” and use it to run some computations on x.

El algoritmo puede decirnos que el número no es primo, y entonces lo descartamos; o que no lo sabe, y entonces tenemos que decidir qué hacer. Normalmente se repite unas cuantas veces y se acepta el resultado.

Checking if a number x is prime is generally done with the Miller-Rabin test2, a probabilistic algorithm where you pick a “base” and use it to run some computations on x. It will either conclusively prove x is composite (i.e. not prime), or fail to do so.

Luego habla un poco de la evaluación. Muy interesante.

Manejo de errores en aplicaciones: algunas ideas y estrategias

Teruel. Torre mudéjar.

En The Ultimate Guide to Error Handling in Python una lista de consejos sobre manejo de errores programando en Python que me gustó leer, aunque se pueden aplicar casi a cualquier otro lenguaje.

Primero comenta las dos aproximaciones típicas para afrontar los problemas:

  • Mirar antes de avanzar (verificar que se cumplen las condiciones necesarias para realizar la acción que vamos a llevar a cabo).

The “look before you leap” pattern for error handling says that you should check that the conditions for performing an action that can fail are proper before triggering the action itself.

El problema es que es difícil saber todo lo que puede salir mal a la hora de realizar una acción, así que es fácil que nuestro código siga teniendo problemas. También pueden aparecer condiciones de carrera (cuando algo cambia entre la verificación y la realización de la acción).

  • Es mejor pedir perdón que pedir permiso (esto es, realizamos la acción y si falla, manejamos los errores).

The competing pattern says that it is “easier to ask forgiveness than permission”. What does this mean? It means you should perform the action, and deal with any errors afterwards.

El problema en este caso es que podemos capturar el fallo, pero es posible que en muchos casos la captura sea genérica, poco informativa y cuando vayamos a mirar lo sucedido no lo sepamos muy bien.

On the other side, we need to know what exceptions to write down in the except clause, because any exception classes that we miss are going to bubble up and potentially cause the Python application to crash.

Una vez establecidas estas ideas nos lleva por algunos detalles interesantes.

Primero, saber si estamos ante un error, o se trata de un error de otra parte del código que nos llega.

You either need to introduce a new error yourself and put it in the system for some other part of the application to handle, or you received an error from somewhere else and need to decide what to do with it.

A continuación, hay que tener en cuenta que hay errores de los que nos podemos recuperar, y otros de los que no.

Recoverable vs. Non-Recoverable Errors

Si encontramos errores de los que el código que tenemos es el responsble, podemos manejarlos, añadiendo instrucciones que gestionen el problema y nos permita continuar con la tarea.

What do you think is the best way to handle this case? Well, recover from the error and continue, without bothering anyone else!

También puede ocurrir que los errores vengan de otra parte del código y en ese caso aplicamos la estrategia de mejor pedir perdón que pedir permiso, y añadimos instrucciones para remediar lo que tengamos entre manos.

How do we handle this case? We use EAFP to catch the error, and then we do whatever needs to be done to recover from it and continue.

Pero también pueden aparece errores que no son recuperables. En este caso, podemos interrumpir la ejecución de esa parte del código y lanzar el error hacia arriba, con la esperanza de que en un nivel superior puedan manejarlo.

The only reasonable action that can be taken is to stop the current function and alert one level up the call stack of the error, with the hope that the caller knows what to do.

De cualquier modo, habrá errores que nosotros no hayamos sido capaces de manejar, pero que los niveles superiores tampoco podrán.

Now we have a piece of code that called some function, the function raised an error, and we in our function have no idea how to fix things up so that we can continue, so we have to consider this error as non-recoverable. What do we do now?

Al final, este tipo de errores podrían capturarse al máximo nivel para estar seguros de que el programa no hará nada extraño.

The reason is that at this level we really cannot let any exceptions reach Python because we do not want this program to ever crash, so this is the one situation in which it makes sense to catch all exceptions.

También discute sobre si el tratamiento de errores se debería hacer a más bajo nivel o más alto, y es partidario de llevarlo hacia arriba, para tener código más limpio y fácil de mantener.

In fact, you should design your applications so that as much code as possible is in functions that do not need to concern themselves with error handling. Moving error handling code to higher-level functions is a very good strategy that helps you have clean and maintainable code.

Cambiando el terreno, luego habla de los errores en producción frente a los errores en desarrollo. Parece claro que en la fase de programación el fallo catastrófico de una aplicación no debería ser un problema. Incluso es bueno, porque nos permite encontrar los errores y poder arreglarlos.

During development, there is actually nothing wrong with the application crashing and showing a stack trace. In fact, this is a good thing, since you want errors and bugs to be noticed and fixed.

Y también dice que es más fácil gestionarlo cuando los errores se manejan al máximo nivel (fuera de la lógica del programa).

This becomes much easier to implement when the error handling is in one place and separate from the application logic.

Simplemente el tratamiento sería diferente: en desarrollo dejamos al programa fallar y que pase lo que tenga que psar; en producción, capturamos el problema, informamos y hacemos una salida ‘limpia’.

When we are running in development mode we now re-raise the exceptions to cause the application to crash, so that we can see the errors and the stack traces while working.

Interesante, recordatorio y organización de las ideas.

Cuando los que nos protegen la lían

eFindex: Enredados

En How to protect yourself from the Salt Typhoon Hack, no matter what the FBI says hablaban del ataque realizado desde China donde consiguieron acceso a ocho telefoneras (empresas de telecomunicaciones), consiguiendo una buena cantidad de datos de sus usuarios (cuándo, dónde y a quién se realizaban las llamadas).

At least eight telecommunications companies were compromised in the hack,

That information includes details about when and where calls were placed and to whom, but not their contents.

Para algunos usurios (muy pocos, alrededor de 150) era todavía peor porque habrían conseguido acceder al contenido de las comunicaciones.

¿El problema? Parte del compromiso vino por parte del sistema que utlilzan las agencias gubernamentales (cuerpos y fuerzas de seguridad) para realizar su trabajo.

Another “vector” of the attack, according to government officials, was the interface where law enforcement agencies request wiretaps from telecom companies under the 1994 Communications Assistance for Law Enforcement Act.

Y el problema es el de siempre: si haces un sistema para que los protectores de la ley puedan interceptar determinadas comunicaciones, seguramente alguien más conseguirá tener ese mismo acceso.

“If you build a system so that it is easy to break into, people will do so — both the good guys and the bad. That’s the inevitable consequence of CALEA, one we warned would come to pass — and it did,” she said.

El FBI decía que no había sido el único sistema comprometido, pero está claro que ha sido parte del problema.

Los defensores de la privacidad nos recuerdan que si la información hubiera ido cifrada de extremo a extremo, el pboelma sería mucho menor.

End-to-end encrypted communications make sure that a written message or voice call is protected from the moment it leaves your device to the moment it arrives at its destination, by ensuring that only the sender and the recipient can decrypt the messages, which are unreadable by any third party — whether that happens to be a Chinese hacker or the FBI.

Las policías, por su parte, dicen que necesitan herramientas para perseguir a los malos.

The FBI and other agencies have long maintained that there might be some way to give them special access to communications without making things easier for hackers and spies.

Así que, mientras esto siga de esta forma, nuestra única protección es utilizar sistemas de mensajería cifrados, como pueden ser Signal o WhatsApp (ojo, porque hay otras que la gente cree que son seguras y están en duda). Esta última también tiene sus detractores, desde luego.

Privacy advocates say that the best thing that people can do to protect themselves from prying eyes is to use some of the same apps recommended by Wyden, such as Signal or WhatsApp.

Algunos conceptos básicos de cifrado

Máquina de cifrado Hagelin M-209

Nunca insistimos lo suficiente en lo importante que es el cifrado y que es necesario hacerlo bien. En Encryption: Ciphers, Digests, Salt, and IV - What You Need to Know algunas definiciones básicas de la terminología relacionada con el tema.

El cifrado sería un método para convertir los datos en algo que no puede utlilizarse y que solo puede volverse a hacer útil mediante el descifrado.

Encryption is a method of turning data into an unusable form that can be made useful only by means of decryption

Sería, nos dice, la última línea de defensa ante la posibilidad de que alguien consiga saltarse los sistemas de control de acceso y otras protecciones.

It is the last line of defense after an attacker has managed to break through authorization systems and access control.

Nos recuerda que los datos pueden estar cifrados en reposo, cuando están almacenados, y también cuando se transmiten por la red.

Data can be encrypted at-rest, such as on disk, or in transit, such as between two parties communicating over the Internet.

Una cifra (o método de cifrado) sería el algoritmo que utilizamos para cifrar. Un ejemplo es AES256. Es lo que la mayoría de la gente piensa cuando hablamos de cifrado.

A cipher is the algorithm used for encryption. For example, AES256 is a cipher. The idea of a cipher is what most people will think of when it comes to encryption.

Un resumen es la transformación que se utiliza para ofuscar y alargar la contraseña antes de cifrarla.

A digest is basically a hash function that is used to scramble and lengthen the password (i.e., the encryption key) before it’s used by the cipher.

Sirve, nos dice, para crear un conjunto de contraseñas aleatorizadas (aunque las originales no lo sean) y de la misma longitud que serían más adecuada para almacenar cifrada.

Relacionado está el términino de sal, cuyo objetivo es que dos contraseñas que pueden ser iguales no se vean iguales después de aleatorizarlas. De esta manera, un atacante que conociera la clave de un usuario no podría darse cuenta de que hay otros que utilizan la misma.

Usually, salt is randomly generated for each key and stored with it. In order to match known hashes, the attacker would have to precompute rainbow tables for a great many random values, which is generally not feasible.

Con la mejora de la potencia de cálculo ha surgido la idea de iterar los cálculos: no nos quedamos ocn una sola aplicación de la transformación sino que se hacen muchas, de forma que alguien que intentara un ataque por fuerza bruta lo tenga más difícil.

This is done many times so to make it computationally difficult for an attacker to reverse-guess the key, hence “iterations” (plural).

El vector de inicialización es un valor aleatorio que se utiliza para el cifrado de cada mensaje: a partir de la clave y del mensaje conseguiremos que diferentes incializaciones provoquen resultados diferentes, incluso aunque el mensaje sea el mismo.

IV (or “Initialization Vector”) is typically a random value that’s used for the encryption of each message. Now, salt is used for producing a key based on a password.

Esto permite evitar los ataques de repetición, puesto que el mismo mensaje repetido no produciría el mismo resultado, que es lo que intentan los atacantes cuando capturan un mensaje (incluso cifrado) y tratan de conseguir la misma acción repitiéndolo.

This makes “replay” attacks difficult, which is where the attacker doesn’t need to decrypt a message; rather, an encrypted message was “sniffed” (i.e., intercepted between the sender and receiver) and then replayed, hoping to repeat the action already performed.

Después de esta introducción muestra algunos ejemplos de aplicación, que valdría la pena mirar con calma.

Complejidades y carga cognitiva

Mecanismo

En Cognitive load is what matters se hablar de la carga cognitiva del desarrollo, que tiene que ver con lo que la persona que desarrolla tiene que pensar para completar su tarea.

Cognitive load is how much a developer needs to think in order to complete a task.

Cuando desarrollamos programas (software) podemos acudir a muy diversos niveles de abstracción. Manejamos infomación como valores, control del flujo y secuencias de llamadas, pero nuestra cabeza puede manejar esto hasta un cierto nivel.

When reading code, you put things like values of variables, control flow logic and call sequences into your head. The average person can hold roughly four such chunks in working memory. Once the cognitive load reaches this threshold, it becomes much harder to understand things.

Hay dos tipos de carga, la intrínseca, provocada por la dificultad de la tarea.

Intrinsic - caused by the inherent difficulty of a task. It can’t be reduced, it’s at the very heart of software development.

Y extrínseca, que tiene que ver con la forma en que se presenta la información, y que es sobre la que podríamos intervenir.

Extraneous - created by the way the information is presented. Caused by factors not directly relevant to the task, such as smart author’s quirks. Can be greatly reduced. We will focus on this type of cognitive load.

Por ejemplo, podríamos reducir expresiones lógicas complejas mediante la adición de variables que simplifiquen el proceso y nos den pistas. Copio sus ejemplos:

Complicado:

if val > someConstant // 🧠+
&& (condition2 || condition3) // 🧠+++, prev cond should be true, one of c2 or c3 has be true
&& (condition4 && !condition5) { // 🤯, we are messed up by this point
...
}

Más sencillo:

isValid = val > someConstant
isAllowed = condition2 || condition3
isSecure = condition4 && !condition5
// 🧠, we don't need to remember the conditions, there are descriptive variables
if isValid && isAllowed && isSecure {
    ...
}

Y se puede simplificar de más formas, pero creo que se ve la idea.

También nombra las salidas prematuras (early returns) que tanto les gustan a las inteligencias artificiales (o, al menos, eso es lo que he visto).

También puede haber otros tipos de complejidad relacionados con algunas construcciones que son ventajosas, como la herencia (que si se nos va de las manos puede obligarnos a llevar en la cabeza varios lugares del código donde pasan cosas interesantes.

Inheritance nightmare

¿Y qué sucede cuando tenemos demasiados métodos pequeños, clases y módulos? Demasidado de cualquier cosa (por mucho que los consejos nos recomienden tener métodos pequeños, compartimentalización, …) es un problema en sí mismo.

Having too many shallow modules can make it difficult to understand the project. Not only do we have to keep in mind each module responsibilities, but also all their interactions. To understand the purpose of a shallow module, we first need to look at the functionality of all the related modules. 🤯

Habla de otros casos relacionados como la exitencia de muchos microservicios en contraposición con un ‘monolito’ bien diseñado.

A well-crafted monolith with truly isolated modules is often much more flexible than a bunch of microservices. It also requires far less cognitive effort to maintain.

Otro caso interesante es el de los lenguajes con muchas características: lo que a priori parece una buena idea, resulta en un problema a la hora de aprender cosas nuevas, decidir cuáles usar, cómo, dónde…

You not only have to understand this complicated program, you have to understand why a programmer decided this was the way to approach a problem from the features that are available. 🤯

Un buen ejemplo es el de los códigos de respuesta del protocolo HTTP: por un lado son amplios y flexibles, pero al final vuelve a ocurrir lo mismo. Los desarrolladores usan menos porque es más sencillo para ellos, y los consumidores no son capaces de diferenciar los pequeños detalles que los diferencian, cuando probablemente sería mucho mejor devolver otro tipo de información.

Why hold this custom mapping in our working memory? It’s better to abstract away your business details from the HTTP transfer protocol, and return self-descriptive codes directly in the response body

Termina hablando de las arquitecturas con nivels (Layered architecture), donde la abstracción que nos ayuda a ocultar la complejidad añade indirecciones (y dificultades cuando estamos buscando problemas).

Abstraction is supposed to hide complexity, here it just adds indirection. Jumping from call to call to read along and figure out what goes wrong and what is missing is a vital requirement to quickly solve a problem.

El compromiso que suponen las ventajas frente a las difultades, que solo puede resolver la experiencia y el conocimiento.

So, why pay the price of high cognitive load for such a layered architecture, if it doesn’t pay off in the future?

He hecho un resumen, pero creo que es un buen resumen-recordatorio de cuestiones que deberíamos tener claras a la hora de abordar proyectos. Salvo que esos proyectos sean, precisamente, para aprender estas cosas.