Un amigo me comento acerca un problema que había publicado un problema a ser resuelto para demostrar los skills de quienes lo resolvían, en estos slides voy a hacer el análisis y resolución del problema tal como se fue dando. Básicamente van a encontrar información acerca de xrange, hashlib, hexdigest y colisiones cíclicas de hashes sha1.
2. Presentación del problema
• Dada la siguiente función de Python:
• Al ejecutar la siguiente llamada se produce un Overflow
• my_func("0123456789012345678901234567890123456789",
9999999999999999)
def my_func(r, n):
for i in xrange(n): r = hashlib.sha1(r[:9]).hexdigest()
return r
3. Análisis del origen del problema
• Dado el trace de la excepción sabemos que la excepción se lanza al intentar
evaluar un objeto como entero pero que el mismo es demasiado grande
para convertirse a entero
• De los valores que pasamos solo uno de estos es un objeto que representa
un numero:
• Siendo que es xrange quien recibe ese valor buscamos su definición en la
librería de Python y encontramos que: The C implementation of Python
restricts all arguments to native C longs (“short” Python integers), and also
requires that the number of elements fit in a native C long. If a larger range
is needed, an alternate version can be crafted
4. Análisis de la lógica de la función
• Antes de analizar una resolución decidí analizar la lógica de la función
realizando algunas pruebas rápidas.
• Lo primero que identifico según la información enviada es que los
hashes aparentan ser sumatorios, por lo cual quiero comprobar esto
mediante pequeñas pruebas con algunas variaciones, de ser asi
podremos implementar una función recursiva.
5. Pruebas de logica
• Realice las siguientes pruebas para llegar a la conclusión de que es
sumatorio.
Algunos ejemplos concretos de la función:
my_func("0123456789012345678901234567890123456789", 0) = 0123456789012345678901234567890123456789
my_func("0123456789012345678901234567890123456789", 1) = 9a7149a5a7786bb368e06d08c5d77774eb43a49e
my_func("0123456789012345678901234567890123456789", 2) = 747c9a467f90021e5d213e2f6d27ccf82e25d0c9
my_func("9a7149a5a7786bb368e06d08c5d77774eb43a49e", 1) = 747c9a467f90021e5d213e2f6d27ccf82e25d0c9
#Prueba si los saltos son sumatorios, y con numeros pares
my_func("0123456789012345678901234567890123456789", 2) = 747c9a467f90021e5d213e2f6d27ccf82e25d0c9
my_func("0123456789012345678901234567890123456789", 4) = 09c39ceafeec24479c8598ee622a399b2e753e2b
my_func("747c9a467f90021e5d213e2f6d27ccf82e25d0c9", 2) = 09c39ceafeec24479c8598ee622a399b2e753e2b #correcto
#Al parecer sumatorios, probando si es logico con numeros impares
my_func("0123456789012345678901234567890123456789", 2) = 747c9a467f90021e5d213e2f6d27ccf82e25d0c9
my_func("0123456789012345678901234567890123456789", 5) = 14b51f5ee4250c7f238363b604dd5201ba2bbeb7
my_func("747c9a467f90021e5d213e2f6d27ccf82e25d0c9", 3) = 14b51f5ee4250c7f238363b604dd5201ba2bbeb7 #correcto
#Prueba con pares mas complejos
my_func("0123456789012345678901234567890123456789", 10) = 2dd637caf35019298ca1909e0ea644d5babadbff
my_func("0123456789012345678901234567890123456789", 98) = 168c666606aa8feb0c91a420e68cbe32d841eb5b
my_func("2dd637caf35019298ca1909e0ea644d5babadbff", 88) = 168c666606aa8feb0c91a420e68cbe32d841eb5b #correcto
#Prueba con impares mas complejos
my_func("0123456789012345678901234567890123456789", 27) = eef3f00f84909ec5e3177fd86d5e6550ac782d7f
my_func("0123456789012345678901234567890123456789", 319) = 92b1f1f3e8447874272de50f22ccd99b9baaebb9
my_func("eef3f00f84909ec5e3177fd86d5e6550ac782d7f", 319-27) = 92b1f1f3e8447874272de50f22ccd99b9baaebb9 #correcto
6. Pruebas de logica – análisis de resultados
• Realizando las pruebas de lógicas encontramos que los tiempos para
resolver linealmente este proceso serian demasiado largos, por otro
lado para generar los nuevos hashes solo se están utilizando los
primeros 9 elementos del string, por lo cual existe una amplia
posibilidad de que se repitan los resultados antes de llegar al numero
mas grande considerado como entero (mas detalle en un próximo
slide).
• Para avanzar con nuevas pruebas de lógicas hacemos una pequeña
modificación en el código que guarde hashes previos en una lista
temporal, si es factible encontrar colisiones repetitivas de los hashes
en un tiempo aceptable.
7. Investigacion
• Buscando información se encuentra acerca de los ciclos y algoritmos
para encontrarlos:
• https://en.wikipedia.org/wiki/Cycle_detection
• http://pythoncentral.io/hashing-strings-with-python/
8. Nuevo código para mas pruebas de lógica
import sys
import hashlib
def my_func(r, n):
temp = [] # lista temporal de hashes
strikes = 0 # utilizado para contar las repeticiones
if isinstance( n, long ): # esta prueba se hizo para probar una solucion
print "calling recursion" # recursiva, pero los tiempos se extienden
r = my_func(r, n-sys.maxint) # demasiado, y con el planteo original
n = sys.maxint # se supero el limite de recursividad
for i in xrange(n):
r = hashlib.sha1(r[:9]).hexdigest()
if r in temp: # chequeamos si ya esta en la lista temporal
print str(i) + " - " + r + " index " + str(temp.index(r))
# temp.index lo usamos para saber la distancia entre repeticiones
strikes += 1 # aumentamos los strikes
temp.append(r) # luego de imprimir en pantalla la info lo agregamos
if strikes == 3:
break # 3 strikes y cortamos la ejecucion
return r
9. Resultados de las nuevas pruebas
• Encontramos que si se encontró un ciclo de colisiones en un tiempo
aceptable:
Las colisiones se dan en rangos de 109019 repeticiones a partir de la ocurrencia 264088 (Es decir 155069 +
264088).
Se creo una pequeña modificación al código para verificar esta distancia:
10. Resolución del problema
• Tal como se menciona en la librería sabemos que en caso de necesitar
utilizar xrange con un numero superior a un int necesitamos crear un
desarrollo propio. Lo primero que debemos averiguar es según
nuestra plataforma cual es el numero máximo considerado como
entero, lo que se puede obtener mediante la librería “sys”:
• Solo con el punto anterior no bastaría, ya que los tiempos para correr
esta función con un numero como sys.maxint se extendería
demasiado. Por eso se busco otras alternativas y encontramos así que
existe colisiones a partir de la ocurrencia 264088 que condicen con el
hash 155069, es decir, a partir de 155069 cada 109019 hashes
calculados aparecen colisiones
11. Resolución del problema - 2
• Dados los puntos anteriores se modifico el código original para que
cualquier n mayor a 264088 (155069+109019) se reduzca a un
numero entero entre 155069 y 264087 y encontrar su colisión
correspondiente.
• La función utilizara para resolver el problema es:
• def my_func(r, n):
if n >= 264088: # 155069+109019
n = int((n-((n/109019)*109019))+109019)
for i in xrange(n):
r = hashlib.sha1(r[:9]).hexdigest()
return r
12. Resultado de la ejecucion
• El resultante a correr
my_func("0123456789012345678901234567890123456789",
9999999999999999) fue:
'd82fd2b1b9c82df7c199ad716033aeb33785d2a0’
13. Mejoras en performance
• Dado que cualquier n superior a 264088 es reducido a un numero
entre 155069 y 264087 los tiempos de ejecución se reducen
drásticamente.
• Ejecución previa modificación:
• Ejecución luego de la modificación: