Skip to content

Commit 828312c

Browse files
committed
perf: improve speed of cracker.c
- more inteligent secret key generation - use OpenMP for multithreading - leave old cracker, because it's simple - update old cracker to new coding style
1 parent a57f4a5 commit 828312c

File tree

3 files changed

+298
-32
lines changed

3 files changed

+298
-32
lines changed

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,15 @@ if (BUILD_MISC_TESTS)
529529
add_executable(save-generator
530530
other/fun/save-generator.c)
531531
target_link_modules(save-generator toxcore misc_tools)
532+
533+
add_executable(cracker
534+
other/fun/cracker.c)
535+
target_link_modules(cracker ${LIBSODIUM_LIBRARIES})
536+
find_package(OpenMP)
537+
if(OpenMP_C_FOUND)
538+
target_link_libraries(cracker OpenMP::OpenMP_C)
539+
endif()
540+
532541
add_executable(afl_toxsave
533542
testing/afl_toxsave.c)
534543
target_link_modules(afl_toxsave toxcore)

other/fun/cracker.c

Lines changed: 205 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,153 @@
99
* Will try to find a public key starting with: ABCDEF
1010
*/
1111

12+
#include <inttypes.h>
13+
#include <stdbool.h>
1214
#include <stdlib.h>
1315
#include <stdio.h>
1416
#include <string.h>
17+
#include <time.h>
1518

1619
/* Sodium includes*/
1720
#include <sodium/crypto_scalarmult_curve25519.h>
1821
#include <sodium/randombytes.h>
1922

20-
#include "../../testing/misc_tools.h"
21-
#include "../../toxcore/ccompat.h"
23+
#define KEY_LEN 32
24+
// Maximum number of bytes this program can crack in one run
25+
#define MAX_CRACK_BYTES 8
26+
// Maximum length of hex encoded prefix
27+
#define MAX_HEX_PREFIX_LEN (MAX_CRACK_BYTES * 2)
2228

23-
static void print_key(uint8_t *client_id)
24-
{
25-
uint32_t j;
29+
#if defined(_OPENMP)
30+
#include <omp.h>
31+
#define NUM_THREADS() ((unsigned) omp_get_max_threads())
32+
#else
33+
#define NUM_THREADS() (1U)
34+
#endif
2635

27-
for (j = 0; j < 32; j++) {
36+
static void print_key(const uint8_t *client_id)
37+
{
38+
for (uint32_t j = 0; j < 32; j++) {
2839
printf("%02X", client_id[j]);
2940
}
3041
}
3142

43+
/// bytes needs to be at least (hex_len+1)/2 long
44+
static size_t hex_string_to_bin(const char *hex_string, size_t hex_len, uint8_t *bytes)
45+
{
46+
size_t i;
47+
const char *pos = hex_string;
48+
// make even
49+
50+
for (i = 0; i < hex_len / 2; ++i, pos += 2) {
51+
uint8_t val;
52+
53+
if (sscanf(pos, "%02hhx", &val) != 1) {
54+
return 0;
55+
}
56+
57+
bytes[i] = val;
58+
}
59+
60+
if (i * 2 < hex_len) {
61+
uint8_t val;
62+
63+
if (sscanf(pos, "%hhx", &val) != 1) {
64+
return 0;
65+
}
66+
67+
bytes[i] = (uint8_t)(val << 4);
68+
++i;
69+
}
70+
71+
return i;
72+
}
73+
74+
static size_t match_hex_prefix(const uint8_t *key, const uint8_t *prefix, size_t prefix_len)
75+
{
76+
size_t same = 0;
77+
uint8_t diff = 0;
78+
size_t i;
79+
80+
for (i = 0; i < prefix_len / 2; i++) {
81+
diff = key[i] ^ prefix[i];
82+
83+
// First check high nibble
84+
if ((diff & 0xF0) == 0) {
85+
same++;
86+
}
87+
88+
// Then low nibble
89+
if (diff == 0) {
90+
same++;
91+
} else {
92+
break;
93+
}
94+
}
95+
96+
// check last high nibble
97+
if ((prefix_len % 2) && diff == 0) {
98+
diff = key[i] ^ prefix[i];
99+
100+
// First check high nibble
101+
if ((diff & 0xF0) == 0) {
102+
same++;
103+
}
104+
}
105+
106+
return same;
107+
}
108+
109+
static void cracker_core(uint64_t range_start, uint64_t range_end, uint64_t range_offs, uint64_t priv_key_shadow[4],
110+
uint32_t *longest_match, uint8_t hex_prefix[MAX_CRACK_BYTES], size_t prefix_chars_len)
111+
{
112+
#pragma omp parallel for firstprivate(priv_key_shadow) shared(longest_match, range_start, range_end, range_offs, hex_prefix, prefix_chars_len) schedule(static) default(none)
113+
114+
for (uint64_t batch = range_start; batch < range_end; batch++) {
115+
uint8_t *priv_key = (uint8_t *) priv_key_shadow;
116+
/*
117+
* We can't use the first and last bytes because they are masked in
118+
* curve25519. Offset by 16 bytes to get better alignment.
119+
*/
120+
uint64_t *counter = priv_key_shadow + 2;
121+
/*
122+
* Add to `counter` instead of assign here, to preservere more randomness on short runs
123+
* There can be an intentional overflow in `batch + range_offs`
124+
*/
125+
*counter += batch + range_offs;
126+
uint8_t pub_key[KEY_LEN] = {0};
127+
128+
crypto_scalarmult_curve25519_base(pub_key, priv_key);
129+
130+
const unsigned matching = (unsigned) match_hex_prefix(pub_key, hex_prefix, prefix_chars_len);
131+
132+
// Global compare and update
133+
uint32_t l_longest_match;
134+
#pragma omp atomic read
135+
l_longest_match = *longest_match;
136+
137+
if (matching > l_longest_match) {
138+
#pragma omp atomic write
139+
*longest_match = matching;
140+
141+
#pragma omp critical
142+
{
143+
printf("%u chars matching: \n", matching);
144+
printf("Public key: ");
145+
print_key(pub_key);
146+
printf("\nSecret key: ");
147+
print_key(priv_key);
148+
printf("\n");
149+
}
150+
}
151+
}
152+
}
153+
154+
void print_stats(double seconds_passed, double keys_tried)
155+
{
156+
printf("Runtime: %10lus, Keys tried %e/%e, Calculating %e keys/s\n",
157+
(unsigned long) seconds_passed, keys_tried, (double) UINT64_MAX, keys_tried / seconds_passed);
158+
}
32159

33160
int main(int argc, char *argv[])
34161
{
@@ -37,43 +164,89 @@ int main(int argc, char *argv[])
37164
return 0;
38165
}
39166

40-
long long unsigned int num_tries = 0;
167+
const size_t prefix_chars_len = strlen(argv[1]);
168+
169+
/*
170+
* If you can afford the hardware to crack longer prefixes, you can probably
171+
* afford to rewrite this program.
172+
*/
173+
if (prefix_chars_len > MAX_HEX_PREFIX_LEN) {
174+
printf("Finding a key with more than 16 hex chars as prefix is not supported\n");
175+
return 1;
176+
}
177+
178+
uint8_t hex_prefix[MAX_CRACK_BYTES] = {0};
41179

42-
uint32_t len = strlen(argv[1]) / 2;
43-
unsigned char *key = hex_string_to_bin(argv[1]);
44-
uint8_t pub_key[32], priv_key[32], c_key[32];
180+
const size_t prefix_len = hex_string_to_bin(argv[1], prefix_chars_len, hex_prefix);
45181

46-
if (len > 32) {
47-
len = 32;
182+
if (prefix_len == 0) {
183+
printf("Invalid hex key specified\n");
184+
return 1;
48185
}
49186

50-
memcpy(c_key, key, len);
51-
free(key);
52-
randombytes(priv_key, 32);
187+
printf("Searching for key with prefix: %s\n", argv[1]);
53188

54-
while (1) {
55-
crypto_scalarmult_curve25519_base(pub_key, priv_key);
56-
uint32_t i;
189+
time_t start_time = time(NULL);
57190

58-
if (memcmp(c_key, pub_key, len) == 0) {
59-
break;
60-
}
191+
// Declare private key bytes as uint64_t[4] so we can lower the alignment without problems
192+
uint64_t priv_key_shadow[KEY_LEN / 8];
193+
uint8_t *priv_key = (uint8_t *) priv_key_shadow;
194+
// Put randomness into the key
195+
randombytes(priv_key, KEY_LEN);
196+
uint32_t longest_match = 0;
61197

62-
for (i = 32; i != 0; --i) {
63-
priv_key[i - 1] += 1;
64198

65-
if (priv_key[i - 1] != 0) {
66-
break;
67-
}
199+
// Finishes a batch every ~10s on my PC
200+
const uint64_t batch_size = (UINT64_C(1) << 18) * NUM_THREADS();
201+
202+
// calculate remaining batch that doesn't fit the main loop
203+
const uint64_t rem_batch_size = UINT64_MAX % batch_size;
204+
205+
const uint64_t rem_start = UINT64_MAX - rem_batch_size - 1;
206+
207+
cracker_core(rem_start, UINT64_MAX, 1, priv_key_shadow, &longest_match, hex_prefix, prefix_chars_len);
208+
209+
double seconds_passed = difftime(time(NULL), start_time);
210+
double old_seconds_passed = seconds_passed;
211+
212+
// Reduce time to first stats output
213+
print_stats(seconds_passed, rem_batch_size + 1);
214+
215+
if (longest_match >= prefix_chars_len) {
216+
printf("Found matching prefix, exiting...\n");
217+
return 0;
218+
}
219+
220+
221+
for (uint64_t tries = 0; tries < rem_start; tries += batch_size) {
222+
cracker_core(tries, tries + batch_size, 0, priv_key_shadow, &longest_match, hex_prefix, prefix_chars_len);
223+
224+
seconds_passed = difftime(time(NULL), start_time);
225+
// Use double type to avoid overflow in addition, we don't need precision here anyway
226+
double keys_tried = ((double) tries) + rem_batch_size + 1;
227+
228+
if (longest_match >= prefix_chars_len) {
229+
print_stats(seconds_passed, keys_tried);
230+
printf("Found matching prefix, exiting...\n");
231+
return 0;
68232
}
69233

70-
++num_tries;
234+
// Rate limit output
235+
if (seconds_passed - old_seconds_passed > 5.0) {
236+
old_seconds_passed = seconds_passed;
237+
print_stats(seconds_passed, keys_tried);
238+
fflush(stdout);
239+
}
71240
}
72241

73-
printf("Public key:\n");
74-
print_key(pub_key);
75-
printf("\nPrivate key:\n");
242+
printf("Congrats future person who successfully searched a key space of 2^64\n");
243+
uint64_t *counter = priv_key_shadow + 2;
244+
*counter = 0;
245+
printf("Didn't find anything from:\n");
246+
print_key(priv_key);
247+
printf("\nto:\n");
248+
*counter = UINT64_MAX;
76249
print_key(priv_key);
77-
printf("\n %llu keys tried\n", num_tries);
78-
return 0;
250+
printf("\n");
251+
return 2;
79252
}

other/fun/cracker_simple.c

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/* Public key cracker.
2+
*
3+
* Can be used to find public keys starting with specific hex (ABCD) for example.
4+
*
5+
* NOTE: There's probably a way to make this faster.
6+
*
7+
* Usage: ./cracker ABCDEF
8+
*
9+
* Will try to find a public key starting with: ABCDEF
10+
*/
11+
12+
#include <stdlib.h>
13+
#include <stdio.h>
14+
#include <string.h>
15+
16+
/* Sodium includes*/
17+
#include <sodium/crypto_scalarmult_curve25519.h>
18+
#include <sodium/randombytes.h>
19+
20+
#include "../../testing/misc_tools.h"
21+
#include "../../toxcore/ccompat.h"
22+
23+
// Secret key and public key length
24+
#define KEY_LEN 32
25+
26+
static void print_key(const uint8_t *client_id)
27+
{
28+
for (int j = 0; j < KEY_LEN; ++j) {
29+
printf("%02X", client_id[j]);
30+
}
31+
}
32+
33+
34+
int main(int argc, char *argv[])
35+
{
36+
if (argc < 2) {
37+
printf("usage: ./cracker public_key(or beginning of one in hex format)\n");
38+
return 0;
39+
}
40+
41+
long long unsigned int num_tries = 0;
42+
43+
size_t len = strlen(argv[1]) / 2;
44+
unsigned char *key = hex_string_to_bin(argv[1]);
45+
uint8_t pub_key[KEY_LEN], priv_key[KEY_LEN], c_key[KEY_LEN];
46+
47+
if (len > KEY_LEN) {
48+
printf("%zu characters given, truncating to: %d\n", len * 2, KEY_LEN * 2);
49+
len = KEY_LEN;
50+
}
51+
52+
memcpy(c_key, key, len);
53+
free(key);
54+
randombytes(priv_key, KEY_LEN);
55+
56+
while (1) {
57+
crypto_scalarmult_curve25519_base(pub_key, priv_key);
58+
59+
if (memcmp(c_key, pub_key, len) == 0) {
60+
break;
61+
}
62+
63+
/*
64+
* We can't use the first and last bytes because they are masked in
65+
* curve25519. Using them would generate duplicate keys.
66+
*/
67+
for (int i = (KEY_LEN - 1); i > 1; --i) {
68+
priv_key[i - 1] += 1;
69+
70+
if (priv_key[i - 1] != 0) {
71+
break;
72+
}
73+
}
74+
75+
++num_tries;
76+
}
77+
78+
printf("Public key:\n");
79+
print_key(pub_key);
80+
printf("\nPrivate key:\n");
81+
print_key(priv_key);
82+
printf("\n %llu keys tried\n", num_tries);
83+
return 0;
84+
}

0 commit comments

Comments
 (0)