jueves, 20 de febrero de 2014

Cifrado de claves

El cifrado de claves debe ser algo fundamental en el desarrollo de cualquier aplicación, debido a que es un aspecto básico de seguridad, que siempre debe ser considerado. Digamos que nos asignan en un nuevo proyecto y este incluye tener acceso a la información de una o más bases de datos, al hacer una consulta a la tabla usuario vemos que campo password no tiene cifrado y las claves de los usuarios se ven en texto plano. Mi opinión seria que un entorno productivo no puede trabajar de esa forma, menos si esta de cara a Internet, porque lo que esto indica, es que puede ser muy probable la existencia otras vulnerabilidades de seguridad, como por ejemplo inyección SQL o LDAP. Por supuesto permitiría a un atacante sacar todas esas claves planas. En resumen quien trabajó en el proyecto y cometió tal descuido deja mucho que desear, por decir algo suave. Lo que nos lleva a vivir en el problema del que estuvo antes que yo no sabia lo que hacia... Y cosas por el estilo. Pero como lo importante es todo lo contrario, debemos dignificar la profesión.
Muy bien las dos consideraciones básicas son:
  1. Si se requiere de autenticación de usuarios para el propio sistema, lo más recomendado es usar un algoritmo hash como MD5 o SHA, de una sola vía. ¿Que quiere decir que es de una sola vía? Que solo puede ser cifrado u no existe algoritmo de descifrado. Lo cual permite solo la comparación de hash para saber si la clave coincide, su utilización es común en la autenticación de usuarios en sistemas como Unix o Linux o en sistemas web tipo CMS, CRM, ERP, u otras aplicaciones que manejan sus propios usuarios, obviamente también en ldap y otros sistemas SSO.
  2. Si es un sistema que debe utilizar esa password para enviar mensajes a otros sistemas, lo cual requiere utilización de clave plana. También debe ser cifrada pero esta vez se utiliza un algoritmo de cifrado de dos vías. ¿Que quiere decir que es de dos vías? Que debe permitir el cifrado y el descifrado de la información. Esto es posible con algoritmos como 3DES (lento) y AES entre otros más elaborados y seguros como PKI.
Un ejemplo practico para el primer caso seria algo como:
from django.http import HttpResponse
from django.template import RequestContext, loader
from django.db.models import User
from hashlib import md5

class Login(TemplateView):

    def dispatch(self, request):
        template = loader.get_template('login.html')
        user = User.objects.get(user_name=request.POST['user'])
        if md5(request.POST['password']).hexdigest() == user.user_password:
            data = {"status": "logueado con exito"}
        else:
            data = {"status": "password fail"}
        context = RequestContext(request, data)
        return HttpResponse(template.render(context))

En el segundo caso practico podría implementarse por ejemplo en AES de una forma como:
from Crypto.Cipher import AES
from Crypto import Random
import base64

class AESCipher:

    def __init__(self, key):
        self.bs = 32
        self.key = hashlib.sha256(key.encode()).digest()

    def encrypt(self, raw):
        raw = self._pad(raw)
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return base64.b64encode(iv + cipher.encrypt(raw))

    def decrypt(self, enc):
        enc = base64.b64decode(enc)
        iv = enc[:AES.block_size]
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')

    def _pad(self, s):
        return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)

    @staticmethod
    def _unpad(s):
        return s[:-ord(s[len(s)-1:])]


UnitTest para quien lo quiera integrar con TDD
import unittest
import AESCipher

class TestCore(unittest.TestCase):

    def test_aescipher(self):
        aes_key = 'basura'
        cipher = AESCipher(aes_key)
        mesg = "passphrase"
        secret = cipher.encrypt(mesg)
        self.assertNotEqual(mesg, secret, "must be different but are equals")
        self.assertEqual(cipher.decrypt(secret), mesg, "must be equal but are different")
De esta forma solo el sistema conoce explicitamente como cifrar y descifrar las claves, obviamente esto está implementado en growpy.
Bien ahora una vista a la base de datos y se vera algo como:
sqlite> .schema node
CREATE TABLE node (
        node_id INTEGER NOT NULL,
        node_name VARCHAR NOT NULL,
        node_os_name VARCHAR NOT NULL,
        node_login VARCHAR NOT NULL,
        node_password VARCHAR NOT NULL,
        PRIMARY KEY (node_id),
        unique(node_name)
);

sqlite> SELECT node_password FROM node LIMIT 1;
sqlite> 4wGtGj7/KY2yW26ciBrhQEpUFu9rakjdbc7R/qckgFtuEXXx9WMAPAY2qTWy+ae0
Espero que esto sirva para no encontrarme ni dejar sistemas cuyas bases de datos tienen campos para claves en texto plano.