algorithm - ofuscación - ofuscar codigo android
Ofuscación de una identificación (10)
Estoy buscando una manera de encriptar / ocultar un ID entero en otro entero. Más precisamente, necesito una función int F(int x)
, de modo que
- x <-> F (x) es correspondencia uno a uno (si x! = y, F (x)! = F (y))
- dado F (x), es fácil descubrir x - entonces F no es una función hash
- dado x y F (x) es difícil / imposible de encontrar F (y), algo como
x ^ 0x1234
no funcionará
Para mayor claridad, no estoy buscando una solución de encriptación fuerte, solo ofuscación. Imagine una aplicación web con URL como example.com/profile/1
, example.com/profile/1
, etc. Los perfiles en sí no son secretos, pero me gustaría evitar que los voyeurs ocasionales vean / obtengan todos los perfiles uno tras otro, así que preferiría ocultarlos detrás de algo como example.com/profile/23423
, example.com/profile/23423
, etc. Aunque los tokens almacenados en la base de datos pueden hacer el trabajo con bastante facilidad, tengo curiosidad de saber si hay algunas matemáticas simples disponibles para esta.
Un requisito importante que no tenía claro es que los resultados deberían parecer "aleatorios", es decir, dada una secuencia x,x+1,...,x+n
, F(x),F(x+1)...F(x+n)
no debe formar una progresión de ningún tipo.
Desea que la transformación sea reversible y no obvia. Eso suena como una encriptación que toma un número en un rango dado y produce un número diferente en el mismo rango. Si su rango es de 64 bits, entonces use DES. Si su rango es de 128 bits, use AES. Si quieres un rango diferente, entonces tu mejor opción es probablemente el cifrado Hasty Pudding , que está diseñado para hacer frente a diferentes tamaños de bloques y con rangos de números que no encajan perfectamente en un bloque, como de 100,000 a 999.999.
Encontré esta pieza particular de código Python / PHP muy útil:
Escribí un artículo sobre permutaciones seguras con cifras de bloque , que deberían cumplir con sus requisitos como se indica.
Sugeriría, sin embargo, que si quieres identificadores difíciles de adivinar, primero deberías utilizarlos: generar UUID, y usarlos como la clave primaria para tus registros, en primer lugar, no hay necesidad de poder para convertir hacia y desde un ID "real".
Genere una clave simétrica privada para usar en su aplicación y encripte su entero con ella. Esto satisfará los tres requisitos, incluido el # 3 más difícil: uno debería adivinar su clave para romper su esquema.
Haga algo con los bits de la identificación que no los destruirá. Por ejemplo:
- rotar el valor
- use búsqueda para reemplazar ciertas partes del valor
- xor con algún valor
- intercambiar bits
- swap bytes
- reflejar todo el valor
- reflejar una parte del valor
- ... use su imaginación
Para el descifrado, haz todo eso en orden inverso.
Cree un programa que ''cifrará'' algunos valores interesantes para usted y colóquelos en una tabla que pueda examinar. Haga que el mismo programa PRUEBE su rutina de cifrado / descifrado CON todos los valores que desea tener en su sistema.
Agregue cosas a la lista anterior en las rutinas hasta que sus números se vean correctamente destrozados para usted.
Para cualquier otra cosa, obtenga una copia de The Book .
La ofuscación no es realmente suficiente en términos de seguridad.
Sin embargo, si estás tratando de frustrar al espectador casual, te recomendaría una combinación de dos métodos:
- Una clave privada que combinas con el id xor''ing together
- Girar los bits por una cierta cantidad antes y después de que se haya aplicado la tecla
Aquí hay un ejemplo (usando pseudo código):
def F(x)
x = x XOR 31415927 # XOR x with a secret key
x = rotl(x, 5) # rotate the bits left 5 times
x = x XOR 31415927 # XOR x with a secret key again
x = rotr(x, 5) # rotate the bits right 5 times
x = x XOR 31415927 # XOR x with a secret key again
return x # return the value
end
No lo he probado, pero creo que es reversible, debe ser rápido y no demasiado fácil de descifrar.
Lo que está describiendo aquí parece ser lo opuesto a una función unidireccional: es fácil de invertir pero muy difícil de aplicar. Una opción sería usar un algoritmo estándar de encriptación de clave pública en el que arregle una clave pública (secreta, elegida al azar) que mantenga en secreto y una clave privada que comparta con el resto del mundo. De esta manera, su función F (x) sería la encriptación de x usando la clave pública. Luego puede descifrar fácilmente F (x) de vuelta a x usando la clave de descifrado privada. Tenga en cuenta que las funciones de la clave pública y privada se invierten aquí: usted entrega la clave privada a todos para que puedan descifrar la función, pero mantienen la clave pública en secreto en su servidor. De esa manera:
- La función es una biyección, por lo que es invertible.
- Dado F (x), x es eficientemente computable.
- Dado x y F (x), es extremadamente difícil calcular F (y) desde y, ya que sin la clave pública (suponiendo que utilice un esquema de encriptación criptográficamente fuerte) no existe una forma factible de encriptar los datos, incluso si el privado la clave de descifrado es conocida.
Esto tiene muchas ventajas. En primer lugar, puede estar seguro de que el sistema de cifrado es seguro, ya que si utiliza un algoritmo bien establecido como RSA, entonces no necesita preocuparse por la inseguridad accidental. En segundo lugar, ya hay bibliotecas para hacer esto, por lo que no necesita codificar mucho y puede ser inmune a los ataques de canal lateral. Finalmente, puede hacer posible que cualquiera pueda ir e invertir F (x) sin que nadie pueda calcular F (x).
Un detalle: definitivamente no debería usar simplemente el tipo int estándar aquí. Incluso con enteros de 64 bits, hay tan pocas combinaciones posibles que un atacante podría intentar con la fuerza bruta invirtiendo todo hasta que encuentren el cifrado F (y) para algunos y, incluso si no tienen la clave. Sugeriría usar algo así como un valor de 512 bits, ya que incluso un ataque de ciencia ficción no sería capaz de forzar la fuerza bruta.
¡Espero que esto ayude!
No estoy seguro de qué tan "difícil" lo necesite, qué tan rápido o qué poca memoria usar. Si no tiene restricciones de memoria, puede hacer una lista de todos los enteros, mezclarlos y usar esa lista como un mapeo. Sin embargo, incluso para un entero de 4 bytes, necesitarías mucha memoria.
Sin embargo, esto podría hacerse más pequeño, por lo que, en lugar de mapear todos los enteros, correlacionaría solo 2 (o el peor caso 1) de un byte y lo aplicaría a cada grupo en el entero. Entonces, usando 2 bytes, un entero sería (grupo1) (grupo2) que asignaría cada grupo a través del mapa al azar. Pero eso significa que si solo cambia el grupo2, entonces la asignación para el grupo1 se mantendrá igual. Esto podría "arreglarse" asignando diferentes bits a cada grupo.
Entonces, * (grupo2) podría ser (bit 14,12,10,8,6,4,2,0) entonces, agregar 1 cambiaría tanto el grupo1 como el grupo2 .
Aún así, esto es solo seguridad por oscuridad, cualquiera que pueda alimentar números en su función (incluso si mantiene la función en secreto) podría resolverlo con bastante facilidad.
Ofuscarlo con una combinación de 2 o 3 métodos simples:
- XOR
- mezclar bits individuales
- convertir a representación modular (D.Knuth, Vol. 2, Capítulo 4.3.2)
- elija 32 (o 64) subconjuntos de bits superpuestos y bits XOR en cada subconjunto (bits de paridad de subconjuntos)
- representarlo en sistema numérico de longitud variable y dígitos aleatorios
- elija un par de enteros impares
y
que sean inversos multiplicativos entre sí (módulo 2 32 ), luego multiplique porx
para ofuscar y multiplique pory
para restaurar, todas las multiplicaciones son módulo 2 32 (fuente: "Un uso práctico de multiplicativo inversas "por Eric Lippert )
El método del sistema numérico de longitud variable no obedece por sí solo a su requisito de "progresión". Siempre produce progresiones aritméticas cortas. Pero cuando se combina con algún otro método, da buenos resultados.
Lo mismo es cierto para el método de representación modular.
Aquí está el ejemplo de código C ++ para 3 de estos métodos. El ejemplo de fragmentos aleatorios puede usar máscaras y distancias diferentes para ser más impredecible. Otros 2 ejemplos son buenos para números pequeños (solo para dar la idea). Deben extenderse para ofuscar todos los valores enteros correctamente.
// *** Numberic system base: (4, 3, 5) -> (5, 3, 4)
// In real life all the bases multiplied should be near 2^32
unsigned y = x/15 + ((x/5)%3)*4 + (x%5)*12; // obfuscate
unsigned z = y/12 + ((y/4)%3)*5 + (y%4)*15; // restore
// *** Shuffle bits (method used here is described in D.Knuth''s vol.4a chapter 7.1.3)
const unsigned mask1 = 0x00550055; const unsigned d1 = 7;
const unsigned mask2 = 0x0000cccc; const unsigned d2 = 14;
// Obfuscate
unsigned t = (x ^ (x >> d1)) & mask1;
unsigned u = x ^ t ^ (t << d1);
t = (u ^ (u >> d2)) & mask2;
y = u ^ t ^ (t << d2);
// Restore
t = (y ^ (y >> d2)) & mask2;
u = y ^ t ^ (t << d2);
t = (u ^ (u >> d1)) & mask1;
z = u ^ t ^ (t << d1);
// *** Subset parity
t = (x ^ (x >> 1)) & 0x44444444;
u = (x ^ (x << 2)) & 0xcccccccc;
y = ((x & 0x88888888) >> 3) | (t >> 1) | u; // obfuscate
t = ((y & 0x11111111) << 3) | (((y & 0x11111111) << 2) ^ ((y & 0x22222222) << 1));
z = t | ((t >> 2) ^ ((y >> 2) & 0x33333333)); // restore
Si xor
es aceptable para todo menos inferir F(y)
dado x
F(x)
entonces creo que puedes hacer eso con una sal . Primero elige una función secreta de un solo sentido. Por ejemplo S(s) = MD5(secret ^ s)
. Entonces F(x) = (s, S(s) ^ x)
donde s
se elige aleatoriamente. Lo escribí como una tupla, pero puedes combinar las dos partes en un entero, por ejemplo, F(x) = 10000 * s + S(s) ^ x
. El desencriptado extrae la sal de nuevo y usa F''(F(x)) = S(extract s) ^ (extract S(s)^x)
. Dadas x
y F(x)
puede ver s
(aunque está ligeramente ofuscado) y puede inferir S(s)
pero para otro usuario y
con una sal aleatoria diferente t
el usuario que conoce F(x)
no puede encontrar S(t)