Implementing hf mf hardnested:

- move code to separate files mfhardnested.[ch]
- add: create partial statelists for all possible partial Sum Properties
- add: create candidate states based on Sum(a0) and Sum(a8)
- add: show size of remaining key space
This commit is contained in:
pwpiwi 2015-10-27 14:09:14 +01:00
commit 3a8f9b79b0
4 changed files with 11 additions and 496 deletions

View file

@ -57,14 +57,14 @@ CORESRCS = uart.c \
CMDSRCS = nonce2key/crapto1.c\ CMDSRCS = nonce2key/crapto1.c\
nonce2key/crypto1.c\ nonce2key/crypto1.c\
nonce2key/nonce2key.c\ nonce2key/nonce2key.c\
loclass/cipher.c \ loclass/cipher.c \
loclass/cipherutils.c \ loclass/cipherutils.c \
loclass/des.c \ loclass/des.c \
loclass/ikeys.c \ loclass/ikeys.c \
loclass/elite_crack.c\ loclass/elite_crack.c\
loclass/fileutils.c\ loclass/fileutils.c\
mifarehost.c\ mifarehost.c\
crc.c \ crc.c \
crc16.c \ crc16.c \
@ -85,6 +85,7 @@ CMDSRCS = nonce2key/crapto1.c\
cmdhficlass.c \ cmdhficlass.c \
cmdhfmf.c \ cmdhfmf.c \
cmdhfmfu.c \ cmdhfmfu.c \
cmdhfmfhard.c \
cmdhw.c \ cmdhw.c \
cmdlf.c \ cmdlf.c \
cmdlfio.c \ cmdlfio.c \

View file

@ -9,6 +9,7 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include "cmdhfmf.h" #include "cmdhfmf.h"
#include "cmdhfmfhard.h"
static int CmdHelp(const char *Cmd); static int CmdHelp(const char *Cmd);
@ -873,8 +874,7 @@ int CmdHF14AMfNestedHard(const char *Cmd)
switch (isOK) { switch (isOK) {
case 1 : PrintAndLog("Error: No response from Proxmark.\n"); break; case 1 : PrintAndLog("Error: No response from Proxmark.\n"); break;
case 2 : PrintAndLog("Button pressed. Aborted.\n"); break; case 2 : PrintAndLog("Button pressed. Aborted.\n"); break;
case 3 : PrintAndLog("File error. Aborted.\n"); break; default : break;
default : PrintAndLog("Unknown Error.\n");
} }
return 2; return 2;
} }

View file

@ -12,7 +12,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <pthread.h> #include <pthread.h>
#include <math.h>
#include "mifarehost.h" #include "mifarehost.h"
#include "proxmark3.h" #include "proxmark3.h"
@ -195,490 +194,6 @@ int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t * key, uint8_t trgBlockNo
} }
typedef struct noncelistentry {
uint32_t nonce_enc;
uint8_t par_enc;
void *next;
} noncelistentry_t;
typedef struct noncelist {
uint16_t num;
uint16_t Sum;
bool updated;
noncelistentry_t *first;
} noncelist_t;
static noncelist_t nonces[256];
static uint16_t first_byte_Sum = 0;
static uint16_t first_byte_num = 0;
static uint8_t best_first_byte;
static uint16_t guessed_Sum8;
static float guessed_Sum8_confidence;
static int add_nonce(uint32_t nonce_enc, uint8_t par_enc)
{
uint8_t first_byte = nonce_enc >> 24;
noncelistentry_t *p1 = nonces[first_byte].first;
noncelistentry_t *p2 = NULL;
if (p1 == NULL) { // first nonce with this 1st byte
first_byte_num++;
first_byte_Sum += parity((nonce_enc & 0xff000000) | (par_enc & 0x08) | 0x01); // 1st byte sum property. Note: added XOR 1
// printf("Adding nonce 0x%08x, par_enc 0x%02x, parity(0x%08x) = %d\n",
// nonce_enc,
// par_enc,
// (nonce_enc & 0xff000000) | (par_enc & 0x08) |0x01,
// parity((nonce_enc & 0xff000000) | (par_enc & 0x08) | 0x01));
}
while (p1 != NULL && (p1->nonce_enc & 0x00ff0000) < (nonce_enc & 0x00ff0000)) {
p2 = p1;
p1 = p1->next;
}
if (p1 == NULL) { // need to add at the end of the list
if (p2 == NULL) { // list is empty yet. Add first entry.
p2 = nonces[first_byte].first = malloc(sizeof(noncelistentry_t));
} else { // add new entry at end of existing list.
p2 = p2->next = malloc(sizeof(noncelistentry_t));
}
} else if ((p1->nonce_enc & 0x00ff0000) != (nonce_enc & 0x00ff0000)) { // found distinct 2nd byte. Need to insert.
if (p2 == NULL) { // need to insert at start of list
p2 = nonces[first_byte].first = malloc(sizeof(noncelistentry_t));
} else {
p2 = p2->next = malloc(sizeof(noncelistentry_t));
}
} else { // we have seen this 2nd byte before. Nothing to add or insert.
return (0);
}
// add or insert new data
p2->next = p1;
p2->nonce_enc = nonce_enc;
p2->par_enc = par_enc;
nonces[first_byte].num++;
nonces[first_byte].Sum += parity((nonce_enc & 0x00ff0000) | (par_enc & 0x04)); // 2nd byte sum property. Note: added XOR 1
nonces[first_byte].updated = true; // indicates that we need to recalculate the Sum(a8) probability for this first byte
return (1); // new nonce added
}
static uint16_t SumPropertyOdd(struct Crypto1State *s)
{
uint16_t oddsum = 0;
for (uint16_t j = 0; j < 16; j++) {
uint32_t oddstate = s->odd;
uint16_t part_sum = 0;
for (uint16_t i = 0; i < 5; i++) {
part_sum ^= filter(oddstate);
oddstate = (oddstate << 1) | ((j >> (3-i)) & 0x01) ;
}
oddsum += part_sum;
}
return oddsum;
}
static uint16_t SumPropertyEven(struct Crypto1State *s)
{
uint16_t evensum = 0;
for (uint16_t j = 0; j < 16; j++) {
uint32_t evenstate = s->even;
uint16_t part_sum = 0;
for (uint16_t i = 0; i < 4; i++) {
evenstate = (evenstate << 1) | ((j >> (3-i)) & 0x01) ;
part_sum ^= filter(evenstate);
}
evensum += part_sum;
}
return evensum;
}
static uint16_t SumProperty(struct Crypto1State *s)
{
uint16_t sum_odd = SumPropertyOdd(s);
uint16_t sum_even = SumPropertyEven(s);
return (sum_odd*(16-sum_even) + (16-sum_odd)*sum_even);
}
static double p_hypergeometric(uint16_t N, uint16_t K, uint16_t n, uint16_t k)
{
// for efficient computation we are using the recursive definition
// (K-k+1) * (n-k+1)
// P(X=k) = P(X=k-1) * --------------------
// k * (N-K-n+k)
// and
// (N-K)*(N-K-1)*...*(N-K-n+1)
// P(X=0) = -----------------------------
// N*(N-1)*...*(N-n+1)
if (n-k > N-K || k > K) return 0.0; // avoids log(x<=0) in calculation below
if (k == 0) {
// use logarithms to avoid overflow with huge factorials (double type can only hold 170!)
double log_result = 0.0;
for (int16_t i = N-K; i >= N-K-n+1; i--) {
log_result += log(i);
}
for (int16_t i = N; i >= N-n+1; i--) {
log_result -= log(i);
}
return exp(log_result);
} else {
if (n-k == N-K) { // special case. The published recursion below would fail with a divide by zero exception
double log_result = 0.0;
for (int16_t i = k+1; i <= n; i++) {
log_result += log(i);
}
for (int16_t i = K+1; i <= N; i++) {
log_result -= log(i);
}
return exp(log_result);
} else { // recursion
return (p_hypergeometric(N, K, n, k-1) * (K-k+1) * (n-k+1) / (k * (N-K-n+k)));
}
}
}
static float sum_probability(uint16_t K, uint16_t n, uint16_t k)
{
const uint16_t N = 256;
const float p[257] = {
0.0289, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0083, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0006, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0339, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0049, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0934, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0119, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0489, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0602, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.4180, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0602, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0489, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0119, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0934, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0049, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0339, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0006, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0083, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
0.0289 };
if (k > K || p[K] == 0.0) return 0.0;
double p_T_is_k_when_S_is_K = p_hypergeometric(N, K, n, k);
double p_S_is_K = p[K];
double p_T_is_k = 0;
for (uint16_t i = 0; i <= 256; i++) {
if (p[i] != 0.0) {
p_T_is_k += p[i] * p_hypergeometric(N, i, n, k);
}
}
return(p_T_is_k_when_S_is_K * p_S_is_K / p_T_is_k);
}
static void Tests()
{
#define NUM_STATISTICS 100000
uint32_t statistics[257];
struct Crypto1State cs;
time_t time1 = clock();
for (uint16_t i = 0; i < 257; i++) {
statistics[i] = 0;
}
for (uint64_t i = 0; i < NUM_STATISTICS; i++) {
cs.odd = (rand() & 0xfff) << 12 | (rand() & 0xfff);
cs.even = (rand() & 0xfff) << 12 | (rand() & 0xfff);
uint16_t sum_property = SumProperty(&cs);
statistics[sum_property] += 1;
if (i%(NUM_STATISTICS/100) == 0) printf(".");
}
printf("\nTests: Calculated %d Sum properties in %0.3f seconds (%0.0f calcs/second)\n", NUM_STATISTICS, ((float)clock() - time1)/CLOCKS_PER_SEC, NUM_STATISTICS/((float)clock() - time1)*CLOCKS_PER_SEC);
for (uint16_t i = 0; i < 257; i++) {
if (statistics[i] != 0) {
printf("probability[%3d] = %0.5f\n", i, (float)statistics[i]/NUM_STATISTICS);
}
}
// printf("Tests: probabilities for n=15, k=5:\n");
// for (uint16_t i = 0; i < 257; i++) {
// if (statistics[i] != 0) {
// printf("p[%3d] = %0.4f\n", i, sum_probability(i, 15, 5));
// }
// }
printf("\nTests: Hypergeometric Probability for selected parameters\n");
printf("p_hypergeometric(256, 206, 255, 206) = %0.8f\n", p_hypergeometric(256, 206, 255, 206));
printf("p_hypergeometric(256, 206, 255, 205) = %0.8f\n", p_hypergeometric(256, 206, 255, 205));
printf("p_hypergeometric(256, 156, 1, 1) = %0.8f\n", p_hypergeometric(256, 156, 1, 1));
printf("p_hypergeometric(256, 156, 1, 0) = %0.8f\n", p_hypergeometric(256, 156, 1, 0));
printf("p_hypergeometric(256, 1, 1, 1) = %0.8f\n", p_hypergeometric(256, 1, 1, 1));
printf("p_hypergeometric(256, 1, 1, 0) = %0.8f\n", p_hypergeometric(256, 1, 1, 0));
struct Crypto1State *pcs;
pcs = crypto1_create(0xffffffffffff);
printf("\nTests: Sum(a0)=%d for key = 0xffffffffffff\n", SumProperty(pcs));
crypto1_destroy(pcs);
pcs = crypto1_create(0xa0a1a2a3a4a5);
printf("\nTests: Sum(a0)=%d for key = 0xa0a1a2a3a4a5\n", SumProperty(pcs));
crypto1_destroy(pcs);
}
static float estimate_second_byte_sum(void)
{
float confidence = guessed_Sum8_confidence;
for (uint16_t first_byte = 0; first_byte < 256; first_byte++) {
if (nonces[first_byte].updated) {
for (uint16_t sum = 0; sum <= 256; sum++) {
float prob = sum_probability(sum, nonces[first_byte].num, nonces[first_byte].Sum);
if (prob > confidence) {
confidence = prob;
best_first_byte = first_byte;
guessed_Sum8 = sum;
}
}
nonces[first_byte].updated = false;
}
}
return confidence;
}
static int read_nonce_file(void)
{
FILE *fnonces = NULL;
uint32_t cuid;
uint8_t trgBlockNo;
uint8_t trgKeyType;
uint8_t read_buf[9];
uint32_t nt_enc1, nt_enc2;
uint8_t par_enc;
int total_num_nonces = 0;
if ((fnonces = fopen("nonces.bin","rb")) == NULL) {
PrintAndLog("Could not open file nonces.bin");
return 1;
}
PrintAndLog("Reading nonces from file nonces.bin...");
if (fread(read_buf, 1, 6, fnonces) == 0) {
PrintAndLog("File reading error.");
fclose(fnonces);
return 1;
}
cuid = bytes_to_num(read_buf, 4);
trgBlockNo = bytes_to_num(read_buf+4, 1);
trgKeyType = bytes_to_num(read_buf+5, 1);
while (fread(read_buf, 1, 9, fnonces) == 9) {
nt_enc1 = bytes_to_num(read_buf, 4);
nt_enc2 = bytes_to_num(read_buf+4, 4);
par_enc = bytes_to_num(read_buf+8, 1);
//printf("Encrypted nonce: %08x, encrypted_parity: %02x\n", nt_enc1, par_enc >> 4);
//printf("Encrypted nonce: %08x, encrypted_parity: %02x\n", nt_enc2, par_enc & 0x0f);
add_nonce(nt_enc1, par_enc >> 4);
add_nonce(nt_enc2, par_enc & 0x0f);
total_num_nonces += 2;
}
fclose(fnonces);
PrintAndLog("Read %d nonces from file. cuid=%08x, Block=%d, Keytype=%c", total_num_nonces, cuid, trgBlockNo, trgKeyType==0?'A':'B');
return 0;
}
int static acquire_nonces(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, bool nonce_file_write, bool slow)
{
clock_t time1 = clock();
bool initialize = true;
bool field_off = false;
bool finished = false;
uint32_t flags = 0;
uint8_t write_buf[9];
uint32_t total_num_nonces = 0;
uint32_t next_thousand = 1000;
uint32_t total_added_nonces = 0;
FILE *fnonces = NULL;
UsbCommand resp;
uint32_t cuid;
#define CONFIDENCE_THRESHOLD 0.95 // Collect nonces until we are certain enough to have guessed Sum(a8) correctly
clearCommandBuffer();
do {
flags = 0;
flags |= initialize ? 0x0001 : 0;
flags |= slow ? 0x0002 : 0;
flags |= field_off ? 0x0004 : 0;
UsbCommand c = {CMD_MIFARE_ACQUIRE_ENCRYPTED_NONCES, {blockNo + keyType * 0x100, trgBlockNo + trgKeyType * 0x100, flags}};
memcpy(c.d.asBytes, key, 6);
SendCommand(&c);
if (field_off) finished = true;
if (initialize) {
if (!WaitForResponseTimeout(CMD_ACK, &resp, 3000)) return 1;
if (resp.arg[0]) return resp.arg[0]; // error during nested_hard
cuid = resp.arg[1];
// PrintAndLog("Acquiring nonces for CUID 0x%08x", cuid);
if (nonce_file_write && fnonces == NULL) {
if ((fnonces = fopen("nonces.bin","wb")) == NULL) {
PrintAndLog("Could not create file nonces.bin");
return 3;
}
PrintAndLog("Writing acquired nonces to binary file nonces.bin");
num_to_bytes(cuid, 4, write_buf);
fwrite(write_buf, 1, 4, fnonces);
fwrite(&trgBlockNo, 1, 1, fnonces);
fwrite(&trgKeyType, 1, 1, fnonces);
}
}
if (!initialize) {
uint32_t nt_enc1, nt_enc2;
uint8_t par_enc;
uint16_t num_acquired_nonces = resp.arg[2];
uint8_t *bufp = resp.d.asBytes;
for (uint16_t i = 0; i < num_acquired_nonces; i+=2) {
nt_enc1 = bytes_to_num(bufp, 4);
nt_enc2 = bytes_to_num(bufp+4, 4);
par_enc = bytes_to_num(bufp+8, 1);
//printf("Encrypted nonce: %08x, encrypted_parity: %02x\n", nt_enc1, par_enc >> 4);
total_added_nonces += add_nonce(nt_enc1, par_enc >> 4);
//printf("Encrypted nonce: %08x, encrypted_parity: %02x\n", nt_enc2, par_enc & 0x0f);
total_added_nonces += add_nonce(nt_enc2, par_enc & 0x0f);
if (nonce_file_write) {
fwrite(bufp, 1, 9, fnonces);
}
bufp += 9;
}
total_num_nonces += num_acquired_nonces;
}
if (first_byte_num == 256 ) {
// printf("first_byte_num = %d, first_byte_Sum = %d\n", first_byte_num, first_byte_Sum);
float last_confidence = guessed_Sum8_confidence;
guessed_Sum8_confidence = estimate_second_byte_sum();
if (guessed_Sum8_confidence > last_confidence || total_num_nonces > next_thousand) {
next_thousand = (total_num_nonces/1000+1) * 1000;
PrintAndLog("Acquired %5d nonces (%5d with distinct bytes 0 and 1). Guessed Sum(a8) = %3d for first nonce byte = 0x%02x, probability for correct guess = %1.2f%%",
total_num_nonces,
total_added_nonces,
guessed_Sum8,
best_first_byte,
guessed_Sum8_confidence*100);
}
if (guessed_Sum8_confidence >= CONFIDENCE_THRESHOLD) {
field_off = true; // switch off field with next SendCommand and then finish
}
}
if (!initialize) {
if (!WaitForResponseTimeout(CMD_ACK, &resp, 3000)) return 1;
if (resp.arg[0]) return resp.arg[0]; // error during nested_hard
}
initialize = false;
} while (!finished);
if (nonce_file_write) {
fclose(fnonces);
}
PrintAndLog("Acquired a total of %d nonces in %1.1f seconds (%d nonces/minute)",
total_num_nonces,
((float)clock()-time1)/CLOCKS_PER_SEC,
total_num_nonces*60*CLOCKS_PER_SEC/(clock()-time1));
return 0;
}
int mfnestedhard(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, bool nonce_file_read, bool nonce_file_write, bool slow)
{
// initialize the list of nonces
for (uint16_t i = 0; i < 256; i++) {
nonces[i].num = 0;
nonces[i].Sum = 0;
nonces[i].first = NULL;
nonces[i].updated = true;
}
first_byte_num = 0;
first_byte_Sum = 0;
guessed_Sum8 = 0;
best_first_byte = 0;
guessed_Sum8_confidence = 0.0;
//StateList_t statelists[2];
//struct Crypto1State *p1, *p2, *p3, *p4;
if (nonce_file_read) { // use pre-acquired data from file nonces.bin
if (read_nonce_file() != 0) {
return 3;
}
guessed_Sum8_confidence = estimate_second_byte_sum();
} else { // acquire nonces.
uint16_t is_OK = acquire_nonces(blockNo, keyType, key, trgBlockNo, trgKeyType, nonce_file_write, slow);
if (is_OK != 0) {
return is_OK;
}
}
Tests();
PrintAndLog("");
PrintAndLog("Sum(a0) = %d", first_byte_Sum);
PrintAndLog("Guess for Sum(a8) = %d for first nonce byte = 0x%02x, n = %d, k = %d, probability for correct guess = %1.0f%%\n",
guessed_Sum8,
best_first_byte,
nonces[best_first_byte].num,
nonces[best_first_byte].Sum,
guessed_Sum8_confidence*100);
PrintAndLog("Generation of candidate list and brute force phase not yet implemented");
return 0;
}
int mfCheckKeys (uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t * keyBlock, uint64_t * key){ int mfCheckKeys (uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t * keyBlock, uint64_t * key){
*key = 0; *key = 0;

View file

@ -50,7 +50,6 @@ typedef struct {
extern char logHexFileName[FILE_PATH_SIZE]; extern char logHexFileName[FILE_PATH_SIZE];
int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t * key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t * ResultKeys, bool calibrate); int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t * key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t * ResultKeys, bool calibrate);
int mfnestedhard(uint8_t blockNo, uint8_t keyType, uint8_t * key, uint8_t trgBlockNo, uint8_t trgKeyType, bool nonce_file_read, bool nonce_file_write, bool slow);
int mfCheckKeys (uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t * keyBlock, uint64_t * key); int mfCheckKeys (uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t * keyBlock, uint64_t * key);
int mfEmlGetMem(uint8_t *data, int blockNum, int blocksCount); int mfEmlGetMem(uint8_t *data, int blockNum, int blocksCount);