Generar claves aleatorias
Al final, nuestro generador de PINs de 6 dígitos, para un intruso es imprevisible, pero no tan impredecible. Hay que comprender sus límites: su naturaleza numérica y su límite de longitud fija y corta lo hacen adivinable sí o sí, como todos los PINs, con suficiente tiempo y recursos - aunque hayamos puesto nuestro empeño en que no sea una puerta abierta. A veces, una puerta de cristal, si está cerrada evita la entrada de muchos, a pesar de no estar blindada.
En cualquier caso, ahí queda nuestra puerta de cristal: algo tintada, con cerradura. No es inexpugnable, pero a más de un intruso le costará un rato abrirla.
Y ese rato - si se gestiona bien sí puede blindar hasta esa puerta de cristal: introduciendo demoras entre intentos de autenticación, notificando sobre ello, o incluso limitando un número máximo de intentos por dispositivo que se conecta.
Aquí llega la tarea pendiente de la creación de las claves de 128 bits - eso son 16 bytes - o como se escribe en Zig: [16]u8.
Las claves las creamos en tiempo de ejecución:
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); // genera bytes hasta llenar el buffer s_key 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 |
Hemos creado una clave de 16 bytes y cada vez que ejecutes el programa te imprimirá una distinta.
Usamos una función de la librería estándar de Zig para generar los números aleatorios:
|
// genera bytes hasta llenar el buffer s_key std.crypto.random.bytes(s_key); |
También, hay un detalle que te puede haber llamado la atención. A pesar de solicitar la memoria con el allocator, nunca la liberamos de forma manual. Podríamos hacerlo, pero en realidad no es necesario en este caso. El programa que estamos ejecutando es totalmente lineal. La poca memoria que asignamos, a pesar de suponer una leak (fuga de memoria) será liberada al terminar main, sí o sí. En un “script” de prueba tan corto no es necesario pero cuidado - muchos “scripts de prueba” evolucionan o son retomados al día o a la semana siguiente y terminan siendo parte de algo más serio. Así que - efectivamente, igual no es tan mala idea incluir siempre el free, por si las moscas.
El jugador después de testear el programa lo ejecuta sobre la terminal de la IA primigenia:
genvac.zig |
|
const std = @import("std"); const print = std.debug.print; // obtener un pin para un índice fn get_pin4index(n_x: usize) usize { const n_a = n_x + 1; // formula de números pentagonales return (n_a * (3 * n_a - 1)) / 2 % 1_000_000; } // convertir el número a una cadena rellena con ceros a la izquierda 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 { // Semilla simple determinista 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); // genera bytes hasta llenar el buffer s_key std.crypto.random.bytes(s_key); return s_key; } // declaramos la longitud del PIN máxima del PIN const N_PIN_LENGTH = 6; const N_START = 100; // declaramos el total de PINs creados const N_TOTAL_PINS = 1000; // longitud key en bytes const N_KEY_LENGTH = 16; pub fn main() !void { comptime var a_pins: [N_TOTAL_PINS][N_PIN_LENGTH]u8 = undefined; comptime { // añadimos 1000 más por la llamada a get_permuted_index @setEvalBranchQuota(15000); for (0..a_pins.len) |n_i| { // obtenemos el pin en función del indice const n_pin = get_pin4index(n_i + N_START); // obtenemos una nueva posición en el array const n_pos = get_permuted_index(n_i, N_TOTAL_PINS); // convertirmos el pin a cadena rellena con ceros por la izquierda a_pins[n_pos] = left_pad_zeros(n_pin, N_PIN_LENGTH); } } const gpa = std.heap.page_allocator; // imprimimos los primeros 5 y los últimos 5 // PIN y claves para comprobar el resultado 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 |
La IA procesa los datos de los PINs y las claves generadas para cada uno de los robots. Los interpola con los comandos que sobrescriben las configuraciones actuales que llevaron al desastre.
Inmediatamente después, inicia la restauración de la copia de seguridad del sistema global. El resto… es historia de un futuro no determinista.