You are currently browsing the category archive for the ‘sw.CB’ category.

Hola a todos. 🙂 Continuando con la serie sobre stateless, ahora veremos cómo tratar con datos que compartimos y con en los cuales tenemos que confiar durante su uso. Con esto obtendremos información firmada que nos puede servir para procesos como la autenticación. Empecemos…

Una de las maneras de mantener una sesión es guardar variables en la cookie. Perom, si esto suena a no-stateless y no les gusta, lo mismo es guardarlo en la cookie que enviar el valor de la variable po una cabecera. A fin de cuentas, la cookie es una cabecera y en ambos casos se envía la cabecera y su valor en cada petición.

El riesgo en ambos casos es que alguien vea el valor de la variable y decida cambiarla. Necesitamos de un método que nos indique que el valor ha sido malversado.

Imaginemos que tenemos una tabla de usuarios. Una manera sencilla de mantener la sesión es enviar al software cliente el identificador del usuario (llave primaria, id o cualquier otro término que se desee usar). Es decir, tenemos un usuario cuyo identificador es 3814ebe3 y el cual será reenviado constantemente durante la comunicación.

Obviamente, el problema aquí es que en cualquier momento nos pueden cambiar el valor del identificador logrando suplantar la identidad de otro usuario.

Tenemos que saber que el valor que hemos enviado es uno en el que podemos confiar. Para ello desarrollaremos dos funciones make_secure(value) y check_secure(value).

Necesitamos de un método de hashing para asegurar la integridad de los datos. Usaremos el algoritmo HMAC para obtener el hash de los valores que queremos asegurar.

def make_secure(value, salt):
  return '%s|%s|%s' % (value, salt, hmac.new(SECRET, value+salt).hexdigest())

El valor digest es el hash que representa nuestros datos. En la función anterior vemos una variable SECRET. Esta es la parte que sólo conocemos nosotros. Nos sirve para que el hash generado depende de algo que sólo posee el servidor, por ejemplo. Usualmente, la variable SECRET es una cadena de caracteres. Tambíen, necesitamos un salt que no es otra cosa que una cadena aleatoria de caracteres. Por ejemplo, con

SECRET = 'Js4:su(8"(v/di#f:g)3-g5_=+u3E&"'

obtenemos lo siguiente

>>> make_secure('3814ebe3', '%(aB$N:05')
... '3814ebe3|%(aB$N:05|e3130eb3d9b51cca5a746df2736824d3'

Así, sería necesario conocer el SECRET, lo cual es compplicado pues sólo se necesita en el lado del servidor.

Algo que nos hemos dejado en el camino es cómo obtener el salt.

CHOICES = digits + '!"#$%&\\(;.:)=?+-*/' + letters
def make_salt(length=9):
  return ''.join(random.choice(CHOICES) for _ in range(length))

donde CHOICES es el conjunto de caracteres para formar el salt. (En este caso, números más letras y otros signos.)

Ahora bien, el método make_secure puede refinarse para sólo depender del valor que queremos asegurar:

def make_secure(value, salt=None):
  if not salt:
    salt = make_salt()
  return '%s|%s|%s' % (value, salt, hmac.new(SECRET, value+salt).hexdigest())

Lo que nos falta es el método que nos permite validar el valor:

def check_secure(secure_value):
  value, salt, _ = secure_value.split('|')
  return value if secure_value == make_secure(value, salt) else None

El método de validación regenera la terna a partir del valor y el salt. Si alguna parte del valor ha sido modificado, este método fallará indicando que la información ha sido comprometida.

Este método es la base para firmar payloads y asegurarnos de su autenticidad. No necesita persistencia de caché o disco, pues el SECRET puede estar en la memoria de la aplicación. Cambiarlo implica cerrar las sesiones activas.

Además, es posible añadir conceptos como tiempo de expiración. Sólo sería necesario calcular el hash con el timestamp de expiración. Y así, nuevamente, no es necesario almacenar esa información en caché, por ejemplo. 😉

Anuncios

En la publicación anterior veíamos como intercambiar variables sin usar otra variable intermedia. Ahora explicaremos un poco porqué esto funciona.

Para describir todas las posibles soluciones tenemos que echar mano del concepto matemático de grupo. Un grupo es un conjunto G junto con una operación binaria que satisface

  1. Clausura. Para todo a y b en G, su multiplicación a ⋅ b también está en G.
  2. Asociatividad. Para todo a, b y c en G, tenemos (a ⋅ b)⋅c = a ⋅ (b ⋅ c).
  3. Identidad. Hay un e en G tal que, para todo a en G, se cumple e ⋅ a = a ⋅ e = a.
  4. Inveros. Para cada a en G, hay un inverso a−1 tal que se cumple a−1 ⋅ a = e = a ⋅ a−1.

Así, podemos aplicar el cambio de variables mediante

a ← a ⋅ b
b ← a ⋅ b−1
a ← b−1 ⋅ a

En la siguiente tabla podemos ver paso a paso lo que sucede

a b

c

d

a ← a ⋅ b

c ⋅ d

d

b ← a ⋅ b−1

c ⋅ d

(c ⋅ d)⋅d−1 = c ⋅ (d ⋅ d−1)=c

a ← b−1 ⋅ a

c−1 ⋅ (c ⋅ d)=(c−1 ⋅ c)⋅d = d

c

Notar que tenemos que escribir b−1 ⋅ a y no a ⋅ b−1 ya que en general un grupo no es conmutativo.

Hay varios ejemplos de grupos. Tenemos a los enteros cuya operación sería la adición, la identidad es 0 y el inverso de n es n. No deberíamos preocuparnos por un overflow pues los enteros siguen siendo un grupo bajo la adición módulo N.

También funciona para la representación binaria (con una cantidad de dígitos fijo), ya que las cadenas de bits forman un grupo donde la operación es la disyunción exclusiva, la identidad es la cadena de ceros y toda cadena es su propia inversa.

Los floats distintos de cero forman un grupo bajo la operación de multiplicación, donde la identidad es 1 y el inverso de x es 1/x. Pero hay cuestiones de overflow y underflow, valores NaN y la no asociatividad de la multiplicación de punto flotant

Funciona bien con matrices de dimensiones fijas bajo la operación de adición, donde la identidad es la matriz cero y el inverso está dado por la negación elemento a elemento.

También funciona con las matrices cuadradas, no singulares y de la misma dimensión bajo la operación de la multiplicación matricial, donde la identidad es la matriz identidad y los inversos vienen dados por la matriz inversa (nuevamente, con cuestiones de punto flotante).

Finalmente, las permutaciones también forman un grupo bajo la operación de composición, donde la identidad está dada por la permutación nula y los inverso se obtienen deshaciendo todos los cambios.

Como nota final, quería aclarar que no son necesarias todas las propiedades de grupo. Un cuasigrupo es un conjunto con tres operaciones binarias: multiplicación *, división izquierda \ y división derecha /, que satisfacen

x|(x * y)=y
x * (x|y)=y
(x * y)/y = x
(x * y)*y = x

Todo grupo es automáticamente un cuasigrupo al definir a * b = a ⋅ b, a/b = a ⋅ b−1 y a|b = a−1 ⋅ b. De manera que los cuasigrupos son un generalización de los grupos. Entonces, podemos expresar el cambio de variables como

a ← a * b
b ← a/b
a ← b|a

Es bastante interesante saber que los cuadrados latinos fornan un cuasigrupo, así, por ejemplo, tenemos que las soluciones completas de un Soduku son cuadrados latinos. Luego, cualquier problema de intercambio de variables sin usar una variable temporal pueden ser resueltos con un Soduku suficientemente grande. 😉

Lo bueno de interactuar con otras personas es que uno aprende qué evitar y qué imitar. Al discutir cuestiones relacionadas con el concepto stateless me he topado con visiones acertadas y, por otro lado, descaradas. Así que en las siguientes publicaciones iré exponiendo pequeñas ideas sobre lo que podemos entender por ausencia de estados. Empecemos…

Uno aprende cómo declarar e inicializar variables cuando inicia cualquier curso de programación. Así, por ejemplo, aprendemos a definir las variables a y b como sigue:

a = 12345
b = 67890

Y una acción importante, necesaria en varios algoritmos, es poder intercambiar el valor de ambas variables de manera que se cumpla

a == 67890 and b == 12345

sea verdadero.

Para ello es usual recurrir a una variable auxiliar (también llamada temporal) temp. Mediante ella, guardamos temporalmente el valor de una de las variables para poder recuperar dicho valor durante el intercambio. Así, para cambiar las variables

temp = a
a = b
b = temp

¿Esto es stateless? Cuando formulé la pregunta vi más de una cara de sorpresa. Para el procedimiento de cambio de variable, esto no es stateless; pues, para ejecutarla, hemos necesitado guardar información acerca del estado del problema, donde el estado está definido por el valor de las variables a y b.

Una manera de hacer stateless es recurrir a la disyunción exclusiva.

a ^= b
b ^= a
a ^= b

Con ello logramos los esperado

a == 67890 and b == 12345

sin necesidad de usar una variable intermedia.

Otra forma de lograr lo mismo es la siguiente:

a = a + b
b = a - b
a = a - b

En conclusión, hemos visto dos maneras de realizar un intercambio de variables stateless. 🙂 Las próximas dos publicaciones irán del concepto matemático de grupo, el primero, que el que explica el porqué funciona este intercambio stateless, y el otro, sobre cómo aplicar el mismo principio para lograr una autenticación. Luego, una más sobre JWT. (Con sus respectivos comentarios acerca de lo que me llevó a escribir sobre esto.)

Categorías

Anuncios