Generating random keys
In the end, our 6-digit PIN generator is unpredictable to an intruder, but not that unpredictable. Its limits must be understood: its numeric nature and its fixed, short length make it guessable sooner or later - like all PINs - given enough time and resources, even if we’ve done our best to avoid leaving the door wide open. Sometimes, a glass door, if closed, keeps many out - even if it’s not reinforced.
In any case, there it is - our glass door: slightly tinted, with a lock. It’s not impenetrable, but it’ll take more than a moment for an intruder to open it.
And that delay - if managed properly - can reinforce even that glass door: by adding delays between authentication attempts, sending alerts, or even limiting the number of attempts per connecting device.
Now comes the pending task of creating 128-bit keys - that’s 16 bytes - or, in Zig syntax: [16]u8.
We generate these keys at runtime:
getkey.zig |
|
const std = @import("std"); const print = std.debug.print; pub fn get_key(o_alloc: std.mem.Allocator, n_size: usize) ![]const u8 { const s_key = try o_alloc.alloc(u8, n_size); // generate bytes until the s_key buffer is full std.crypto.random.bytes(s_key); return s_key; } pub fn main() !void { const gpa = std.heap.page_allocator; const s_key = try get_key(gpa, 16); for (s_key) |c_x| print("{X} ", .{c_x}); print("\n", .{}); } |
|
$ zig run getkey.zig |
|
D3 F5 3B 59 96 BC 29 AF 24 EE 1E 85 61 DF 7 60 |
We’ve created a 16-byte key, and each time you run the program, it will print a different one.
We use a function from Zig’s standard library to generate the random numbers:
|
// generate bytes until the s_key buffer is full std.crypto.random.bytes(s_key); |
There’s also a detail you might’ve noticed. Even though we request memory using the allocator, we never free it manually.
We could, but in this case, it’s not necessary. The program we’re running is completely linear. The small amount of memory we allocate - while technically a leak - will be released when the main function finishes, no matter what.
In such a short test “script”, it’s not a problem, but be careful - many “test scripts” evolve or get picked up a day or a week later and end up becoming part of something more serious. So yes - it might not be such a bad idea to include free anyway, just in case.
The player, after testing the program, runs it on the terminal of the primordial AI:
genvac.zig |
|
const std = @import("std"); const print = std.debug.print; // get a PIN for an index fn get_pin4index(n_x: usize) usize { const n_a = n_x + 1; // pentagonal number formula return (n_a * (3 * n_a - 1)) / 2 % 1_000_000; } // convert the number to a zero-padded string fn left_pad_zeros(n_a: usize, n_len: usize) [n_len]u8 { comptime var s_resp: [n_len]u8 = undefined; for (0..n_len) |n_ix| { s_resp[n_ix] = '0'; } var n_x: usize = n_a; if (n_x == 0) return s_resp; for (0..s_resp.len - 1) |n_i| { const n_pos = s_resp.len - 1 - n_i; const n_digit = n_x % 10; s_resp[n_pos] = @as(u8, '0' + n_digit); n_x = n_x / 10; } return s_resp; } fn get_permuted_index(n_i: usize, n_items: usize) usize { // simple deterministic seed return (n_i * 524287) % n_items; } pub fn get_key(o_alloc: std.mem.Allocator, n_size: usize) ![]const u8 { const s_key = try o_alloc.alloc(u8, n_size); // generate bytes until the s_key buffer is full std.crypto.random.bytes(s_key); return s_key; } // declare the maximum PIN length const N_PIN_LENGTH = 6; const N_START = 100; // declare the total number of PINs const N_TOTAL_PINS = 1000; // key length in bytes const N_KEY_LENGTH = 16; pub fn main() !void { comptime var a_pins: [N_TOTAL_PINS][N_PIN_LENGTH]u8 = undefined; comptime { // add 1000 more for the call to get_permuted_index @setEvalBranchQuota(15000); for (0..a_pins.len) |n_i| { // get the PIN based on the index const n_pin = get_pin4index(n_i + N_START); // get a new position in the array const n_pos = get_permuted_index(n_i, N_TOTAL_PINS); // convert the PIN to a zero-padded string a_pins[n_pos] = left_pad_zeros(n_pin, N_PIN_LENGTH); } } const gpa = std.heap.page_allocator; // print the first 5 and // last 5 PINs and keys to check the result for (a_pins[0..5].*, 0..) |s_pin, n_i| { const s_key = try get_key(gpa, N_KEY_LENGTH); defer gpa.free(s_key); print("{}:{s}:{X}", .{ n_i + 1, s_pin, s_key }); print("\n", .{}); } print("...\n", .{}); const n_last = a_pins.len; for (a_pins[n_last - 5 .. n_last].*, n_last - 5..) |s_pin, n_i| { const s_key = try get_key(gpa, N_KEY_LENGTH); defer gpa.free(s_key); print("{}:{s}:{X}", .{ n_i + 1, s_pin, s_key }); print("\n", .{}); } } |
|
$ zig run genvac.zig |
|
1:015251:81F9F4BF7C7F2297A5CEDAF2CCA1A53D 2:057302:11F940ABAAB81B5E2757703E5B655B9F 3:048540:8499E016EF4C0FBEFE8C939789353EEB 4:088965:13DA40F342C781229A4F32314FA3392E 5:078577:1E72EAC88941085666357B2AAE453842 ... 996:057801:3D22AD46BEBABC4A5A2CB24BE27BA80A 997:065417:76964ED5F087DFA7DE9DBC952B248D39 998:079720:9BA1B4BC56B743F8A2C8ACF26ABDF853 999:043210:80893DBA936F860AE454C6365CC5589A 1000:055887:FE35AFFE7A4F183C0334903663E4FDBE |
The AI processes the PIN and key data generated for each robot. It interpolates them using commands that overwrite the configurations that led to the disaster.
Immediately, the AI starts restoring the global system's backup.
The rest… is the story of a non-deterministic future.