#include "cogs_mikai.h" #include #include #include #include // Encode or decode a MyKey block (XOR bit manipulation) static inline void encode_decode_block(uint32_t* block) { *block ^= (*block & 0x00C00000) << 6 | (*block & 0x0000C000) << 12 | (*block & 0x000000C0) << 18 | (*block & 0x000C0000) >> 6 | (*block & 0x00030000) >> 12 | (*block & 0x00000300) >> 6; *block ^= (*block & 0x30000000) >> 6 | (*block & 0x0C000000) >> 12 | (*block & 0x03000000) >> 18 | (*block & 0x00003000) << 6 | (*block & 0x00000030) << 12 | (*block & 0x0000000C) << 6; *block ^= (*block & 0x00C00000) << 6 | (*block & 0x0000C000) << 12 | (*block & 0x000000C0) << 18 | (*block & 0x000C0000) >> 6 | (*block & 0x00030000) >> 12 | (*block & 0x00000300) >> 6; } // Public wrapper for debug purposes void mykey_encode_decode_block(uint32_t* block) { encode_decode_block(block); } // Calculate checksum of a generic block static inline void calculate_block_checksum(uint32_t* block, const uint8_t block_num) { uint8_t checksum = 0xFF - block_num - (*block & 0x0F) - (*block >> 4 & 0x0F) - (*block >> 8 & 0x0F) - (*block >> 12 & 0x0F) - (*block >> 16 & 0x0F) - (*block >> 20 & 0x0F); // Clear first byte and set to checksum value *block &= 0x00FFFFFF; *block |= checksum << 24; } // Return the number of days between 1/1/1995 and a specified date static uint32_t days_difference(uint8_t day, uint8_t month, uint16_t year) { if(month < 3) { year--; month += 12; } return year * 365 + year / 4 - year / 100 + year / 400 + (month * 153 + 3) / 5 + day - 728692; } // Get current transaction offset static uint8_t get_current_transaction_offset(MyKeyData* key) { uint32_t block3C = key->eeprom[0x3C]; // If first transaction, set the pointer to 7 to fill the first transaction block if(block3C == 0xFFFFFFFF) { return 0x07; } // Decode transaction pointer uint32_t current = block3C ^ (key->eeprom[0x07] & 0x00FFFFFF); encode_decode_block(¤t); if((current & 0x00FF0000 >> 16) > 0x07) { // Out of range return 0x07; } else { // Return result (a value between 0x00 and 0x07) return current >> 16; } } // Calculate the encryption key and save the result in key struct void mykey_calculate_encryption_key(MyKeyData* key) { FURI_LOG_I(TAG, "=== Encryption Key Calculation ==="); FURI_LOG_I(TAG, "UID (as stored): 0x%016llX", key->uid); // OTP calculation (reverse block 6 + 1, incremental. 1,2,3, etc.) uint32_t block6 = key->eeprom[0x06]; FURI_LOG_I(TAG, "Block 0x06 raw: 0x%08lX", block6); uint32_t block6_reversed = __bswap32(block6); FURI_LOG_I(TAG, "Block 0x06 reversed: 0x%08lX", block6_reversed); uint32_t otp = ~block6_reversed + 1; FURI_LOG_I(TAG, "OTP (~reversed + 1): 0x%08lX", otp); // Encryption key calculation // MK = UID * VENDOR // SK (Encryption key) = MK * OTP uint32_t block18_raw = key->eeprom[0x18]; uint32_t block19_raw = key->eeprom[0x19]; FURI_LOG_I(TAG, "Block 0x18 raw: 0x%08lX", block18_raw); FURI_LOG_I(TAG, "Block 0x19 raw: 0x%08lX", block19_raw); uint32_t block18 = block18_raw; uint32_t block19 = block19_raw; encode_decode_block(&block18); encode_decode_block(&block19); FURI_LOG_I(TAG, "Block 0x18 decoded: 0x%08lX", block18); FURI_LOG_I(TAG, "Block 0x19 decoded: 0x%08lX", block19); uint64_t vendor = (((uint64_t)block18 << 16) | (block19 & 0x0000FFFF)) + 1; FURI_LOG_I(TAG, "Vendor: 0x%llX", vendor); // Calculate encryption key: UID * vendor * OTP // UID is now correctly stored in big-endian format, no swapping needed key->encryption_key = (key->uid * vendor * otp) & 0xFFFFFFFF; FURI_LOG_I(TAG, "Encryption Key: 0x%08lX", key->encryption_key); FURI_LOG_I(TAG, "==================================="); } // Check if MyKey is reset (no vendor bound) bool mykey_is_reset(MyKeyData* key) { static const uint32_t block18_reset = 0x8FCD0F48; static const uint32_t block19_reset = 0xC0820007; return key->eeprom[0x18] == block18_reset && key->eeprom[0x19] == block19_reset; } // Get block value uint32_t mykey_get_block(MyKeyData* key, uint8_t block_num) { if(block_num >= SRIX4K_BLOCKS) return 0; return key->eeprom[block_num]; } // Modify block void mykey_modify_block(MyKeyData* key, uint32_t block, uint8_t block_num) { if(block_num < SRIX4K_BLOCKS) { key->eeprom[block_num] = block; } } // Extract current credit using libmikai method (block 0x21) - PRIMARY METHOD uint16_t mykey_get_current_credit(MyKeyData* key) { FURI_LOG_I(TAG, "=== Credit Decoding (libmikai method) ==="); // Use libmikai approach: read from block 0x21 uint32_t block21_raw = key->eeprom[0x21]; FURI_LOG_I(TAG, "Block 0x21 raw: 0x%08lX", block21_raw); FURI_LOG_I(TAG, " Bytes: [%02X %02X %02X %02X]", (uint8_t)(block21_raw & 0xFF), (uint8_t)((block21_raw >> 8) & 0xFF), (uint8_t)((block21_raw >> 16) & 0xFF), (uint8_t)((block21_raw >> 24) & 0xFF)); FURI_LOG_I(TAG, "Encryption key: 0x%08lX", key->encryption_key); uint32_t after_xor = block21_raw ^ key->encryption_key; FURI_LOG_I(TAG, "After XOR: 0x%08lX", after_xor); uint32_t current_credit = after_xor; encode_decode_block(¤t_credit); FURI_LOG_I(TAG, "After encode_decode: 0x%08lX", current_credit); uint16_t credit_lower = current_credit & 0xFFFF; uint16_t credit_upper = (current_credit >> 16) & 0xFFFF; FURI_LOG_I(TAG, "Lower 16 bits: %u cents (%u.%02u EUR)", credit_lower, credit_lower / 100, credit_lower % 100); FURI_LOG_I(TAG, "Upper 16 bits: %u cents (%u.%02u EUR)", credit_upper, credit_upper / 100, credit_upper % 100); FURI_LOG_I(TAG, "========================================="); return credit_lower; } // Get credit from transaction history (for comparison/debugging) uint16_t mykey_get_credit_from_history(MyKeyData* key) { uint32_t block3C = key->eeprom[0x3C]; if(block3C == 0xFFFFFFFF) { return 0xFFFF; // No history available } // Decrypt block 0x3C to get starting offset uint32_t decrypted_3C = block3C ^ key->eeprom[0x07]; uint32_t starting_offset = ((decrypted_3C & 0x30000000) >> 28) | ((decrypted_3C & 0x00100000) >> 18); if(starting_offset >= 8) { return 0xFFFF; // Invalid offset } // Get most recent transaction (offset 8 in the circular buffer) // Blocks are already in big-endian format, credit is in lower 16 bits uint32_t txn_block = key->eeprom[0x34 + ((starting_offset + 8) % 8)]; uint16_t credit = txn_block & 0xFFFF; FURI_LOG_D(TAG, "Credit from transaction history: %d cents", credit); return credit; } // Add N cents to MyKey actual credit bool mykey_add_cents(MyKeyData* key, uint16_t cents, uint8_t day, uint8_t month, uint8_t year) { FURI_LOG_I(TAG, "=== Adding %u cents (%u.%02u EUR) ===", cents, cents / 100, cents % 100); // Check reset key if(mykey_is_reset(key)) { FURI_LOG_E(TAG, "Key is reset, cannot add credit"); return false; } if(key->eeprom[0x06] == 0 || key->eeprom[0x06] == 0xFFFFFFFF) { FURI_LOG_E(TAG, "Key has no vendor"); return false; } // Calculate current credit uint16_t precedent_credit; uint16_t actual_credit = mykey_get_current_credit(key); FURI_LOG_I(TAG, "Current credit: %u cents", actual_credit); // Get current transaction position uint8_t current = get_current_transaction_offset(key); FURI_LOG_I(TAG, "Current transaction offset: %u", current); // Split credit into multiple transactions. Stop at 5 cent. do { // Save current credit to precedent precedent_credit = actual_credit; // Choose current recharge if(cents / 200 > 0) { cents -= 200; actual_credit += 200; } else if(cents / 100 > 0) { cents -= 100; actual_credit += 100; } else if(cents / 50 > 0) { cents -= 50; actual_credit += 50; } else if(cents / 20 > 0) { cents -= 20; actual_credit += 20; } else if(cents / 10 > 0) { cents -= 10; actual_credit += 10; } else if(cents / 5 > 0) { cents -= 5; actual_credit += 5; } else { // Less than 5 cents actual_credit += cents; cents = 0; } // Point to new credit position current = (current == 7) ? 0 : current + 1; // Save new credit to history blocks uint32_t txn_block = (uint32_t)day << 27 | (uint32_t)month << 23 | (uint32_t)year << 16 | actual_credit; key->eeprom[0x34 + current] = txn_block; FURI_LOG_I(TAG, "Transaction %u: %u cents at block 0x%02X", current, actual_credit, 0x34 + current); } while(cents > 5); FURI_LOG_I(TAG, "Final credit: %u cents, precedent: %u cents", actual_credit, precedent_credit); // Save new credit to 21 and 25 key->eeprom[0x21] = actual_credit; calculate_block_checksum(&key->eeprom[0x21], 0x21); encode_decode_block(&key->eeprom[0x21]); key->eeprom[0x21] ^= key->encryption_key; key->eeprom[0x25] = actual_credit; calculate_block_checksum(&key->eeprom[0x25], 0x25); encode_decode_block(&key->eeprom[0x25]); key->eeprom[0x25] ^= key->encryption_key; // Save precedent credit to 23 and 27 key->eeprom[0x23] = precedent_credit; calculate_block_checksum(&key->eeprom[0x23], 0x23); encode_decode_block(&key->eeprom[0x23]); key->eeprom[0x27] = precedent_credit; calculate_block_checksum(&key->eeprom[0x27], 0x27); encode_decode_block(&key->eeprom[0x27]); // Save transaction pointer to block 3C key->eeprom[0x3C] = current << 16; calculate_block_checksum(&key->eeprom[0x3C], 0x3C); encode_decode_block(&key->eeprom[0x3C]); key->eeprom[0x3C] ^= key->eeprom[0x07] & 0x00FFFFFF; // Increment operation counter (block 0x12, lower 24 bits) uint32_t op_count = (key->eeprom[0x12] & 0x00FFFFFF) + 1; key->eeprom[0x12] = (key->eeprom[0x12] & 0xFF000000) | (op_count & 0x00FFFFFF); FURI_LOG_I(TAG, "Operation counter incremented to: %lu", op_count); // Mark as modified key->is_modified = true; return true; } // Reset credit history and charge N cents bool mykey_set_cents(MyKeyData* key, uint16_t cents, uint8_t day, uint8_t month, uint8_t year) { // Backup precedent blocks uint32_t dump[10]; memcpy(dump, &key->eeprom[0x21], SRIX_BLOCK_LENGTH); memcpy(dump + 1, &key->eeprom[0x34], 9 * SRIX_BLOCK_LENGTH); key->eeprom[0x21] = 0; calculate_block_checksum(&key->eeprom[0x21], 0x21); encode_decode_block(&key->eeprom[0x21]); key->eeprom[0x21] ^= key->encryption_key; // Reset transaction history and pointer (0x34-0x3C) memset(&key->eeprom[0x34], 0xFF, 9 * SRIX_BLOCK_LENGTH); // If there is an error, restore precedent dump if(!mykey_add_cents(key, cents, day, month, year)) { memcpy(&key->eeprom[0x21], dump, SRIX_BLOCK_LENGTH); memcpy(&key->eeprom[0x34], dump + 1, 9 * SRIX_BLOCK_LENGTH); return false; } return true; } // Reset a MyKey to associate it with another vendor void mykey_reset(MyKeyData* key) { for(uint8_t i = 0x10; i < SRIX4K_BLOCKS; i++) { uint32_t current_block; switch(i) { case 0x10: case 0x14: case 0x3F: case 0x43: { // Key ID (first byte) + days elapsed from production uint32_t production_date = key->eeprom[0x08]; // Decode BCD (Binary Coded Decimal) production date uint8_t pday = (production_date >> 28 & 0x0F) * 10 + (production_date >> 24 & 0x0F); uint8_t pmonth = (production_date >> 20 & 0x0F) * 10 + (production_date >> 16 & 0x0F); uint16_t pyear = (production_date & 0x0F) * 1000 + (production_date >> 4 & 0x0F) * 100 + (production_date >> 12 & 0x0F) * 10 + (production_date >> 8 & 0x0F); uint32_t elapsed = days_difference(pday, pmonth, pyear); current_block = ((key->eeprom[0x07] & 0xFF000000) >> 8) | (((elapsed / 1000 % 10) << 12) + ((elapsed / 100 % 10) << 8)) | (((elapsed / 10 % 10) << 4) + (elapsed % 10)); calculate_block_checksum(¤t_block, i); break; } case 0x11: case 0x15: case 0x40: case 0x44: // Key ID [last three bytes] current_block = key->eeprom[0x07]; calculate_block_checksum(¤t_block, i); break; case 0x22: case 0x26: case 0x51: case 0x55: { // Production date (last three bytes) uint32_t production_date = key->eeprom[0x08]; current_block = (production_date & 0x0000FF00) << 8 | (production_date & 0x00FF0000) >> 8 | (production_date & 0xFF000000) >> 24; calculate_block_checksum(¤t_block, i); encode_decode_block(¤t_block); break; } case 0x12: case 0x16: case 0x41: case 0x45: // Operations counter current_block = 1; calculate_block_checksum(¤t_block, i); break; case 0x13: case 0x17: case 0x42: case 0x46: // Generic blocks current_block = 0x00040013; calculate_block_checksum(¤t_block, i); break; case 0x18: case 0x1C: case 0x47: case 0x4B: // Generic blocks current_block = 0x0000FEDC; calculate_block_checksum(¤t_block, i); encode_decode_block(¤t_block); break; case 0x19: case 0x1D: case 0x48: case 0x4C: // Generic blocks current_block = 0x00000123; calculate_block_checksum(¤t_block, i); encode_decode_block(¤t_block); break; case 0x21: case 0x25: // Current credit (0,00€) mykey_calculate_encryption_key(key); current_block = 0; calculate_block_checksum(¤t_block, i); encode_decode_block(¤t_block); current_block ^= key->encryption_key; break; case 0x20: case 0x24: case 0x4F: case 0x53: // Generic blocks current_block = 0x00010000; calculate_block_checksum(¤t_block, i); encode_decode_block(¤t_block); break; case 0x1A: case 0x1B: case 0x1E: case 0x1F: case 0x23: case 0x27: case 0x49: case 0x4A: case 0x4D: case 0x4E: case 0x50: case 0x52: case 0x54: case 0x56: // Generic blocks current_block = 0; calculate_block_checksum(¤t_block, i); encode_decode_block(¤t_block); break; default: current_block = 0xFFFFFFFF; break; } // If this block has a different value than EEPROM, modify it if(key->eeprom[i] != current_block) { key->eeprom[i] = current_block; } } // Mark as modified key->is_modified = true; } // Save raw card data to file for debugging bool mykey_save_raw_data(COGSMyKaiApp* app, const char* path) { if(!app->mykey.is_loaded) { return false; } Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { storage_file_free(file); furi_record_close(RECORD_STORAGE); return false; } // Write header FuriString* line = furi_string_alloc(); furi_string_printf(line, "MyKey Raw Data Dump\n"); furi_string_cat_printf(line, "UID: %016llX\n", (unsigned long long)app->mykey.uid); furi_string_cat_printf(line, "Encryption Key: 0x%08lX\n\n", app->mykey.encryption_key); storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line)); // Write all blocks for(size_t i = 0; i < SRIX4K_BLOCKS; i++) { furi_string_printf(line, "Block 0x%02zX: 0x%08lX\n", i, app->mykey.eeprom[i]); storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line)); } furi_string_free(line); storage_file_close(file); storage_file_free(file); furi_record_close(RECORD_STORAGE); return true; }