├── .gitignore ├── README.md ├── build.zig └── src └── main.zig /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HP iLO license key validation library 2 | 3 | This library validates HP iLO license keys. This is most definitely not a 4 | keygen, but if you want to understand how license keys are constructed, 5 | have a look at the unit tests. 6 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const Builder = @import("std").build.Builder; 2 | 3 | pub fn build(b: *Builder) void { 4 | const mode = b.standardReleaseOptions(); 5 | const lib = b.addStaticLibrary("ilo_license_key", "src/main.zig"); 6 | lib.setBuildMode(mode); 7 | lib.install(); 8 | 9 | var main_tests = b.addTest("src/main.zig"); 10 | main_tests.setBuildMode(mode); 11 | 12 | const test_step = b.step("test", "Run library tests"); 13 | test_step.dependOn(&main_tests.step); 14 | } 15 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | pub const IloLicenseKey = struct { 5 | product_id_1: u10, 6 | product_ver_1: u4, 7 | product_id_2: u10, 8 | product_ver_2: u4, 9 | product_id_3: u10, 10 | product_ver_3: u4, 11 | license_type: u4, 12 | transaction_date: u14, 13 | transaction_number: u18, 14 | seats_demo: u16, 15 | feature_mask: u4, 16 | reserved: u1, 17 | 18 | const alphabet = "23456789BCDGHJKLMNPQRSTVWXYZ"; 19 | const magic1: u128 = 0x65424a64633535322c6d50616a23; // #jaPm,255cdJBe 20 | const magic2 = [_]u32{ 21 | 10, 4, 10, 4, 22 | 10, 4, 4, 14, 23 | 18, 16, 4, 1, 24 | 16, 25 | }; 26 | 27 | pub const Error = error{ 28 | WrongKeyLength, 29 | WrongFirstCharacter, 30 | CharacterNotInAlphabet, 31 | ChecksumError, 32 | }; 33 | 34 | pub fn from_string(key: []const u8) !IloLicenseKey { 35 | if (key.len != 25) { 36 | return Error.WrongKeyLength; 37 | } 38 | 39 | if (key[0] != alphabet[1]) { 40 | return Error.WrongFirstCharacter; 41 | } 42 | 43 | const key_as_int = blk: { 44 | var result: u128 = 0; 45 | for (key[1..]) |c| { 46 | result *= alphabet.len; 47 | if (memchr(alphabet, c)) |pos| { 48 | result += pos; 49 | } else { 50 | return Error.CharacterNotInAlphabet; 51 | } 52 | } 53 | break :blk result; 54 | }; 55 | 56 | const key_xored = key_as_int ^ magic1; 57 | 58 | const unscrambled_key = blk: { 59 | var result = [_]u32{0} ** 13; 60 | var foo = [_]u32{0} ** 13; 61 | var w: u128 = 1; 62 | var i: u8 = 0; 63 | while (i < 32) : (i += 1) { 64 | var j: u8 = 0; 65 | while (j < 13) : (j += 1) { 66 | if (foo[j] < magic2[j]) { 67 | if (key_xored & w != 0) { 68 | result[j] |= @as(u32, 1) << @intCast(u5, foo[j]); 69 | } 70 | w <<= 1; 71 | foo[j] += 1; 72 | } 73 | } 74 | } 75 | break :blk result; 76 | }; 77 | 78 | if (unscrambled_key[12] != checksum(unscrambled_key[0..12])) { 79 | return Error.ChecksumError; 80 | } 81 | 82 | return IloLicenseKey{ 83 | .product_id_1 = @intCast(u10, unscrambled_key[0]), 84 | .product_ver_1 = @intCast(u4, unscrambled_key[1]), 85 | .product_id_2 = @intCast(u10, unscrambled_key[2]), 86 | .product_ver_2 = @intCast(u4, unscrambled_key[3]), 87 | .product_id_3 = @intCast(u10, unscrambled_key[4]), 88 | .product_ver_3 = @intCast(u4, unscrambled_key[5]), 89 | .license_type = @intCast(u4, unscrambled_key[6]), 90 | .transaction_date = @intCast(u14, unscrambled_key[7]), 91 | .transaction_number = @intCast(u18, unscrambled_key[8]), 92 | .seats_demo = @intCast(u16, unscrambled_key[9]), 93 | .feature_mask = @intCast(u4, unscrambled_key[10]), 94 | .reserved = @intCast(u1, unscrambled_key[11]), 95 | }; 96 | } 97 | 98 | pub fn to_string(self: IloLicenseKey) [25]u8 { 99 | var data: [13]u32 = .{ 100 | self.product_id_1, 101 | self.product_ver_1, 102 | self.product_id_2, 103 | self.product_ver_2, 104 | self.product_id_3, 105 | self.product_ver_3, 106 | self.license_type, 107 | self.transaction_date, 108 | self.transaction_number, 109 | self.seats_demo, 110 | self.feature_mask, 111 | self.reserved, 112 | 0, 113 | }; 114 | data[12] = checksum(data[0..12]); 115 | 116 | const scrambled_key: u128 = blk: { 117 | var result: u128 = 0; 118 | var foo = [_]u32{0} ** 13; 119 | var w: u128 = 1; 120 | var i: u8 = 0; 121 | while (i < 32) : (i += 1) { 122 | var j: u8 = 0; 123 | while (j < 13) : (j += 1) { 124 | if (foo[j] < magic2[j]) { 125 | if (data[j] & @as(u32, 1) << @intCast(u5, foo[j]) != 0) { 126 | result |= w; 127 | } 128 | w <<= 1; 129 | foo[j] += 1; 130 | } 131 | } 132 | } 133 | break :blk result; 134 | }; 135 | 136 | var key_as_int = scrambled_key ^ magic1; 137 | 138 | return blk: { 139 | var result = [_]u8{'3'} ++ [_]u8{0} ** 24; 140 | var i: u8 = 24; 141 | while (i > 0) : (i -= 1) { 142 | result[i] = alphabet[@intCast(usize, @mod(key_as_int, alphabet.len))]; 143 | key_as_int /= alphabet.len; 144 | } 145 | break :blk result; 146 | }; 147 | } 148 | 149 | fn memchr(s: []const u8, c: u8) ?usize { 150 | var i: usize = 0; 151 | for (s) |b| { 152 | if (b == c) { 153 | return i; 154 | } 155 | i += 1; 156 | } 157 | return null; 158 | } 159 | 160 | fn checksum(data: []const u32) u32 { 161 | var x: u32 = 0x4242; 162 | for (data) |d| { 163 | const y: u32 = x >> 14; 164 | x <<= 2; 165 | if ((x & 0x20000) != 0) { 166 | x |= y; 167 | x |= 2; 168 | } else { 169 | x |= y & 1; 170 | } 171 | x <<= 16; 172 | x ^= d; 173 | x ^= d << 16; 174 | x >>= 16; 175 | } 176 | return x; 177 | } 178 | 179 | pub fn format_transaction_date(self: IloLicenseKey) [10]u8 { 180 | const year = self.transaction_date / 384 + 2001; 181 | const month = @mod(self.transaction_date, 384) / 32; 182 | const day = @mod(self.transaction_date, 32); 183 | var result: [10]u8 = undefined; 184 | _ = std.fmt.bufPrint(result[0..], "{:0>4}-{:0>2}-{:0>2}", .{ year, month, day }) catch unreachable; 185 | return result; 186 | } 187 | }; 188 | 189 | test "IloLicenseKey.from_string (valid)" { 190 | const license = try IloLicenseKey.from_string("3Q23VVTZ39HLB6LYNMNCC8YRN"); 191 | testing.expect(license.product_id_1 == 6); // Reserved for Test (#1) 192 | testing.expect(license.product_ver_1 == 1); 193 | testing.expect(license.product_id_2 == 0); 194 | testing.expect(license.product_ver_2 == 0); 195 | testing.expect(license.product_id_3 == 0); 196 | testing.expect(license.product_ver_3 == 0); 197 | testing.expect(license.license_type == 1); // FQL 198 | testing.expect(license.transaction_date == 7470); // 2020-05-14 199 | testing.expect(license.transaction_number == 0x3ffff); 200 | testing.expect(license.seats_demo == 1337); 201 | testing.expect(license.feature_mask == 0); 202 | testing.expect(license.reserved == 0); 203 | } 204 | 205 | test "IloLicenseKey.from_string (wrong key length)" { 206 | testing.expectError(IloLicenseKey.Error.WrongKeyLength, IloLicenseKey.from_string("3Q23VVTZ39HLB")); 207 | } 208 | 209 | test "IloLicenseKey.from_string (wrong first character)" { 210 | testing.expectError(IloLicenseKey.Error.WrongFirstCharacter, IloLicenseKey.from_string("4Q23VVTZ39HLB6LYNMNCC8YRN")); 211 | } 212 | 213 | test "IloLicenseKey.from_string (character not in alphabet)" { 214 | testing.expectError(IloLicenseKey.Error.CharacterNotInAlphabet, IloLicenseKey.from_string("3AAAAAAAA9HLB6LYNMNCC8YRN")); 215 | } 216 | 217 | test "IloLicenseKey.from_string (checksum error)" { 218 | testing.expectError(IloLicenseKey.Error.ChecksumError, IloLicenseKey.from_string("3Q23VVTZ39HLB6LYNMNCC8YRX")); 219 | } 220 | 221 | test "IloLicenseKey.to_string" { 222 | const license = IloLicenseKey{ 223 | .product_id_1 = 6, // Reserved for Test (#1) 224 | .product_ver_1 = 1, 225 | .product_id_2 = 0, 226 | .product_ver_2 = 0, 227 | .product_id_3 = 0, 228 | .product_ver_3 = 0, 229 | .license_type = 1, // FQL 230 | .transaction_date = 7470, // 2020-05-14 231 | .transaction_number = 0x3ffff, 232 | .seats_demo = 1337, 233 | .feature_mask = 0, 234 | .reserved = 0, 235 | }; 236 | std.testing.expectEqualSlices(u8, IloLicenseKey.to_string(license)[0..], "3Q23VVTZ39HLB6LYNMNCC8YRN"); 237 | } 238 | 239 | test "IloLicenseKey.format_transaction_date" { 240 | const license = IloLicenseKey{ 241 | .product_id_1 = 0, 242 | .product_ver_1 = 0, 243 | .product_id_2 = 0, 244 | .product_ver_2 = 0, 245 | .product_id_3 = 0, 246 | .product_ver_3 = 0, 247 | .license_type = 0, 248 | .transaction_date = 7470, // 2020-05-14 249 | .transaction_number = 0, 250 | .seats_demo = 0, 251 | .feature_mask = 0, 252 | .reserved = 0, 253 | }; 254 | std.testing.expectEqualSlices(u8, license.format_transaction_date()[0..], "2020-05-14"); 255 | } 256 | --------------------------------------------------------------------------------