-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #662 from noproto/dev
Add Saflok and MyKey KDFs
- Loading branch information
Showing
3 changed files
with
329 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
#include "nfc_supported_card_plugin.h" | ||
#include <flipper_application/flipper_application.h> | ||
#include <lib/nfc/protocols/st25tb/st25tb.h> | ||
#include <nfc/nfc_device.h> | ||
#include <nfc/helpers/nfc_util.h> | ||
|
||
//Structures data of mykey card | ||
enum { | ||
MYKEY_BLOCK_KEY_ID = 0x07, | ||
MYKEY_BLOCK_PRODUCTION_DATE = 0x08, | ||
MYKEY_BLOCK_VENDOR_ID_1 = 0x18, | ||
MYKEY_BLOCK_VENDOR_ID_2 = 0x19, | ||
MYKEY_BLOCK_CURRENT_CREDIT = 0x21, | ||
MYKEY_BLOCK_PREVIOUS_CREDIT = 0x23, | ||
MYKEY_DEFAULT_VENDOR_ID = 0xFEDC0123, | ||
MYKEY_DEFAULT_VENDOR_ID_1 = 0xFEDC, | ||
MYKEY_DEFAULT_VENDOR_ID_2 = 0x0123, | ||
}; | ||
|
||
typedef enum { | ||
LockIdStatusNone, | ||
LockIdStatusActive, | ||
} LockIdStatus; | ||
|
||
/* Function to obtain the UID as a 32-bit */ | ||
uint32_t get_uid(const uint8_t uid[8]) { | ||
return (uid[7] | (uid[6] << 8) | (uid[5] << 16) | (uid[4] << 24)); | ||
} | ||
|
||
/* OTP calculation (reverse block 6, incremental. 1,2,3, ecc.) */ | ||
uint32_t new_get_count_down_counter(uint32_t b6) { | ||
return ~(b6 << 24 | (b6 & 0x0000FF00) << 8 | (b6 & 0x00FF0000) >> 8 | b6 >> 24); | ||
} | ||
|
||
/* Function to check if the vendor is bound */ | ||
int get_is_bound(uint32_t vendor_id) { | ||
return (vendor_id != MYKEY_DEFAULT_VENDOR_ID); | ||
} | ||
|
||
/* MK = UID * VENDOR */ | ||
uint32_t get_master_key(uint32_t uid, uint32_t vendor_id) { | ||
return uid * (vendor_id + 1); | ||
} | ||
|
||
/* SK (Encryption key) = MK * OTP */ | ||
uint32_t get_encryption_key(uint32_t master_key, uint32_t count_down_counter) { | ||
return master_key * (count_down_counter + 1); | ||
} | ||
|
||
/* Encode or decode a MyKey block */ | ||
uint32_t encode_decode_block(uint32_t input) { | ||
/* | ||
* Swap all values using XOR | ||
* 32 bit: 1111222233334444 | ||
*/ | ||
input ^= (input & 0x00C00000) << 6 | (input & 0x0000C000) << 12 | (input & 0x000000C0) << 18 | | ||
(input & 0x000C0000) >> 6 | (input & 0x00030000) >> 12 | (input & 0x00000300) >> 6; | ||
input ^= (input & 0x30000000) >> 6 | (input & 0x0C000000) >> 12 | (input & 0x03000000) >> 18 | | ||
(input & 0x00003000) << 6 | (input & 0x00000030) << 12 | (input & 0x0000000C) << 6; | ||
input ^= (input & 0x00C00000) << 6 | (input & 0x0000C000) << 12 | (input & 0x000000C0) << 18 | | ||
(input & 0x000C0000) >> 6 | (input & 0x00030000) >> 12 | (input & 0x00000300) >> 6; | ||
return input; | ||
} | ||
|
||
uint32_t get_block(uint32_t block) { | ||
return encode_decode_block(__bswap32(block)); | ||
} | ||
|
||
uint32_t get_xored_block(uint32_t block, uint32_t key) { | ||
return encode_decode_block(__bswap32(block) ^ key); | ||
} | ||
|
||
uint32_t get_vendor(uint32_t b1, uint32_t b2) { | ||
return b1 << 16 | (b2 & 0x0000FFFF); | ||
} | ||
|
||
static bool mykey_parse(const NfcDevice* device, FuriString* parsed_data) { | ||
furi_assert(device); | ||
furi_assert(parsed_data); | ||
|
||
bool parsed = false; | ||
|
||
do { | ||
//Get data | ||
const St25tbData* data = nfc_device_get_data(device, NfcProtocolSt25tb); | ||
|
||
//Calc data | ||
uint32_t _uid = get_uid(data->uid); | ||
uint32_t _count_down_counter_new = new_get_count_down_counter(__bswap32(data->blocks[6])); | ||
uint32_t _vendor_id = get_vendor( | ||
get_block(data->blocks[MYKEY_BLOCK_VENDOR_ID_1]), | ||
get_block(data->blocks[MYKEY_BLOCK_VENDOR_ID_2])); | ||
uint32_t _master_key = get_master_key(_uid, _vendor_id); | ||
uint32_t _encryption_key = get_encryption_key(_master_key, _count_down_counter_new); | ||
uint16_t credit = | ||
get_xored_block(data->blocks[MYKEY_BLOCK_CURRENT_CREDIT], _encryption_key); | ||
uint16_t _previous_credit = get_block(data->blocks[MYKEY_BLOCK_PREVIOUS_CREDIT]); | ||
bool _is_bound = get_is_bound(_vendor_id); | ||
|
||
//parse data | ||
furi_string_cat_printf(parsed_data, "\e#MyKey Card\n"); | ||
furi_string_cat_printf(parsed_data, "UID: %08lX\n", _uid); | ||
furi_string_cat_printf(parsed_data, "Vendor ID: %08lX\n", _vendor_id); | ||
furi_string_cat_printf( | ||
parsed_data, "Current Credit: %d.%02d E \n", credit / 100, credit % 100); | ||
furi_string_cat_printf( | ||
parsed_data, | ||
"Previus Credit: %d.%02d E \n", | ||
_previous_credit / 100, | ||
_previous_credit % 100); | ||
furi_string_cat_printf(parsed_data, "Is Bound: %s\n", _is_bound ? "yes" : "no"); | ||
|
||
parsed = true; | ||
} while(false); | ||
|
||
return parsed; | ||
} | ||
|
||
/* Actual implementation of app<>plugin interface */ | ||
static const NfcSupportedCardsPlugin mykey_plugin = { | ||
.protocol = NfcProtocolSt25tb, | ||
.verify = NULL, | ||
.read = NULL, | ||
.parse = mykey_parse, | ||
}; | ||
|
||
/* Plugin descriptor to comply with basic plugin specification */ | ||
static const FlipperAppPluginDescriptor mykey_plugin_descriptor = { | ||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, | ||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, | ||
.entry_point = &mykey_plugin, | ||
}; | ||
|
||
/* Plugin entry point - must return a pointer to const descriptor */ | ||
const FlipperAppPluginDescriptor* mykey_plugin_ep() { | ||
return &mykey_plugin_descriptor; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
// From: https://gitee.com/jadenwu/Saflok_KDF/blob/master/saflok.c | ||
// KDF published and reverse engineered by Jaden Wu | ||
// FZ plugin by @noproto | ||
|
||
#include "nfc_supported_card_plugin.h" | ||
#include <flipper_application/flipper_application.h> | ||
#include <nfc/nfc_device.h> | ||
#include <nfc/helpers/nfc_util.h> | ||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h> | ||
#include <stdint.h> | ||
|
||
#define TAG "Saflok" | ||
#define MAGIC_TABLE_SIZE 192 | ||
#define KEY_LENGTH 6 | ||
#define UID_LENGTH 4 | ||
#define CHECK_SECTOR 1 | ||
|
||
typedef struct { | ||
uint64_t a; | ||
uint64_t b; | ||
} MfClassicKeyPair; | ||
|
||
static MfClassicKeyPair saflok_1k_keys[] = { | ||
{.a = 0x000000000000, .b = 0xffffffffffff}, // 000 | ||
{.a = 0x2a2c13cc242a, .b = 0xffffffffffff}, // 001 | ||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, // 002 | ||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, // 003 | ||
{.a = 0x000000000000, .b = 0xffffffffffff}, // 004 | ||
{.a = 0x000000000000, .b = 0xffffffffffff}, // 005 | ||
{.a = 0x000000000000, .b = 0xffffffffffff}, // 006 | ||
{.a = 0x000000000000, .b = 0xffffffffffff}, // 007 | ||
{.a = 0x000000000000, .b = 0xffffffffffff}, // 008 | ||
{.a = 0x000000000000, .b = 0xffffffffffff}, // 009 | ||
{.a = 0x000000000000, .b = 0xffffffffffff}, // 010 | ||
{.a = 0x000000000000, .b = 0xffffffffffff}, // 011 | ||
{.a = 0x000000000000, .b = 0xffffffffffff}, // 012 | ||
{.a = 0x000000000000, .b = 0xffffffffffff}, // 013 | ||
{.a = 0x000000000000, .b = 0xffffffffffff}, // 014 | ||
{.a = 0x000000000000, .b = 0xffffffffffff}, // 015 | ||
}; | ||
|
||
void generate_saflok_key(const uint8_t* uid, uint8_t* key) { | ||
static const uint8_t magic_table[MAGIC_TABLE_SIZE] = { | ||
0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xF0, 0x57, 0xB3, 0x9E, 0xE3, 0xD8, 0x00, 0x00, 0xAA, | ||
0x00, 0x00, 0x00, 0x96, 0x9D, 0x95, 0x4A, 0xC1, 0x57, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, | ||
0x8F, 0x43, 0x58, 0x0D, 0x2C, 0x9D, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xFF, 0xCC, 0xE0, | ||
0x05, 0x0C, 0x43, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x34, 0x1B, 0x15, 0xA6, 0x90, 0xCC, | ||
0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x89, 0x58, 0x56, 0x12, 0xE7, 0x1B, 0x00, 0x00, 0xAA, | ||
0x00, 0x00, 0x00, 0xBB, 0x74, 0xB0, 0x95, 0x36, 0x58, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, | ||
0xFB, 0x97, 0xF8, 0x4B, 0x5B, 0x74, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xC9, 0xD1, 0x88, | ||
0x35, 0x9F, 0x92, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x8F, 0x92, 0xE9, 0x7F, 0x58, 0x97, | ||
0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x16, 0x6C, 0xA2, 0xB0, 0x9F, 0xD1, 0x00, 0x00, 0xAA, | ||
0x00, 0x00, 0x00, 0x27, 0xDD, 0x93, 0x10, 0x1C, 0x6C, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, | ||
0xDA, 0x3E, 0x3F, 0xD6, 0x49, 0xDD, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x58, 0xDD, 0xED, | ||
0x07, 0x8E, 0x3E, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x5C, 0xD0, 0x05, 0xCF, 0xD9, 0x07, | ||
0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x11, 0x8D, 0xD0, 0x01, 0x87, 0xD0}; | ||
|
||
uint8_t magic_byte = (uid[3] >> 4) + (uid[2] >> 4) + (uid[0] & 0x0F); | ||
uint8_t magickal_index = (magic_byte & 0x0F) * 12 + 11; | ||
|
||
uint8_t temp_key[KEY_LENGTH] = {magic_byte, uid[0], uid[1], uid[2], uid[3], magic_byte}; | ||
uint8_t carry_sum = 0; | ||
|
||
for(int i = KEY_LENGTH - 1; i >= 0; i--, magickal_index--) { | ||
uint16_t keysum = temp_key[i] + magic_table[magickal_index]; | ||
temp_key[i] = (keysum & 0xFF) + carry_sum; | ||
carry_sum = keysum >> 8; | ||
} | ||
|
||
memcpy(key, temp_key, KEY_LENGTH); | ||
} | ||
|
||
static bool saflok_verify(Nfc* nfc) { | ||
bool verified = false; | ||
|
||
do { | ||
const uint8_t block_num = mf_classic_get_first_block_num_of_sector(CHECK_SECTOR); | ||
FURI_LOG_D(TAG, "Saflok: Verifying sector %i", CHECK_SECTOR); | ||
|
||
MfClassicKey key = {0}; | ||
nfc_util_num2bytes(saflok_1k_keys[CHECK_SECTOR].a, COUNT_OF(key.data), key.data); | ||
|
||
MfClassicAuthContext auth_context; | ||
MfClassicError error = | ||
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context); | ||
if(error != MfClassicErrorNone) { | ||
FURI_LOG_D(TAG, "Saflok: Failed to read block %u: %d", block_num, error); | ||
break; | ||
} | ||
|
||
verified = true; | ||
} while(false); | ||
|
||
return verified; | ||
} | ||
|
||
static bool saflok_read(Nfc* nfc, NfcDevice* device) { | ||
FURI_LOG_D(TAG, "Entering Saflok KDF"); | ||
|
||
furi_assert(nfc); | ||
furi_assert(device); | ||
|
||
bool is_read = false; | ||
|
||
MfClassicData* data = mf_classic_alloc(); | ||
nfc_device_copy_data(device, NfcProtocolMfClassic, data); | ||
|
||
do { | ||
MfClassicType type = MfClassicType1k; | ||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); | ||
if(error != MfClassicErrorNone) break; | ||
data->type = type; | ||
|
||
size_t uid_len; | ||
const uint8_t* uid = mf_classic_get_uid(data, &uid_len); | ||
FURI_LOG_D( | ||
TAG, "Saflok: UID identified: %02X%02X%02X%02X", uid[0], uid[1], uid[2], uid[3]); | ||
if(uid_len != UID_LENGTH) break; | ||
|
||
uint8_t key[KEY_LENGTH]; | ||
generate_saflok_key(uid, key); | ||
uint64_t num_key = nfc_util_bytes2num(key, KEY_LENGTH); | ||
FURI_LOG_D(TAG, "Saflok: Key generated for UID: %012llX", num_key); | ||
|
||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { | ||
if(saflok_1k_keys[i].a == 0x000000000000) { | ||
saflok_1k_keys[i].a = num_key; | ||
} | ||
} | ||
|
||
MfClassicDeviceKeys keys = {}; | ||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { | ||
nfc_util_num2bytes(saflok_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); | ||
FURI_BIT_SET(keys.key_a_mask, i); | ||
nfc_util_num2bytes(saflok_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); | ||
FURI_BIT_SET(keys.key_b_mask, i); | ||
} | ||
|
||
error = mf_classic_poller_sync_read(nfc, &keys, data); | ||
if(error != MfClassicErrorNone) { | ||
FURI_LOG_W(TAG, "Failed to read data"); | ||
break; | ||
} | ||
|
||
nfc_device_set_data(device, NfcProtocolMfClassic, data); | ||
|
||
is_read = true; | ||
} while(false); | ||
|
||
mf_classic_free(data); | ||
|
||
return is_read; | ||
} | ||
|
||
/* Actual implementation of app<>plugin interface */ | ||
static const NfcSupportedCardsPlugin saflok_plugin = { | ||
.protocol = NfcProtocolMfClassic, | ||
.verify = saflok_verify, | ||
.read = saflok_read, | ||
// KDF mode | ||
.parse = NULL, | ||
}; | ||
|
||
/* Plugin descriptor to comply with basic plugin specification */ | ||
static const FlipperAppPluginDescriptor saflok_plugin_descriptor = { | ||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, | ||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, | ||
.entry_point = &saflok_plugin, | ||
}; | ||
|
||
/* Plugin entry point - must return a pointer to const descriptor */ | ||
const FlipperAppPluginDescriptor* saflok_plugin_ep() { | ||
return &saflok_plugin_descriptor; | ||
} |