He enfrentado un problema similar, para el cual implementé la siguiente mitigación. Apreciaría mucho el aporte, ya que no afirmo que esta sea necesariamente una solución excelente.
TL; DR
Almacene los ID como HMAC(ID, master-key)
y la contraseña de cada usuario se usa para derivar una clave por usuario para cifrar master-key
.
Capacidades / limitaciones del adversario
Protege contra un adversario capaz de leer todos los datos en reposo, pero no uno que pueda leer memoria (por ejemplo, interceptar la comunicación entre procesos entre el servidor web y el CGI).
Las sales uno a uno dificultan la búsqueda
La búsqueda eficiente en su caso requiere indexación y búsqueda de un valor único . Al incluir cada número de ID, debe calcular HASH(search query, salt)
para todas las sales que es (a) ineficiente cuando se realiza para cada búsqueda, y (b) ineficaz desde una perspectiva de seguridad como se describe a continuación.
Pequeño espacio de búsqueda
El problema principal radica en el hecho de que el espacio de búsqueda de los números de identificación es muy pequeño. Los ataques de fuerza bruta en hashes son, además de otros medios, mitigados por el aumento de los factores de trabajo (también conocido como estiramiento) con PBKDF2, bcrypt, scrypt, etc. Sin embargo, el espacio de búsqueda es de hecho tan pequeño que cualquier otro significativo -un aumento suficiente en el factor de trabajo para los adversarios sería demasiado incómodo para los usuarios (por ejemplo, 30 s + por búsqueda).
Cambiar el problema
Mi enfoque fue modificar el problema para proteger una clave secreta. HMAC permite tanto los datos (el número de identificación) como una clave. Las ID se almacenan como HMAC(ID, key)
mientras que las búsquedas se realizan con HMAC(search, key)
. Esto requiere coincidencias exactas, pero se puede hacer que no distinga mayúsculas de minúsculas con uppercase(ID)
y uppercase(search)
. Un ataque de fuerza bruta con una clave de 256 bits es inviable incluso sin un factor de trabajo incrementado.
¿Cómo protegemos la clave? Los usuarios de Query-API están autenticados, por lo que su contraseña se puede utilizar (después de usar sal y estirar) como el "material de ingreso de claves" (IKM) para HKDF . HKDF permite que se generen claves independientes desde una única fuente de entropía (el IKM) mediante la inclusión de 'información contextual' y una sal. Para la función HKDF(IKM, context, salt)
donde IKM = PBKDF2(password, rounds)
luego calculamos HKDF(IKM, 'Authentication', 'user-specific non-secret auth salt')
y HKDF(IKM, 'KeyWrapping', 'user-specific non-secret wrap salt')
. El primero se almacena en la base de datos de manera muy similar a un hash de autenticación con contraseña y el último se usa para envolver (es decir, cifrar) la clave maestra utilizada en los HMAC anteriores (tenga en cuenta que cada usuario tiene su propia clave maestra envuelta).
Cada vez que se autentica una sesión, la clave maestra no envuelta se ajusta dentro de una sesión cifrada. Puede utilizar HKDF con el ID de sesión (ya secreto) para derivar las claves de cifrado / almacenamiento para la sesión. A diferencia de las contraseñas que deben extenderse (incurrir en una demora de uno o dos segundos en la autenticación), los ID de sesión se pueden generar desde un CSPRNG (asegúrese de usar HttpOnly
y secure
flags).
Esto plantea otro problema, ya que necesitamos envolver inicialmente la clave maestra para cada usuario sin saber realmente su contraseña. Al crear el usuario en la base de datos, deben tener un par de clave privada / pública creado. La clave privada está encriptada con HKDF(IKM, 'PrivateKeyEncryption', 'user-specific non-secret PKI salt')
. Cada usuario puede tener la clave maestra que le ha dado un usuario administrativo. Al iniciar sesión, el sistema verifica su bandeja de entrada de la clave de acceso en busca de cualquier nueva clave maestra y luego la envuelve para la próxima vez. En realidad, podría utilizar el enfoque asimétrico, pero es computacionalmente más costoso.
Caveats
- Es imposible restablecer las contraseñas perdidas sin requerir que un usuario administrativo le dé la clave maestra al usuario.
-
NO soy un criptógrafo : puede haber vulnerabilidades derivadas de tener varias instancias de texto cifrado del mismo texto simple (EDIT: vea el primer comentario). Si es así, ¿puede mitigarse esto usando prefijos y sufijos entrópicos de longitud aleatoria? ¿O es una IV suficiente? Puede haber otros problemas que he pasado por alto completamente.
- Esto se basa en claves secretas efímeras (contraseña al iniciar sesión, ID de sesión en cada solicitud) almacenadas en la memoria. No estoy seguro de cómo funcionará tu trabajo cron; una posible solución es hacer que use un cifrado asimétrico con el conocimiento de una clave pública cuya clave privada esté encriptada con la clave maestra a la que todos los usuarios pueden acceder (cualquier sesión autenticada puede mover claves a la base de datos).
- ¿Su modelo de amenaza requiere una ingeniería tan extensa? La complejidad resulta en más lugares para que las cosas salgan mal.