Tom Butcher 8f2d3a2489 Add initial project structure for DES Key Cracker
- Created .gitignore to exclude build artifacts and dependencies.
- Added package.json and package-lock.json for project dependencies and scripts.
- Included pnpm workspace configuration for managing packages.
- Implemented TypeScript configuration in tsconfig.json.
- Added README.md with project description and usage instructions.
- Introduced native code for DES encryption and decryption in C/C++.
- Created initial decoded data structure for handling scan results.
- Established basic file structure for decoded outputs and native builds.
2026-09-19 16:10:35 +01:00

256 lines
9.6 KiB
Objective-C

#import <Foundation/Foundation.h>
#import <Metal/Metal.h>
#import <stdio.h>
#import <stdlib.h>
#import <string.h>
enum { kMaxHits = 256, kThreadsPerGroup = 256, kParamsSize = 104 };
typedef struct {
uint64_t index;
uint64_t key;
uint64_t plain;
} Hit;
static void fail(const char *msg) {
fprintf(stderr, "%s\n", msg);
exit(2);
}
static uint64_t parse_hex_u64(const char *hex) {
const char *p = hex;
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) p += 2;
return strtoull(p, NULL, 16);
}
static NSDictionary<NSString *, NSString *> *parse_opts(int argc, const char **argv) {
NSMutableDictionary *opts = [NSMutableDictionary dictionary];
for (int i = 2; i + 1 < argc; i += 2) {
if (strncmp(argv[i], "--", 2) != 0) {
i -= 1;
continue;
}
opts[@(argv[i] + 2)] = @(argv[i + 1]);
}
return opts;
}
static void json_escape(const char *src, char *dst, size_t dstsz) {
size_t o = 0;
for (size_t i = 0; src[i] && o + 2 < dstsz; i++) {
unsigned char c = (unsigned char)src[i];
if (c == '"' || c == '\\') {
dst[o++] = '\\';
dst[o++] = (char)c;
} else if (c < 32) {
dst[o++] = '?';
} else {
dst[o++] = (char)c;
}
}
dst[o] = 0;
}
static void write_params(void *dst, uint64_t start, uint32_t key_len, uint32_t charset_len,
uint32_t pad_byte, uint32_t fill_count, uint32_t batch_count,
uint64_t target, const uint64_t *fills, int nfills) {
uint8_t *p = dst;
memcpy(p, &start, 8); p += 8;
memcpy(p, &key_len, 4); p += 4;
memcpy(p, &charset_len, 4); p += 4;
memcpy(p, &pad_byte, 4); p += 4;
memcpy(p, &fill_count, 4); p += 4;
memcpy(p, &batch_count, 4); p += 4;
uint32_t z = 0;
memcpy(p, &z, 4); p += 4;
memcpy(p, &target, 8); p += 8;
for (int i = 0; i < 8; i++) {
uint64_t f = i < nfills ? fills[i] : 0;
memcpy(p, &f, 8);
p += 8;
}
}
static NSArray<id<MTLDevice>> *all_devices(void) {
NSArray<id<MTLDevice>> *devs = MTLCopyAllDevices();
if (devs && devs.count > 0) {
return devs;
}
id<MTLDevice> def = MTLCreateSystemDefaultDevice();
if (!def) {
return @[];
}
return @[def];
}
static id<MTLDevice> pick_device(NSDictionary<NSString *, NSString *> *opts) {
NSArray<id<MTLDevice>> *devs = all_devices();
if (devs.count == 0) {
fail("no Metal GPU");
}
int idx = opts[@"device"] ? opts[@"device"].intValue : 0;
if (idx < 0 || idx >= (int)devs.count) {
idx = 0;
}
return devs[(NSUInteger)idx];
}
static void run_devices(void) {
NSArray<id<MTLDevice>> *devs = all_devices();
printf("[");
for (NSUInteger i = 0; i < devs.count; i++) {
char name[512];
json_escape(devs[i].name.UTF8String ?: "Metal GPU", name, sizeof(name));
uint64_t mem = 0;
if ([devs[i] respondsToSelector:@selector(recommendedMaxWorkingSetSize)]) {
mem = (uint64_t)devs[i].recommendedMaxWorkingSetSize;
}
printf("%s{\"id\":%lu,\"name\":\"%s\",\"type\":\"metal\",\"memory\":%llu}",
i ? "," : "", (unsigned long)i, name, (unsigned long long)mem);
}
printf("]\n");
}
static id<MTLLibrary> compile_library(id<MTLDevice> device, NSString *path) {
NSError *err = nil;
NSString *src = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&err];
if (!src) {
fprintf(stderr, "cannot read shader: %s\n", err.localizedDescription.UTF8String);
exit(2);
}
MTLCompileOptions *copts = [[MTLCompileOptions alloc] init];
id<MTLLibrary> lib = [device newLibraryWithSource:src options:copts error:&err];
if (!lib) {
fprintf(stderr, "Metal compile failed:\n%s\n", err.localizedDescription.UTF8String);
exit(2);
}
return lib;
}
static void run_selftest(id<MTLDevice> device, id<MTLLibrary> lib) {
NSError *err = nil;
id<MTLFunction> fn = [lib newFunctionWithName:@"des_known_answer"];
id<MTLComputePipelineState> pipe = [device newComputePipelineStateWithFunction:fn error:&err];
if (!pipe) fail("failed to build DES known-answer pipeline");
id<MTLCommandQueue> queue = [device newCommandQueue];
id<MTLBuffer> out = [device newBufferWithLength:16 options:MTLResourceStorageModeShared];
id<MTLCommandBuffer> cmd = [queue commandBuffer];
id<MTLComputeCommandEncoder> enc = [cmd computeCommandEncoder];
[enc setComputePipelineState:pipe];
[enc setBuffer:out offset:0 atIndex:0];
[enc dispatchThreads:MTLSizeMake(1, 1, 1) threadsPerThreadgroup:MTLSizeMake(1, 1, 1)];
[enc endEncoding];
[cmd commit];
[cmd waitUntilCompleted];
uint64_t *words = out.contents;
uint64_t expected = 0x85E813540F0AB405ULL;
uint64_t plain = 0x0123456789ABCDEFULL;
if (words[0] != expected || words[1] != plain) {
fprintf(stderr, "DES self-test failed ct=%016llx expected=%016llx roundtrip=%016llx\n",
words[0], expected, words[1]);
exit(2);
}
fprintf(stderr, "metal_selftest ok gpu=%s\n", device.name.UTF8String);
}
static void run_brute(id<MTLDevice> device, id<MTLLibrary> lib, NSDictionary<NSString *, NSString *> *opts) {
NSError *err = nil;
id<MTLFunction> fn = [lib newFunctionWithName:@"des_brute"];
id<MTLComputePipelineState> pipe = [device newComputePipelineStateWithFunction:fn error:&err];
if (!pipe) fail("failed to build DES brute pipeline");
id<MTLCommandQueue> queue = [device newCommandQueue];
NSString *keyLenStr = opts[@"key-len"] ?: @"0";
NSString *charsetStr = opts[@"charset"] ?: @"";
NSString *padStr = opts[@"pad"] ?: @"00";
NSString *targetStr = opts[@"target"] ?: @"0";
NSString *startStr = opts[@"start"] ?: @"0";
NSString *countStr = opts[@"count"] ?: @"0";
NSString *batchStr = opts[@"batch"] ?: @"16777216";
uint32_t key_len = (uint32_t)keyLenStr.intValue;
const char *cs = charsetStr.UTF8String;
uint32_t charset_len = (uint32_t)strlen(cs);
uint32_t pad_byte = (uint32_t)(parse_hex_u64(padStr.UTF8String) & 0xFF);
uint64_t target = parse_hex_u64(targetStr.UTF8String);
uint64_t start = strtoull(startStr.UTF8String, NULL, 10);
uint64_t count = strtoull(countStr.UTF8String, NULL, 10);
uint64_t batch = strtoull(batchStr.UTF8String, NULL, 10);
if (key_len == 0 || charset_len == 0 || count == 0) fail("brute requires --key-len --charset --count");
uint64_t fills[8] = {0};
int nfills = 0;
NSString *fillsStr = opts[@"fills"] ?: @"0000000000000000";
NSArray *parts = [fillsStr componentsSeparatedByString:@","];
for (NSString *part in parts) {
if (nfills >= 8) break;
fills[nfills++] = parse_hex_u64(part.UTF8String);
}
id<MTLBuffer> paramsBuf = [device newBufferWithLength:kParamsSize options:MTLResourceStorageModeShared];
id<MTLBuffer> charsetBuf = [device newBufferWithBytes:cs length:charset_len options:MTLResourceStorageModeShared];
id<MTLBuffer> hitsBuf = [device newBufferWithLength:kMaxHits * sizeof(Hit) options:MTLResourceStorageModeShared];
id<MTLBuffer> countBuf = [device newBufferWithLength:4 options:MTLResourceStorageModeShared];
uint64_t done = 0;
CFAbsoluteTime t0 = CFAbsoluteTimeGetCurrent();
while (done < count) {
uint64_t n = batch < (count - done) ? batch : (count - done);
*(uint32_t *)countBuf.contents = 0;
write_params(paramsBuf.contents, start + done, key_len, charset_len, pad_byte,
(uint32_t)nfills, (uint32_t)n, target, fills, nfills);
id<MTLCommandBuffer> cmd = [queue commandBuffer];
id<MTLComputeCommandEncoder> enc = [cmd computeCommandEncoder];
[enc setComputePipelineState:pipe];
[enc setBuffer:paramsBuf offset:0 atIndex:0];
[enc setBuffer:charsetBuf offset:0 atIndex:1];
[enc setBuffer:hitsBuf offset:0 atIndex:2];
[enc setBuffer:countBuf offset:0 atIndex:3];
NSUInteger groups = (NSUInteger)((n + kThreadsPerGroup - 1) / kThreadsPerGroup);
[enc dispatchThreadgroups:MTLSizeMake(groups, 1, 1)
threadsPerThreadgroup:MTLSizeMake(kThreadsPerGroup, 1, 1)];
[enc endEncoding];
[cmd commit];
[cmd waitUntilCompleted];
uint32_t hitn = *(uint32_t *)countBuf.contents;
if (hitn > kMaxHits) hitn = kMaxHits;
Hit *hits = hitsBuf.contents;
for (uint32_t i = 0; i < hitn; i++) {
printf("{\"index\":%llu,\"key_hex\":\"%016llx\",\"plain_hex\":\"%016llx\"}\n",
hits[i].index, hits[i].key, hits[i].plain);
fflush(stdout);
}
done += n;
double elapsed = CFAbsoluteTimeGetCurrent() - t0;
if (elapsed < 1e-6) elapsed = 1e-6;
fprintf(stderr, "gpu %llu/%llu %.0f keys/s\n", done, count, done / elapsed);
}
}
int main(int argc, const char **argv) {
if (argc < 2) {
fail("usage: med_gpu devices | selftest --shader PATH | brute --shader PATH [options]");
}
@autoreleasepool {
if (strcmp(argv[1], "devices") == 0) {
run_devices();
return 0;
}
NSDictionary *opts = parse_opts(argc, argv);
NSString *shader = opts[@"shader"];
if (!shader) fail("missing --shader");
id<MTLDevice> device = pick_device(opts);
id<MTLLibrary> lib = compile_library(device, shader);
if (strcmp(argv[1], "selftest") == 0) {
run_selftest(device, lib);
} else if (strcmp(argv[1], "brute") == 0) {
run_selftest(device, lib);
run_brute(device, lib, opts);
} else {
fail("unknown command");
}
}
return 0;
}