MIKAI
Library to modify content of a Mykey
mykey.c
Go to the documentation of this file.
1 /*
2  * @author Lilz <https://telegram.me/Lilz73>
3  * @copyright 2020-2021 Lilz <https://telegram.me/Lilz73>
4  * @license MIKAI LICENSE
5  *
6  * This file is part of MIKAI.
7  *
8  * MIKAI is free software: you can redistribute it and/or modify
9  * it under the terms of the MIKAI License, as published by
10  * Lilz along with this program and available on "MIKAI Download" Telegram channel
11  * <https://telegram.me/mikaidownload>.
12  *
13  * MIKAI is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY.
15  *
16  * You should have received a copy of the MIKAI License along
17  * with MIKAI.
18  * If not, see <https://telegram.me/mikaidownload>.
19  */
20 
21 #include <stdint.h>
22 #include <string.h>
23 #include <mikai/mikai.h>
24 #include <mikai-internal.h>
25 #include <mikai-error.h>
26 #include <srix.h>
27 
32 static inline void encodeDecodeBlock(uint32_t *block) {
33  /*
34  * Swap all values using XOR
35  * 32 bit: 1111222233334444
36  */
37  *block ^= (*block & 0x00C00000) << 6 | (*block & 0x0000C000) << 12 | (*block & 0x000000C0) << 18 |
38  (*block & 0x000C0000) >> 6 | (*block & 0x00030000) >> 12 | (*block & 0x00000300) >> 6;
39  *block ^= (*block & 0x30000000) >> 6 | (*block & 0x0C000000) >> 12 | (*block & 0x03000000) >> 18 |
40  (*block & 0x00003000) << 6 | (*block & 0x00000030) << 12 | (*block & 0x0000000C) << 6;
41  *block ^= (*block & 0x00C00000) << 6 | (*block & 0x0000C000) << 12 | (*block & 0x000000C0) << 18 |
42  (*block & 0x000C0000) >> 6 | (*block & 0x00030000) >> 12 | (*block & 0x00000300) >> 6;
43 }
44 
50 static uint8_t getCurrentTransactionOffset(MyKey *key) {
51  uint32_t *block3C = SrixGetBlock(key->srix4k, 0x3C);
52 
53  /* If first transaction, set the pointer to 7 to fill the first transaction block */
54  if (*block3C == 0xFFFFFFFF) {
55  return 0x07;
56  }
57 
58  /* Decode transaction pointer */
59  uint32_t current = *block3C ^ (*SrixGetBlock(key->srix4k, 0x07) & 0x00FFFFFF);
60  encodeDecodeBlock(&current);
61 
62  if ((current & 0x00FF0000 >> 16) > 0x07) {
63  /* Out of range */
64  return 0x07;
65  } else {
66  /* Return result (a value between 0x00 and 0x07) */
67  return current >> 16;
68  }
69 }
70 
76 static inline void calculateBlockChecksum(uint32_t *block, const uint8_t blockNum) {
77  uint8_t checksum = 0xFF - blockNum - (*block & 0x0F) - (*block >> 4 & 0x0F) - (*block >> 8 & 0x0F) -
78  (*block >> 12 & 0x0F) - (*block >> 16 & 0x0F) - (*block >> 20 & 0x0F);
79 
80  // Clear first byte and set to checksum value
81  *block &= 0x00FFFFFF;
82  *block |= checksum << 24;
83 }
84 
92 static uint32_t daysDifference(uint8_t day, uint8_t month, uint16_t year) {
93  if (month < 3) {
94  year--;
95  month += 12;
96  }
97  return year * 365 + year / 4 - year / 100 + year / 400 + (month * 153 + 3) / 5 + day - 728692;
98 }
99 
104 void calculateEncryptionKey(MyKey key[static 1]) {
105  /* OTP calculation (reverse block 6 + 1, incremental. 1,2,3, ecc.) */
106  uint32_t *block6 = SrixGetBlock(key->srix4k, 0x06);
107  uint32_t otp = ~(*block6 << 24 | *block6 & 0x0000FF00 << 8 |
108  *block6 & 0x00FF0000 >> 8 | *block6 >> 24) + 1;
109 
110  /*
111  * Encryption key calculation.
112  * MK = UID * VENDOR
113  * SK (Encryption key) = MK * OTP
114  */
115  encodeDecodeBlock(SrixGetBlock(key->srix4k, 0x18));
116  encodeDecodeBlock(SrixGetBlock(key->srix4k, 0x19));
117 
118  uint32_t vendor = (*SrixGetBlock(key->srix4k, 0x18) << 16 |
119  (*SrixGetBlock(key->srix4k, 0x19) & 0x0000FFFF)) + 1;
120 
121  encodeDecodeBlock(SrixGetBlock(key->srix4k, 0x18));
122  encodeDecodeBlock(SrixGetBlock(key->srix4k, 0x19));
123 
124  key->encryptionKey = SrixGetUid(key->srix4k) * vendor * otp;
125 }
126 
127 uint32_t MyKeyGetEncryptionKey(MyKey key[static 1]) {
128  return key->encryptionKey;
129 }
130 
131 bool MyKeyIsReset(MyKey *key) {
132  static const uint32_t block18Reset = 0x8FCD0F48;
133  static const uint32_t block19Reset = 0xC0820007;
134  return *SrixGetBlock(key->srix4k, 0x18) == block18Reset &&
135  *SrixGetBlock(key->srix4k, 0x19) == block19Reset;
136 }
137 
138 bool MyKeyCheckLockID(MyKey key[static 1]) {
139  /*
140  * If there is lock id but block 21 checksum is right, it doesn't block mikai,
141  * because key could be associated with an old reader that doesn't check lock id
142  */
143  uint32_t creditCheck = *SrixGetBlock(key->srix4k, 0x21) ^ key->encryptionKey;
144  encodeDecodeBlock(&creditCheck);
145 
146  /* Save current checksum */
147  uint8_t checksum = creditCheck >> 24;
148 
149  /* Recalculate checksum */
150  calculateBlockChecksum(&creditCheck, 0x21);
151 
152  /* Check lock id and checksum */
153  return (*SrixGetBlock(key->srix4k, 0x05) & 0x000000FF) == 0x7F && checksum != creditCheck >> 24;
154 }
155 
156 uint32_t MyKeyGetBlock(MyKey key[static 1], uint8_t blockNum) {
157  uint32_t *block = SrixGetBlock(key->srix4k, blockNum);
158  return block ? *block : 0;
159 }
160 
161 void MyKeyModifyBlock(MyKey key[static 1], uint32_t block, uint8_t blockNum) {
162  SrixModifyBlock(key->srix4k, block, blockNum);
163 }
164 
165 void MyKeyImportVendor(MyKey key[static 1], const uint32_t vendor) {
166  /* Decode blocks 21 and 25 with precedent vendor's encryption key */
167  *SrixGetBlock(key->srix4k, 0x21) ^= key->encryptionKey;
168  *SrixGetBlock(key->srix4k, 0x25) ^= key->encryptionKey;
169 
170  /* Set new vendor blocks */
171  uint32_t block18 = vendor >> 16;
172  calculateBlockChecksum(&block18, 0x18);
173  encodeDecodeBlock(&block18);
174  SrixModifyBlock(key->srix4k, block18, 0x18);
175 
176  uint32_t block19 = vendor & 0x0000FFFF;
177  calculateBlockChecksum(&block19, 0x19);
178  encodeDecodeBlock(&block19);
179  SrixModifyBlock(key->srix4k, block19, 0x19);
180 
181  /* Recalculate encryption key using new vendor */
183 
184  /* Encode 21 and 25 with new vendor's encryption key */
185  SrixModifyBlock(key->srix4k, *SrixGetBlock(key->srix4k, 0x21) ^ key->encryptionKey, 0x21);
186  SrixModifyBlock(key->srix4k, *SrixGetBlock(key->srix4k, 0x25) ^ key->encryptionKey, 0x25);
187 
188  /* Copy vendor blocks 18 and 19 to 1C and 1D */
189  SrixModifyBlock(key->srix4k, block18, 0x1C);
190  encodeDecodeBlock(SrixGetBlock(key->srix4k, 0x1C));
191  calculateBlockChecksum(SrixGetBlock(key->srix4k, 0x1C), 0x1C);
192  encodeDecodeBlock(SrixGetBlock(key->srix4k, 0x1C));
193 
194  SrixModifyBlock(key->srix4k, block19, 0x1D);
195  encodeDecodeBlock(SrixGetBlock(key->srix4k, 0x1D));
196  calculateBlockChecksum(SrixGetBlock(key->srix4k, 0x1D), 0x1D);
197  encodeDecodeBlock(SrixGetBlock(key->srix4k, 0x1D));
198 }
199 
200 int MyKeyExportVendor(MyKey key[static 1], uint32_t vendor[static 1]) {
201  if (MyKeyIsReset(key)) {
202  key->error = MIKAI_ERROR(MIKAI_MYKEY_ERROR, "unable to export vendor, key is reset");
203  return MIKAI_MYKEY_ERROR;
204  }
205 
206  encodeDecodeBlock(SrixGetBlock(key->srix4k, 0x18));
207  encodeDecodeBlock(SrixGetBlock(key->srix4k, 0x19));
208 
209  *vendor = *SrixGetBlock(key->srix4k, 0x18) << 16 |
210  *SrixGetBlock(key->srix4k, 0x19) & 0x0000FFFF;
211 
212  encodeDecodeBlock(SrixGetBlock(key->srix4k, 0x18));
213  encodeDecodeBlock(SrixGetBlock(key->srix4k, 0x19));
214 
215  return MIKAI_SUCCESS;
216 }
217 
218 void MyKeyExportMemory(MyKey key[static 1], uint32_t dump[const SRIX4K_BLOCKS], uint64_t *uid) {
219  if (uid) {
220  *uid = SrixGetUid(key->srix4k);
221  }
222 
223  memcpy(dump, SrixGetBlock(key->srix4k, 0x00), SRIX4K_BYTES);
224 }
225 
226 void MyKeyReset(MyKey key[static 1]) {
227  for (uint8_t i = 0x10; i < SRIX4K_BLOCKS; i++) {
228  uint32_t currentBlock;
229 
230  switch (i) {
231  case 0x10:
232  case 0x14:
233  case 0x3F:
234  case 0x43: {
235  /* Key ID (first byte) + days elapsed from production */
236  /* CHECK | ID | DAYS | DAYS */
237  uint32_t *productionDate = SrixGetBlock(key->srix4k, 0x08);
238 
239  /* Decode BCD (Binary Coded Decimal) production date */
240  uint8_t day = (*productionDate >> 28 & 0x0F) * 10 + (*productionDate >> 24 & 0x0F);
241  uint8_t month = (*productionDate >> 20 & 0x0F) * 10 + (*productionDate >> 16 & 0x0F);
242  uint16_t year = (*productionDate & 0x0F) * 1000 +
243  (*productionDate >> 4 & 0x0F) * 100 +
244  (*productionDate >> 12 & 0x0F) * 10 +
245  (*productionDate >> 8 & 0x0F);
246 
247  uint32_t elapsed = daysDifference(day, month, year);
248  currentBlock = (*SrixGetBlock(key->srix4k, 0x07) & 0xFF000000) >> 8 |
249  ((elapsed / 1000 % 10) << 12) + ((elapsed / 100 % 10) << 8) |
250  ((elapsed / 10 % 10) << 4) + (elapsed % 10);
251  calculateBlockChecksum(&currentBlock, i);
252  break;
253  }
254 
255  case 0x11:
256  case 0x15:
257  case 0x40:
258  case 0x44:
259  /* Key ID [last three bytes] */
260  currentBlock = *SrixGetBlock(key->srix4k, 0x07);
261  calculateBlockChecksum(&currentBlock, i);
262  break;
263 
264  case 0x22:
265  case 0x26:
266  case 0x51:
267  case 0x55: {
268  /* Production date (last three bytes) */
269  uint32_t *productionDate = SrixGetBlock(key->srix4k, 0x08);
270  currentBlock = (*productionDate & 0x0000FF00) << 8 | (*productionDate & 0x00FF0000) >> 8 |
271  *productionDate & 0x000000FF;
272  calculateBlockChecksum(&currentBlock, i);
273  encodeDecodeBlock(&currentBlock);
274  break;
275  }
276 
277  case 0x12:
278  case 0x16:
279  case 0x41:
280  case 0x45:
281  /* Operations counter */
282  currentBlock = 1;
283  calculateBlockChecksum(&currentBlock, i);
284  break;
285 
286  case 0x13:
287  case 0x17:
288  case 0x42:
289  case 0x46:
290  /* Generic blocks */
291  currentBlock = 0x00040013;
292  calculateBlockChecksum(&currentBlock, i);
293  break;
294 
295  case 0x18:
296  case 0x1C:
297  case 0x47:
298  case 0x4B:
299  /* Generic blocks */
300  currentBlock = 0x0000FEDC;
301  calculateBlockChecksum(&currentBlock, i);
302  encodeDecodeBlock(&currentBlock);
303  break;
304 
305  case 0x19:
306  case 0x1D:
307  case 0x48:
308  case 0x4C:
309  /* Generic blocks */
310  currentBlock = 0x00000123;
311  calculateBlockChecksum(&currentBlock, i);
312  encodeDecodeBlock(&currentBlock);
313  break;
314 
315  case 0x21:
316  case 0x25:
317  /* Current credit (0,00€) */
319  currentBlock = 0;
320  calculateBlockChecksum(&currentBlock, i);
321  encodeDecodeBlock(&currentBlock);
322  currentBlock ^= key->encryptionKey;
323  break;
324 
325  case 0x20:
326  case 0x24:
327  case 0x4F:
328  case 0x53:
329  /* Generic blocks */
330  currentBlock = 0x00010000;
331  calculateBlockChecksum(&currentBlock, i);
332  encodeDecodeBlock(&currentBlock);
333  break;
334 
335  case 0x1A:
336  case 0x1B:
337  case 0x1E:
338  case 0x1F:
339  case 0x23:
340  case 0x27:
341  case 0x49:
342  case 0x4A:
343  case 0x4D:
344  case 0x4E:
345  case 0x50:
346  case 0x52:
347  case 0x54:
348  case 0x56:
349  /* Generic blocks */
350  currentBlock = 0;
351  calculateBlockChecksum(&currentBlock, i);
352  encodeDecodeBlock(&currentBlock);
353  break;
354 
355  default:
356  currentBlock = 0xFFFFFFFF;
357  break;
358  }
359 
360  /* If this block has a different value than EEPROM, modify it. */
361  if (memcmp(SrixGetBlock(key->srix4k, i), &currentBlock, sizeof(uint32_t)) != 0) {
362  SrixModifyBlock(key->srix4k, currentBlock, i);
363  }
364  }
365 }
366 
367 uint16_t MyKeyGetCurrentCredit(MyKey key[static 1]) {
368  uint32_t currentCredit = *SrixGetBlock(key->srix4k, 0x21) ^ key->encryptionKey;
369  encodeDecodeBlock(&currentCredit);
370  return currentCredit;
371 }
372 
373 int MyKeyAddCents(MyKey key[static 1], uint16_t cents, uint8_t day, uint8_t month, uint8_t year) {
374  /* Check lock id */
375  if (MyKeyCheckLockID(key)) {
376  key->error = MIKAI_ERROR(MIKAI_MYKEY_ERROR,"your key has an unknown protection (lock id) and I can't charge it");
377  return MIKAI_MYKEY_ERROR;
378  }
379 
380  /* Check reset key */
381  if (MyKeyIsReset(key)) {
382  key->error = MIKAI_ERROR(MIKAI_MYKEY_ERROR, "your mykey isn't associated with any vendor");
383  return MIKAI_MYKEY_ERROR;
384  }
385 
386  if (*SrixGetBlock(key->srix4k, 0x06) == 0) {
387  key->error = MIKAI_ERROR(MIKAI_MYKEY_ERROR, "your mykey isn't associated with any vendor");
388  return MIKAI_MYKEY_ERROR;
389  }
390 
391  /* Calculate current credit */
392  uint16_t precedentCredit;
393  uint16_t actualCredit = MyKeyGetCurrentCredit(key);
394 
395  /* Get current transaction position */
396  uint8_t current = getCurrentTransactionOffset(key);
397 
398  /* Split credit into multiple transaction. Stop at 5 cent. */
399  do {
400  /* Save current credit to precedent */
401  precedentCredit = actualCredit;
402 
403  /* Choose current recharge */
404  if (cents / 200 > 0) {
405  /* 2€ */
406  cents -= 200;
407  actualCredit += 200;
408  } else if (cents / 100 > 0) {
409  /* 1€ */
410  cents -= 100;
411  actualCredit += 100;
412  } else if (cents / 50 > 0) {
413  /* 0,50€ */
414  cents -= 50;
415  actualCredit += 50;
416  } else if (cents / 20 > 0) {
417  /* 0,20€ */
418  cents -= 20;
419  actualCredit += 20;
420  } else if (cents / 10 > 0) {
421  /* 0,10€ */
422  cents -= 10;
423  actualCredit += 10;
424  } else if (cents / 5 > 0) {
425  /* 0,05€ */
426  cents -= 5;
427  actualCredit += 5;
428  } else {
429  /* < 0.05€ */
430  cents -= cents;
431  actualCredit += cents;
432  }
433 
434  /* Point to new credit position */
435  current = (current == 7) ? 0 : current + 1;
436 
437  /* Save new credit to history blocks */
438  SrixModifyBlock(key->srix4k, day << 27 | month << 23 | year << 16 | actualCredit, 0x34 + current);
439  } while (cents > 0);
440 
441  /* Save new credit to 21 and 25 */
442  SrixModifyBlock(key->srix4k, actualCredit, 0x21);
443  uint32_t *block21 = SrixGetBlock(key->srix4k, 0x21);
444  calculateBlockChecksum(block21, 0x21);
445  encodeDecodeBlock(block21);
446  *block21 ^= key->encryptionKey;
447 
448  SrixModifyBlock(key->srix4k, actualCredit, 0x25);
449  uint32_t *block25 = SrixGetBlock(key->srix4k, 0x25);
450  calculateBlockChecksum(block25, 0x25);
451  encodeDecodeBlock(block25);
452  *block25 ^= key->encryptionKey;
453 
454  /* Save precedent credit to 23 and 27 */
455  SrixModifyBlock(key->srix4k, precedentCredit, 0x23);
456  uint32_t *block23 = SrixGetBlock(key->srix4k, 0x23);
457  calculateBlockChecksum(block23, 0x23);
458  encodeDecodeBlock(block23);
459 
460  SrixModifyBlock(key->srix4k, precedentCredit, 0x27);
461  uint32_t *block27 = SrixGetBlock(key->srix4k, 0x27);
462  calculateBlockChecksum(block27, 0x27);
463  encodeDecodeBlock(block27);
464 
465  /* Save transaction pointer to block 3C */
466  SrixModifyBlock(key->srix4k, current << 16, 0x3C);
467  uint32_t *block3C = SrixGetBlock(key->srix4k, 0x3C);
468  calculateBlockChecksum(block3C, 0x3C);
469  encodeDecodeBlock(block3C);
470  *block3C ^= *SrixGetBlock(key->srix4k, 0x07) & 0x00FFFFFF;
471 
472  return MIKAI_SUCCESS;
473 }
474 
475 int MyKeySetCents(MyKey key[static 1], uint16_t cents, uint8_t day, uint8_t month, uint8_t year) {
476  /* Dump precedent blocks (restore in case of failure) */
477  uint32_t dump[10];
478  memcpy(dump, SrixGetBlock(key->srix4k, 0x21), SRIX_BLOCK_LENGTH);
479  memcpy(dump + 1, SrixGetBlock(key->srix4k, 0x34), 9 * SRIX_BLOCK_LENGTH);
480 
481  uint32_t *block21 = SrixGetBlock(key->srix4k, 0x21);
482  *block21 = 0;
483  calculateBlockChecksum(block21, 0x21);
484  encodeDecodeBlock(block21);
485  *block21 ^= key->encryptionKey;
486 
487  /* Reset transaction history and pointer (0x24-0x3C) */
488  memset(SrixGetBlock(key->srix4k, 0x34), 0xFF, 9 * SRIX_BLOCK_LENGTH);
489 
490  /* If there is an error, restore precedent dump */
491  if (MyKeyAddCents(key, cents, day, month, year) != MIKAI_SUCCESS) {
492  memcpy(SrixGetBlock(key->srix4k, 0x21), dump, SRIX_BLOCK_LENGTH);
493  memcpy(SrixGetBlock(key->srix4k, 0x34), dump + 1, 9 * SRIX_BLOCK_LENGTH);
494  return MIKAI_MYKEY_ERROR;
495  } else {
496  return MIKAI_SUCCESS;
497  }
498 }
mikai.h
SRIX4K_BYTES
#define SRIX4K_BYTES
Definition: mikai.h:31
SrixModifyBlock
void SrixModifyBlock(Srix target[static 1], const uint32_t block, const uint8_t blockNum)
Definition: srix.c:192
MyKey::encryptionKey
uint32_t encryptionKey
Definition: mikai-internal.h:32
MyKeyReset
void MyKeyReset(MyKey key[static 1])
Definition: mykey.c:226
MyKey
Struct that represents a MyKey.
Definition: mikai-internal.h:31
mikai-error.h
MyKey::error
MikaiError error
Definition: mikai-internal.h:34
MyKeySetCents
int MyKeySetCents(MyKey key[static 1], uint16_t cents, uint8_t day, uint8_t month, uint8_t year)
Definition: mykey.c:475
MyKeyExportMemory
void MyKeyExportMemory(MyKey key[static 1], uint32_t dump[const SRIX4K_BLOCKS], uint64_t *uid)
Definition: mykey.c:218
MIKAI_SUCCESS
@ MIKAI_SUCCESS
Definition: mikai-error.h:31
srix.h
MyKeyAddCents
int MyKeyAddCents(MyKey key[static 1], uint16_t cents, uint8_t day, uint8_t month, uint8_t year)
Definition: mykey.c:373
MIKAI_MYKEY_ERROR
@ MIKAI_MYKEY_ERROR
Definition: mikai-error.h:34
MyKeyIsReset
bool MyKeyIsReset(MyKey *key)
Check if a MyKey is reset (if it hasn't an associated vendor code).
Definition: mykey.c:131
SrixGetBlock
uint32_t * SrixGetBlock(Srix target[static 1], uint8_t blockNum)
Definition: srix.c:188
mikai-internal.h
SRIX4K_BLOCKS
#define SRIX4K_BLOCKS
Definition: mikai.h:30
calculateEncryptionKey
void calculateEncryptionKey(MyKey key[static 1])
Calculate the encryption key and save the result in mikai struct.
Definition: mykey.c:104
MyKeyGetBlock
uint32_t MyKeyGetBlock(MyKey key[static 1], uint8_t blockNum)
Definition: mykey.c:156
SRIX_BLOCK_LENGTH
#define SRIX_BLOCK_LENGTH
Definition: mikai.h:28
MyKeyCheckLockID
bool MyKeyCheckLockID(MyKey key[static 1])
Definition: mykey.c:138
MIKAI_ERROR
#define MIKAI_ERROR(type, errorMessage)
Definition: mikai-error.h:46
MyKeyModifyBlock
void MyKeyModifyBlock(MyKey key[static 1], uint32_t block, uint8_t blockNum)
Definition: mykey.c:161
SrixGetUid
uint64_t SrixGetUid(Srix target[static 1])
Definition: srix.c:184
MyKeyImportVendor
void MyKeyImportVendor(MyKey key[static 1], const uint32_t vendor)
Definition: mykey.c:165
MyKeyExportVendor
int MyKeyExportVendor(MyKey key[static 1], uint32_t vendor[static 1])
Definition: mykey.c:200
MyKey::srix4k
Srix * srix4k
Definition: mikai-internal.h:33
MyKeyGetCurrentCredit
uint16_t MyKeyGetCurrentCredit(MyKey key[static 1])
Definition: mykey.c:367
MyKeyGetEncryptionKey
uint32_t MyKeyGetEncryptionKey(MyKey key[static 1])
Definition: mykey.c:127