Esta entrada presenta un pequeño programa, llamado crackpkcs12, que será útil para quien quiera recuperar la contraseña de un archivo PKCS12 (extensiones p12 o pfx) recurriendo a un ataque de diccionario. Además, pretende ser un ejemplo práctico de cómo sacar partido a nuestras máquinas multinúcleo programando aplicaciones que usen hilos...
Cuando alguien me pidió intentar recuperar la contraseña de un archivo PKCS12 que albergaba su certificado Ceres de la FNMT en lo primero que pensé fue en un script en bash que realizara un ataque de diccionario usando openssl. El resultado fue el siguiente:
Sencillo. Tuve suerte y funcionó. Rompió la contraseña con un primer diccionario de pocas palabras, pero tardó cinco horas y media. El comando "time" dejaba claro que este tiempo tal alto para el relativamente bajo número de palabras que tuvo que probar (en torno a un millón) se debe a las tareas de sistema. Me propuse escribir algo más eficiente en C, que utilizara directamente la librería SSL, en lugar de la aplicación de consola. Descargué las fuentes de openssl para encontrar las funciones que leen un archivo PKCS12 y validan su contraseña (d2i_PKCS12_bio y PKCS12_verify_mac respectivamente) y las invoqué directamente desde este programa:
Para mostraros el enorme ahorro de tiempo voy a poner un ejemplo con un diccionario de dos millones y medio de palabras situando la contraseña correcta en la última fila del archivo. La prueba se realizó en mi equipo, un procesador "Intel(R) Core(TM)2 Quad CPU Q8300 @ 2.50GHz" con 4 GB de RAM y GNU/Linux de 64 bits como sistema operativo (concretamente Ubuntu 10.10).
Primero, veamos cuánto tiempo emplea el script:
> time ./crackpkcs12.sh diccionario.txt certificado.p12 real 833m20.804s user 2m41.150s sys 783m55.440s
Más de trece horas. Fijaos en cómo la mayoría del tiempo se pierde en labores de sistema y el tiempo real del proceso es muy pequeño, lo que confirma que la tarea en sí se puede realizar mucho más rápidamente.
Ahora, el programa. Para compilarlo, se debe tener instalada la librería de desarrollo de SSL (libssl-dev en Debian y derivados). Después, simplemente copiar el código a un archivo crackpkcs12.c y compilarlo con:
gcc -O3 -lssl -lcrypto -o crackpkcs12 crackpkcs12.c
No la uso para el ejemplo, pero el programa tiene una opción -v que muestra mensajes a medida que se realiza cierto número de intentos y con -s podemos cambiar la frecuencia de estos mensajes (podéis lanzar el programa sin parámetros para ver un texto explicativo). Veamos cuanto tiempo emplea:
> time ./crackpkcs12 -d diccionario.txt certificado.p12 Password found: taller real 0m37.236s user 0m37.200s sys 0m0.010s
Menos de 40 segundos. Apenas tiempo de sistema. El siguiente paso es aprovechar mejor mi máquina puesto que esta búsqueda utilizaba solamente uno de los 4 núcleos de mi equipo. Me puse manos a la obra y mejoré el programa para utilizar hilos. Si todo iba bien, aún debía poder reducir significativamente el tiempo. Aquí tenéis el resultado:
Esta versión final paralelizada es la contenida en el paquete .tar.gz que podéis descargar de la página de Sourceforge. Como en el caso anterior, para compilarla se debe tener instalada la librería de desarrollo de SSL. Podéis utilizar dicho paquete y recurrir al tradicional:
./configure make
quedando el ejecutable en el directorio "src". La alternativa es copiar el código a un archivo crackpkcs12.c y ejecutar:
gcc -O3 -lpthread -lssl -lcrypto -o crackpkcs12 crackpkcs12.c
Sólo quedaba probarlo. Por defecto, el programa lanza un número de hilos igual al número de procesadores del equipo, en mi caso cuatro. Con el parámetro -t podemos lanzar el número de hilos que deseemos. Los números son los esperados, aproximadamente una cuarta parte que el script sin paralelizar:
> time ./crackpkcs12 -d diccionario.txt certificado.p12 Thread 1 - Password found: taller real 0m9.605s user 0m37.890s sys 0m0.050s
De las más de 13 horas del tiempo inicial del script hemos llegado a menos de 10 segundos y, además, para la próxima vez que tenga que recuperar la contraseña de un archivo PKCS12 tendré la certeza de que estoy aprovechando mi equipo al máximo. Además, confirmé que usar bash para ruptura de contraseñas no es la mejor idea, aunque a veces sea lo más rápido de programar.
El siguiente paso será añadir la posibilidad de usar fuerza bruta sin diccionario.

preguntA
yanmar0231 Octubre 2011 - 7:02pm
YA LO DESCARGUE.
Y AHORA COMO LO INSTALO???
GRACIAS
Compilacion en AIX 6.1 con gcc (III)
aixman219 Mayo 2011 - 2:04pm
Hola (perdon por el retraso),
Con esta version he obtenido los siguientes resultados en el mismo entorno anteriormente comentado:
Para el diccionario grande:
Por otro lado, y dado que tenía la posibilidad de probarlo en un servidor intel, tambien os dejo los datos de los test realizados en este servidor:
Ahí dejo estos resultados y que cada uno saque sus propias conclusiones :-)
Compilacion en AIX 6.1 con gcc (II)
aixman (no verificado)12 Mayo 2011 - 11:43am
Hola,
Con esta versión de crackpkcs12 ya compila y se ejecuta correctamente en Aix Unix. Para las pruebas he utilizado dos diccionarios; uno pequeño que contiene 19126 lineas (peque) y otro más grande de 869228 (grande).
La VM con Aix Unix 6.1 (6100-06-03-1048) posee 6 cores. En Aix cada core puede ejecutar 4 hilos de forma concurrente por lo que en el sistema aparecen 24 cpu logicas. La versión usada de gcc es la gcc-4.2.0-3.
El comando usado ha sido:
time ./crackpkcs12 -d /tmp/peque.txt /tmp/certificado.p12Resultados obtenidos con el diccionario pequeño (5 ejecuciones):
Resultados obtenidos con el diccionario grande (5 ejecuciones):
Con la herramienta nmon se observa que durante la ejecución del programa las 24 CPU Logicas están trabajando a tope:
La password del certificado, en ningun caso, la ha encontrado. Es decir, que ha leido todas las entradas de cada diccionario. Si comentáis el diccionario que estáis usando puedo usarlo para comparar "peras con peras" :-). O si conocéis otro "tocho-brutal" diccionario no tendría pruebas en testearlo.
Muchas gracias por vuestra ayuda y tiempo.
Prueba con esta nueva versión
Alfredo Esteban12 Mayo 2011 - 3:24pm
Hola Aixman:
¿Podrías probar con esta nueva versión? Además de la corrección para la compilación en powerpc contiene sugerencias de Andy.
Alfredo
Compilacion en AIX 6.1 con gcc (II)
aixman (no verificado)12 Mayo 2011 - 9:45am
Hola,
Con esta versión de crackpkcs12 ya compila y se ejecuta correctamente en Aix Unix. Para las pruebas he utilizado dos diccionarios; uno pequeño que contiene 19126 lineas (peque) y otro más grande de 869228 (grande).
La VM con Aix Unix 6.1 (6100-06-03-1048) posee 6 cores. En Aix cada core puede ejecutar 4 hilos de forma concurrente por lo que en el sistema aparecen 24 cpu logicas. La versión usada de gcc es la gcc-4.2.0-3.
El comando usado ha sido:
time ./crackpkcs12 -d /tmp/peque.txt /tmp/certificado.p12Resultados obtenidos con el diccionario pequeño (5 ejecuciones):
Resultados obtenidos con el diccionario grande (5 ejecuciones):
Con la herramienta nmon se observa que durante la ejecución del programa las 24 CPU Logicas están trabajando a tope:
La password del certificado, en ningun caso, la ha encontrado. Es decir, que ha leido todas las entradas de cada diccionario. Si comentáis el diccionario que estáis usando puedo usarlo para comparar "peras con peras" :-). O si conocéis otro "tocho-brutal" diccionario no tendría pruebas en testearlo.
Muchas gracias por vuestra ayuda y tiempo.
getopt devuelve un int, pero se almacena en un char
jorgegv10 Mayo 2011 - 7:03pm
La funcion getopt devuelve un int, pero en la linea 64 se asigna a un char (c), y probablemente por eso falla.
Prueba a quitar de la linea 55 la declaracion de c, y poner una nueva linea justo debajo que diga:
int c;
Con eso c recogerá el valor de getopt adecuadamente.
Compilacion en AIX 6.1 con gcc
aixman (no verificado)10 Mayo 2011 - 12:48pm
Hola,
Me ha llamado la atención este programa en C, y dado que estoy probando el rendimiento de un Power 7 con Aix 6.1 que tiene 12 cores me había preguntado si podría usarlo en dicha plataforma, y comprobar que tiempos maneja el Power 7.
Al compilarlo me aparece lo siguiente:
He comprobado que la línea 64 hace referencia a la lectura de las opciones de entrada del script:
c = getopt (argc, argv, "t:d:vs:")Aunque genera el binario, a la hora de ejecutarlo con sus opciones siempre aparece el mensaje de la funcion usage().
He visto que esto puede deberse a "It's platform- and compiler-dependent. For example, GCC on x86 Linux uses signed char by default, while GCC on PowerPC Linux uses unsigned char by default."
He intentado modificar el código pero mi conocimiento de C es NULL :-(
¿Es posible una modificación del codigo -que no requiera mucho tiempo- para su compilación bajo Aix Unix usando gcc-4.2.0-3?
Muchas gracias por vuestro tiempo y ayuda.
unsigned char
Alfredo Esteban10 Mayo 2011 - 10:15pm
Creo que el problema puede ser que el valor devuelto por getopt se recoge en una variable tipo "char". "char" en x86 tiene signo, mientras que en AIX creo que es un "unsigned char", con lo que la condición nunca se cumplirá.
Prueba con esta versión:
http://pastebin.com/6aXfJHyH
La variable c está definida
Alenog10 Mayo 2011 - 10:11pm
La variable c está definida como char, cuando debería estarlo como int. Ese cambio debería bastar para arreglarlo.
divisón del diccionario?
karmapolice10 Mayo 2011 - 10:44am
Se nota que estoy oxidado con el c. ¿Me puedes decir en que parte se "divide" el diccionario para que cada hilo de ejecución pruebe una parte y no prueben todos todas las palabras?
Lee la respuesta a Jonsito
Alfredo Esteban10 Mayo 2011 - 3:42pm
Tu duda está contestada en la respuesta a Jonsito, en los últimos párrafos (y en las líneas 202 a 204 del código).
Para comprobar que cada hilo llama solamente a las palabras que le corresponden se puede llamar al programa con el parámetro "-s 1". Antes de cada intento muestra un mensaje.
Alucinante
jonsito9 Mayo 2011 - 7:49pm
Un par de sugerencias:
- Puedes "paralelizar" aún más el proceso si en lugar de compartir la estructura p12 tienes una por cada thread. Además te ahorras los locks que consumen bastante cpu con el cambio de contexto
- Si además no tienes problemas de memoria, te sugiero que cambies el fgets(3) por un mapeado en memoria ( mmap(2) ) del fichero de diccionario
- Por último un detalle tonto: creo que tienes un bug que hace que los "n" threads no se "repartan" el diccionario, sino que cada uno se lo "come" entero...
Por lo demás, estupendo trabajo. Espero ansioso los resultados de un crackeo de "fuerza bruta" de contraseñas de "n" caracteres...
Cadad día estoy más de acuerdo con la recomendación de la UE de que se dejen de utilizar el standard pkcs12 como sistema de almacenamiento de certificados, y que se usen sólo como archivos de intercambio que es para lo que se definió originalmente :
PKCS #12: Personal Information Exchange Syntax Standard
Juan Antonio
Gracias Jonsito
Alfredo Esteban9 Mayo 2011 - 10:57pm
En primer lugar gracias por tus comentarios. Te respondo.
Estoy usando estructuras p12 distintas para cada hilo. Sobre los locks, en un programa que en condiciones normales va a ejecutarse durante horas pienso que es una pérdida asumible. No es en ese punto del código donde está el cuello de botella. Evitaba errores de openssl en el acceso simultáneo al archivo.
Hice el programa pensando en archivos de diccionario grandes, muy grandes. Un archivo de diccionario completo, incluyendo dígitos, varios idiomas, variaciones sobre las palabras, etc, puede ocupar más tamaño que la memoria estándar de un PC.
No veo el bug. Con el bucle que comienza con
for (i=0; i<wthread->id && stop==0; i++)avanzo hasta la fila id del hilo al comienzo de cada hilo. Después, con este bucle:
for (i=0; i<wthread->num_threads-1 && stop==0; i++)tras cada intento, adelanto tantas filas como hilos estamos usando sin realizar intento alguno.
Alfredo
De nada :-)
jonsito10 Mayo 2011 - 3:45pm
(no sé si has leído el mensaje que te mandé... )
Efectivamente: no haces intento alguno, pero sí que haces "n" fgets() que tiras a la papelera... las operaciones de E/S son siempre lo primero que hay que evitar al optimizar código
En cuanto al tema de la memoria... las bases de datos habitualmente manejan ficheros de gigas que superan con creces la capacidad de la memoria física. No debería ser muy complicado hacer un mmap del fichero, y recorrer éste mediante punteros en lugar de mediante E/S.
Juan Antonio
Sugerencia
Andy10 Mayo 2011 - 5:41pm
Disculpar que me entrometa.
Antes que nada quería felicitar a Alfredo Esteban por su excelente trabajo y agradecerle por compartirlo.
Con respecto a "fgets()" creo que lo mejor sería abrir una sola vez el fichero de diccionario en el programa principal y guardar el handle en una variable global estática.
Luego hacer una función separada "thread safe" que lea del fichero con fgets() utilizando el handle y devuelva una línea del mismo como resultado.
Finalmente desde el worker thread llamar a dicha función cuando se necesite una palabra del diccionario sin preocuparse por "saltarse" ninguna palabra.
De esa forma cada thread avanza a su propio ritmo, el fichero de diccionario se lee una sola vez, ahorras handles abiertos en el sistema operativo, y lo más importante: haces feliz a jonsito.
Un saludo,
Andy
La Ley de Amdahl
Alfredo Esteban10 Mayo 2011 - 9:53pm
Hombre, si se trata de hacer feliz a Jonsito, cambiamos el programa. No en vano le debemos tener drivers libres para el DNI electrónico :-)
Andy:
Creo que tu idea crea un bloqueo innecesario en la lectura del archivo. Hace que la lectura del mismo condicione la ejecución de las pruebas de contraseñas. Lee mi respuesta a Jonsito y entenderás por qué creo que estamos poniendo el foco en una parte del programa no tan relevante como os parece.
Jonsito:
Me viene a la cabeza la Ley de Amdahl. Y la podemos interpretar desde dos enfoques distintos que conducen a la misma conclusión.
Primer enfoque: Está claro que si paralelizamos una tarea en N hilos, como mucho podemos aspirar a tardar una n-ésima parte del tiempo que en la tarea sin paralelizar (es decir, se trataría de un algoritmo absolutamente paralelizable). Fíjate que en este caso estamos ya rozando ese rendimiento. Para que las mejoras en el programa paralelizable fueran significativas deberían serlo también en el programa sin paralelizar. Dicho de otro modo: Si este método de trocear el archivo fuera tan ineficiente, debería penalizar más la relación entre el tiempo que tarda el programa paralelizado y el que tarda sin paralelizar ... y no lo hace. El cociente es casi 1/4, que es lo máximo posible.
Segundo enfoque: Estamos intentando optimizar una parte del programa que representa una parte muy pequeña del total. Pongamos por ejemplo una ejecución para la que "time" devuelve estos valores:
Pues bien, el mismo programa eliminado todas las referencias a openssl y dejando solo el lanzamiento de los hilos y la lectura del diccionario con sus consiguientes fgets toma:
Menos de un 2,5% del tiempo real. Aunque consiguiéramos que estas tareas se hicieran el doble de rápido la mejora en el programa sería del 1,25%.
Sin embargo, tus argumentos me parecen razonables. ¿Por qué no ocasionarían una mejora drástica? Creo que la explicación es que parte de las optimizaciones que sugieres las realiza el SO a bajo nivel, aprovechando la memoria libre que linux utiliza precisamente para eso, para cachear los archivos que lee.
No sé qué opináis. Quizá se me escape algo.
En cualquier caso, está claro que las sugerencias de Jonsito son pertinentes y bienvenidas.
Alfredo
Posiblemente tengas razón...
jonsito10 Mayo 2011 - 11:25pm
Ciertamente, a nivel de sistema operativo, todas las funciones fxxx() son buffereadas; esto es solo hay un read() para todas las threads, y la E/S es la propia del sistema operativo...
Mi sugerencia iba por ahorrar el consumo de fgets(). básicamente se reduce a un "do { *a++=bufferedRead() } while (*a!='\n'); ( más o menos; puristas abstenerse :-). El ahorro que te propongo es eliminar los fgets() innecesarios
No he hecho los cálculos que me indicas: posiblemente tengas razón en que el tiempo que gasta en fgets() sea insignificante respecto del tiempo empleado en la comprobación de la contraseña. Pero despues de 20 años programando sistemas empotrados y de tiempo real, uno tiende a volverse paranoico, y rascar el ciclo de instrucción hasta en la operación más insignificante. Muchos pocos acaban siendo significativos cuando el número de iteracciones crecen
Como siga picándome de esta manera me veo escribiendo el código y optimizando la función de comprobación... No, en serio, me gusta bastante la implementación que propones; de hecho se me había pasado por la cabeza la idea algo parecido, pero no en base a diccionario, sino en plan "fuerza bruta" (probar todos las posibles combinaciones de "n" caracteres imprimibles)
Y en cuanto a lo de "contentarme"... ¿realmente estoy tan "picajoso?. Jooorl... voy a tener que hacérmelo mirar....
Venga, nos leemos. Saludetes
Juan Antonio
Unas pequeñas pruebas
jonsito12 Mayo 2011 - 10:44am
(btw: no sé por qué los tags <code> y </code> no me funcionan bien...)
Lectura de diccionario mediante fopen y fgets():
/* fgets.c */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> int main(int argc,char *argv[]) { FILE *f=NULL; char *buffer=NULL; buffer= calloc(1024,sizeof(char)); if (buffer==NULL) { perror("calloc() failed"); exit(1); } f=fopen(argv[1],"r"); if (f==NULL) { perror("fopen() failed"); exit(2); } while ( fgets(buffer,1024,f)!=NULL) printf("%s\n",buffer); fclose(f); free(buffer); exit(0); } Lectura de un diccionario mediante mmap+punteros a memoria:Ejecución sobre un diccionario de 60.000 palabras (unos 500Kbytes):
Resumen:
Usar mmap frente a fgets ahorra cerca de un 25% del tiempo de lectura del fichero
Es un ahorro apreciable, especialmente en ficheros grandes, pero como se comenta en el hilo, el ahorro real hay que buscarlo en la función de prueba de la clave.
Esto de ir en busca del clocktick perdido es demasiado adictivo...
Juan Antonio
Acerca de mmap
Alfredo Esteban13 Mayo 2011 - 6:35pm
Jonsito, tomo nota del ejemplo para una mejora del programa. Gracias de nuevo.
A raíz de este tema he estado leyendo sobre mmap y cómo se comporta con archivos que superan la memoria física de la máquina. mmap no implica cargar todo el archivo en memoria, sino mapearlo en ella. Hasta que no se produce el uso efectivo de bytes concretos del archivo no se "trae" a la memoria el contenido del archivo. Cuando usamos mmap y el archivo supera la memoria física lo que ocurre en la práctica es que el espacio del disco duro que alberga el archivo se comporta como espacio de swap. Conclusión: Como dijiste en uno de tus comentarios anteriores, no hay absoluamente nada que temer en usar mmap por muy grande que sea el archivo. Esta muy bien explicado en este enlace.
los tags
admin12 Mayo 2011 - 4:37pm
Sí, se rompe el formato cuando hay saltos de línea.
Trataré de arreglarlo, pero mientras tanto puedes (podéis) utilizar <pre> cuando haya saltos de línea en el código, que tiene igual efecto y funciona mejor.
Que nos os importe no visualizar el efecto; tengo que aprobar ese formato.
Es cuestión de enfoques
Andy10 Mayo 2011 - 11:29pm
Alfredo,
Creo que lo estás enfocando totalmente al revés. Pero vayamos por partes.
Con lo que dices del "bloqueo" al acceso al archivo, obviamente te refieres a que hay que poner el fgets() de la función "centralizada" dentro de un mutex. Claro que si.
EDIT: me acabo de dar cuenta que fgets() es thread-safe, por lo que no hace falta que uses semáforos en la función de lectura centralizada.
Obviamente el acceso a disco es mucho más lento que el acceso a memoria, por lo que si el tiempo de procesamiento de cada palabra del diccionario es mucho menor que lo que se tarda en leer la siguiente palabra desde el fichero, va a haber una cola de espera de hilos listos para continuar, pero sin palabras para poder hacerlo.
Sin embargo, nota que a lo sumo hay que esperar el tiempo equivalente a n-1 lecturas (si tienes mala suerte y estás al final de la cola y además todos los hilos quieren leer al mismo tiempo).
Sin embargo, tal y como tú lo haces, siempre tienes que esperar el tiempo equivalente a n-1 lecturas para "saltarte" las palabras que no son "tuyas".
Si no quieres utilizar mmap(), aprovecha que tenemos multitarea: no hace falta que la función de lectura centralizada se quede sin hacer nada esperando a que le pidan palabras. Se puede armar un buffer circular de X MB e ir leyendo asincrónicamente el fichero de palabras allí con otro worker thread, mientras que cuando llaman a la función devuelves las palabras desde memoria, que es más rápido. Tener un worker thread de I/O mientras tienes varios CPU intensive es ideal.
Por otro lado, hay una enorme ventaja en la lectura "centralizada", que es que los hilos de ejecución no tienen porqué estar sincronizados para maximizar la eficiencia.
Tal y como tienes el programa, no es óptimo si los hilos no procesan exactamente a la misma velocidad. Si te toca compartir core con un proceso "pesado", estás de mala suerte.
Exagerando un poco, si por ejemplo tienes 4 hilos y hay uno que va el doble de lento que los demás, los 3 primeros hilos van a consumir todas sus palabras de diccionario y terminar cuando el hilo restante vaya todavía por la mitad. Los hilos rápidos no van a "ayudar" al más lento. El programa acaba cuando termina el hilo más lento.
Si centralizas la lectura del fichero de diccionario, lo que pasará es que los 3 hilos más rápidos procesarán más palabras que el hilo más lento. O sea que en vez de quedarse sin hacer nada, los hilos más rápidos "ayudan" a los más lentos trabajando más. El programa termina cuando acaba el hilo más rápido (más lo que tarden en terminar con la palabra en curso los demás hilos).
Un saludo,
Andy
Gracias a ti también
Alfredo Esteban11 Mayo 2011 - 8:02pm
Voy a responderte sobre las dos opciones que tú sugieres:
1.- Crear una función común de lectura para hacer que solamente se haga un fgets de cada línea, de lo que se encargaría esa función: Con la implementación actual creo que, como bien ha explicado Jonsito, aunque para cada línea del archivo se produzca un fgets por hilo, el sistema operativo va a realizar una sola lectura que cacheará en memoria. Después, aunque nosotros vemos en el código un fgets, se trata de mover memoria.
2.- Crear una lectura asíncrona en otro hilo que llene un buffer del que los hilos tomarían las palabra: Me parece que aceleraría la ejecución, pero con el techo que marca la Ley de Amdahl. Siendo lo más optimista posible, asumiendo la paralelización total, ahorraríamos un 2,5% del tiempo. Y siendo realista, considerando que todo este proceso de gestionar el buffer llevará tambien su tiempo y puede retrasar alguno de los otros hilos (sobre todo si este hilo es el N+1 siendo N el número de núcleos), creo que la mejora que podrías alcanzar estará entorno al 1%.
Conclusión: Gracias por tus sugerencias Andy. En la primera lectura no entendí bien su alcance. Son pertinentes y bienvenidas también. Las incorporo a las tareas futuras pero creo que antes de atacar una optimización con un efecto que no considero muy significativo (¿te animas tú?) voy a implementar el ataque de fuerza bruta generando las contraseñas de N caracteres ... si es que no lo ha hecho ya Jonsito, que se estará conteniendo :-)
Alfredo
A veces me complico la vida demasiado
Andy11 Mayo 2011 - 11:20pm
Alfredo,
Después de pensarlo mucho, creo que crear una función de lectura centralizada es una complicación innecesaria. Quizás venga de no confiar demasiado en el sistema operativo.
Me explico: como puse en otro comentario, fgets() es thread-safe. Si aceptamos que funciona bien, entonces no hay necesidad de "saltar" palabras clave en los worker threads.
Simplemente utiliza fgets() para obtener una nueva palabra y deja que el SO regule el acceso concurrente desde varios hilos.
Te paso algunos extractos de código con modificaciones sugeridas:
EDIT: lo mejor será cortar y pegar en un editor de texto porque al menos en mi browser el código se ve con un ancho insuficiente por la identación de los comentarios.
El resto podria quedar igual.
Un cordial saludo,
Andy
Cierto
Alfredo Esteban12 Mayo 2011 - 2:03am
Buena idea. Esto simplifica el código. Incorporaré tus cambios al repositorio de subversion para próximas versiones (con una corrección dado que en el mensaje la variable "count" ya no tiene sentido porque ha perdido su valor de intentos acumulados).
Gracias.
También sirve de confirmación de mi tesis. Ejecuta tu versión y la mía repetidas veces y verás lo mismo que yo. Una levísima diferencia:
Media de tiempo real sin las mejoras: 0.985 s
Media de tiempo real con las mejoras: 0.984 s
Media de tiempo de usuario sin las mejoras: 3.890 s
Media de tiempo de usuario con las mejoras: 3.826 s
Media de tiempo de sistema sin las mejoras: 0.015 s
Media de tiempo de sistema con las mejoras: 0.033 s (más alto)
Alfredo
Anda, si es verdad!
Andy12 Mayo 2011 - 7:34am
No me había percatado de que utilizabas el valor de la variable count en el mensaje!!! Una cosa es optimizar y otra es perder funcionalidad. Déjalo como estaba si eso. Te pido disculpas por el despiste.
Habrá que hacer una prueba con un fichero de diccionario GRANDE para notar algunas diferencias, aunque de todos modos y como bien dices, las "mejoras" son mínimas.
El tiempo del sistema es más alto en la versión modificada... intentaré pensar qué significa...
Un cordial saludo,
Andy
Nueva versión
Alfredo Esteban12 Mayo 2011 - 2:55pm
Andy, he creado otra variable para el mensaje y listo. A ver si tengo un rato para hacer una prueba con un archivo grande y te cuento.
La versión la he dejado en subversion:
https://crackpkcs12.svn.sourceforge.net/svnroot/crackpkcs12/trunk/src/cr...