Cómo hash / tokenize de forma segura una cadena

4

Un sistema en el que estoy trabajando acepta como entrada un número de cuenta de cliente y necesita generar un token basado en él. No se nos permite almacenar el texto simple del número de cuenta, por lo que el objetivo del token es el siguiente:

  1. No se puede revertir en el número de cuenta.
  2. Puede utilizarse para buscar e identificar de forma exclusiva un registro de metadatos de cuenta en nuestra base de datos.

Los números de cuenta se parecen a los números de tarjetas de crédito. Son cadenas numéricas largas de 16 dígitos; Los primeros 4 caracteres son constantes; El último carácter es un dígito de control computable. Esto significa que el tamaño real del conjunto de entrada es una cadena numérica de 11 caracteres: 99,999,999,999 posibles permutaciones.

He pensado en los métodos a continuación. Supongamos que hash significa un hash seguro suficientemente lento, como PBKDF2 de alta iteración, bcrypt o argon2 y encrypt significa AES256.

1. Hash simple

hash(account_num)

Aunque es simple, este enfoque es fácil de revertir a través de la fuerza bruta y es vulnerable a las tablas del arco iris.

2. Hash salado por cuenta

hash(salt + account_num)

Este enfoque corrige la vulnerabilidad a las tablas de arco iris, sin embargo, debido al tamaño limitado del conjunto de entrada, todavía es fácil de revertir a través de la fuerza bruta.

3. Hash salado por usuario cifrado con Global Pepper

encrypt(hash( salt + account_num ), pepper)

Esto se basa en mecanismo de almacenamiento de contraseñas de Dropbox . La inversión a través de la fuerza bruta requiere que se filtren los blobs cifrados y la clave de cifrado. Sin embargo, dado que cifrar el mismo valor dos veces con la misma clave produce diferentes blobs de salida, esto rompe la capacidad de seleccionar una cuenta de la base de datos por número de cuenta.

4. Enfoque híbrido

  1. Almacene los últimos 4 dígitos del número de cuenta en texto sin formato.
  2. Almacena el número de cuenta completo como un hash salado encriptado con pimienta.
    1. Use AWS KMS para el cifrado hash para reducir las probabilidades de perder una clave.

Lo que esto logra:

  1. Podemos buscar cuentas utilizando los últimos 4 dígitos del número de cuenta. Basándose en la comprobación de unos pocos miles de números de cuenta, esta selección devolverá entre 1 y 3 cuentas posibles.
  2. Iterar sobre cada una de las posibles coincidencias de cuenta ...
    1. Descifre el hash del número de cuenta para la cuenta.
    2. Compare el hash descifrado con el número de cuenta de entrada.
    3. Detenga la iteración tan pronto como encontremos un hash que coincida (use esta fila de la cuenta) o se quede sin cuentas (cree una nueva fila de la cuenta).

Mi pregunta: ¿Tiene sentido el enfoque 4 en realidad? Por la seguridad adicional que proporciona, ¿es demasiado complicado? ¿Tiene defectos que no he pensado? Más que nada, ¿hay una manera más sencilla de resolver este problema?

    
pregunta crgwbr 28.02.2017 - 18:04
fuente

3 respuestas

5

Parece que su número de cuenta es un número de tarjeta de crédito. Convenientemente, sé por experiencia que una tarjeta gráfica de cinco años puede agotar una clave numérica de sal con 10 sal en 3 días, lo que significa un promedio de 36 horas para revertir un hash PBKDF (4096). Ya no se ve bien para ti.

Es mucho mejor generar un nonce aleatorio para vincularse con la transacción y asociar ese nonce con la tarjeta.

    
respondido por el Jeff Ferland 01.03.2017 - 00:01
fuente
1

Simplemente puede utilizar un HMAC : HMAC tiene dos entradas, un mensaje y una clave de cifrado, y produce un código de autenticación de mensaje, que es básicamente un hash (por ejemplo, no puede volver a activar el MAC en el mensaje, la clave o ambos). Así que en tu caso tendrías:

token = hmac(account_num, key)

HMAC se construye de una manera especial para protegerse contra ataques que pueden revelar la clave. Es una primitiva criptográfica bien entendida.

Sin conocer la clave, un ataque de fuerza bruta en el token para obtener el número de cuenta no es práctico. OTOH, para usted, conociendo la clave, es trivial convertir un número de cuenta en un token.

Por supuesto, ahora la seguridad de sus números de cuenta se basa en la seguridad de la clave; por lo tanto, si pierde la clave y la lista de tokens para un atacante, ha abierto los números de su cuenta para ataques de fuerza bruta. Pero tu idea número 4 sufre el mismo problema.

Podría ser una buena idea pensar en la administración de claves, de modo que pueda cambiar fácilmente una clave si se compromete.

    
respondido por el Pascal 28.02.2017 - 23:44
fuente
1

Tu diseño no es seguro contra un ataque de Oracle. Cualquier número de texto en claro que se coloque en el sistema dará como resultado que se genere el mismo token. En lugar del hashing de fuerza bruta, el atacante simplemente tiene que usar su sistema para hacer su serie de conjeturas, y su oráculo le dirá si está en lo correcto o no.

Ha llamado a estos "números de cuenta" sin definir nada sobre cómo se asignan. Si son realmente aleatorios, entonces sí, tienes 10,000,000,000 posibles conjeturas. Pero como sabemos, el verdadero azar es difícil de conseguir. Y si no están criptográficamente espaciados aleatoriamente entre los valores posibles, un atacante gana una enorme cuna.

Primero usaré tarjetas de crédito como un ejemplo del mundo real de cómo las cunas permiten que se rompan, y luego mostraré cómo el ejemplo podría ser explotado en otros sistemas, tal vez en el suyo.

Considere los números de cuenta emitidos por los bancos. Cada tarjeta identifica al banco en los primeros seis dígitos, que se denominan Número de Identificación del Banco (BIN). Estadísticamente, los clientes tendrán una combinación de tarjetas de crédito, pero una gran parte de ellas (digamos un 13%) es muy probable que hayan sido emitidas por los bancos locales a la tienda donde se están utilizando. Esa es nuestra cuna. Entonces, si veo un valor hash con una tarjeta que termina en 1234, y sé que el BIN del banco local es 444444, simplemente fuerza bruta todos los dígitos que faltan en esta imagen: 4444 44?? ???# 1234 . El carácter # usa el algoritmo de verificación de dígitos para recuperar un dígito faltante de una conjetura. Con solo 100,000 adivinanzas, tendré un 13% de probabilidad de adivinar una tarjeta válida en su sistema.

Entonces, extendamos esto a sus tarjetas, y usted tiene un par de proveedores de tarjetas que las están imprimiendo. Si realiza un pedido de 100,000 tarjetas hoy y reordena 100,000 tarjetas el mes próximo, es posible que el proveedor no tenga forma de saber si la nueva ejecución está emitiendo los mismos números de tarjeta que la ejecución anterior. Por lo tanto, para evitar colisiones, coloca un número único de seis dígitos como los primeros seis dígitos de los números de tarjeta, asegurando que cada lote sea diferente de cada otro lote. (Este es un comportamiento muy común para los proveedores que imprimen tarjetas de regalo). Esto tiene un efecto similar al que hace un número BIN al hacer que los números sean adivinables. Hay alrededor de 50,000 BIN emitidos para tarjetas de crédito, pero puede ser peor en su caso porque no sabemos cuántos lotes posibles de tarjetas ha impreso.

Recuerde, un atacante no está tratando de equilibrar los libros; No necesita la perfección para triunfar como ladrón. No tiene que descifrar un número de token específico que encuentra para robar a una persona. Todo lo que tiene que hacer es fuerza bruta contra su base de datos y encontrar uno o más números que funcionen para él. Cuanto más adivina con éxito, más ganancias obtiene, pero cualquier éxito en el robo de tarjetas es una victoria para él.

En su lugar, considere un sistema de token de un solo uso. Si el atacante coloca 4444 4400 0001 1234 dos veces, obtiene dos tokens diferentes del sistema. Esa es la única forma de evitar que su sistema le proporcione a los posibles atacantes un oráculo.

    
respondido por el John Deters 01.03.2017 - 15:58
fuente

Lea otras preguntas en las etiquetas