Skip to content

Commit

Permalink
chore: add ported mfkey32 and mfkey64
Browse files Browse the repository at this point in the history
  • Loading branch information
li0ard committed Feb 5, 2025
1 parent b09e578 commit 354c5d4
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 4 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ bunx i jsr install @li0ard/crapto1-ts

## Usage

> [!TIP]
> Ported mfkey64 and mfkey32 are placed [here](https://github.com/li0ard/crapto1_ts/tree/main/examples)
### Recovery by 2 sets of 32 bit auth
```ts
import { recovery32 } from "@li0ard/crapto1_ts" // or @li0ard/crapto1-ts
Expand Down
66 changes: 66 additions & 0 deletions examples/mfkey32.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* mfkey32 for TypeScript
*
* Ported from https://github.com/equipter/mfkey by li0ard
*/

import { crypto1_word, lfsr_recovery32, lfsr_rollback_word, prng_successor } from "../src";

if (process.argv.length < 9) {
console.log('Usage: [bun/node] ' + process.argv[1] + ' <uid> <tag challenge> <reader challenge> <reader response> <tag challenge #2> <reader challenge #2> <reader response #2>')
process.exit(1)
}

const dec2hex = (dec: number, bits: number) => {
if (dec < 0) {
return (Math.pow(2, bits) + dec).toString(16).padStart(bits / 4, '0')
} else {
return dec.toString(16).padStart(bits / 4, '0');
}
}

const uid = parseInt(process.argv[2], 16)
const chal = parseInt(process.argv[3], 16)
const rchal = parseInt(process.argv[4], 16)
const rresp = parseInt(process.argv[5], 16)
const chal2 = parseInt(process.argv[6], 16)
const rchal2 = parseInt(process.argv[7], 16)
const rresp2 = parseInt(process.argv[8], 16)

console.log(`MIFARE Classic key recovery - based 32 bits of keystream
Recover key from two 32-bit reader authentication answers only
Recovering key for:
uid: ${dec2hex(uid, 32)}
nt_0: ${dec2hex(chal, 32)}
{nr_0}: ${dec2hex(rchal, 32)}
{ar_0}: ${dec2hex(rresp, 32)}
nt_1: ${dec2hex(chal2, 32)}
{nr_1}: ${dec2hex(rchal2, 32)}
{ar_1}: ${dec2hex(rresp2, 32)}\n`)

const p64 = prng_successor(chal, 64)
const p64b = prng_successor(chal2, 64);

console.log(`LFSR successors of the tag challenge:
nt': ${dec2hex(p64, 32)}
nt'': ${dec2hex(p64b, 32)}\n`)

let ks2 = rresp ^ p64;

console.log(`Keystream used to generate {ar} and {at}:
ks2: ${dec2hex(ks2, 32)}\n`)

let s = lfsr_recovery32(rresp ^ p64, 0);
for (let t = 0; (s[t].odd !== 0) || (s[t].even !== 0); ++t) {
lfsr_rollback_word(s[t], 0, false);
lfsr_rollback_word(s[t], rchal, true);
lfsr_rollback_word(s[t], uid ^ chal, false);
let key = s[t].lfsr
crypto1_word(s[t], uid ^ chal2, false);
crypto1_word(s[t], rchal2, true);
if (rresp2 === (crypto1_word(s[t], 0, false) ^ prng_successor(chal2, 64))) {
console.log(`Found Key: [${key.toString(16).padStart(12, "0")}]`)
break;
}
}
89 changes: 89 additions & 0 deletions examples/mfkey64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* mfkey64 for TypeScript
*
* Ported from https://github.com/equipter/mfkey by li0ard
*/
import { crypto1_byte, lfsr_recovery64, lfsr_rollback_byte, lfsr_rollback_word, prng_successor } from "../src"

if (process.argv.length < 7) {
console.log('Usage: [bun/node] ' + process.argv[1] + ' <uid> <tag challenge> <reader challenge> <reader response> <tag response>')
process.exit(1)
}

const dec2hex = (dec: number, bits: number) => {
if (dec < 0) {
return (Math.pow(2, bits) + dec).toString(16).padStart(bits / 4, '0')
} else {
return dec.toString(16).padStart(bits / 4, '0');
}
}

const uid = parseInt(process.argv[2], 16)
const chal = parseInt(process.argv[3], 16)
const rchal = parseInt(process.argv[4], 16)
const rresp = parseInt(process.argv[5], 16)
const tresp = parseInt(process.argv[6], 16)

let encc = process.argv.length - 7
let enclen: number[] = Array(encc)
let enc: number[][] = Array.from({ length: encc }, () => new Array(120));

for (let i = 0; i < encc; i++) {
enclen[i] = (process.argv[i + 7].length) / 2;
for (let i2 = 0; i2 < enclen[i]; i2++) {
enc[i][i2] = parseInt(process.argv[i + 7].substring(i2 * 2, i2 * 2 + 2), 16)
}
}

console.log(`MIFARE Classic key recovery - based 64 bits of keystream
Recovering key for:
uid: ${dec2hex(uid, 32)}
nt: ${dec2hex(chal, 32)}
{nr}: ${dec2hex(rchal, 32)}
{ar}: ${dec2hex(rresp, 32)}
{at}: ${dec2hex(tresp, 32)}\n`)

for (let i = 0; i < encc; i++) {
process.stdout.write(`{enc${i}}: `)
for (let i2 = 0; i2 < enclen[i]; i2++) {
process.stdout.write(dec2hex(enc[i][i2], 8))
}
console.log("")
}

const p64 = prng_successor(chal, 64)
console.log(`LFSR successors of the tag challenge:
nt': ${dec2hex(p64, 32)}
nt'': ${dec2hex(prng_successor(p64, 32), 32)}\n`)

const ks2 = rresp ^ p64
const ks3 = tresp ^ prng_successor(p64, 32)

console.log(`Keystream used to generate {ar} and {at}:
ks2: ${dec2hex(ks2, 32)}
ks3: ${dec2hex(ks3, 32)}\n`)

let s = lfsr_recovery64(ks2, ks3)[0]

if(process.argv.length > 7) {
console.log("Decrypted communication:")
let ks4 = 0;
let rollb = 0;
for (let i = 0; i < encc; i++) {
process.stdout.write(`{dec${i}}: `)
for (let i2 = 0; i2 < enclen[i]; i2++) {
ks4 = crypto1_byte(s, 0, false);
process.stdout.write(dec2hex(ks4 ^ enc[i][i2], 8))
rollb += 1;
}
console.log("")
}
for (let i = 0; i < rollb; i++) lfsr_rollback_byte(s, 0, false)
}

lfsr_rollback_word(s, 0)
lfsr_rollback_word(s, 0)
lfsr_rollback_word(s, rchal, true)
lfsr_rollback_word(s, uid^chal)

console.log(`\nFound Key: [${s.lfsr.toString(16).padEnd(12, "0")}]\n`)
17 changes: 16 additions & 1 deletion src/crapto1.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { prng_successor } from "./crypto1";
import { LF_POLY_EVEN, LF_POLY_ODD, crypto1_word } from "./crypto1";
import { Crypto1State } from "./state";
import { bebit, binsearch, evenParity32, extend_table, extend_table_simple, filter, parity, quicksort } from "./utils";
import { bebit, binsearch, bit, evenParity32, extend_table, extend_table_simple, filter, parity, quicksort } from "./utils";

/**
* Rollback the shift register in order to get previous states (for bits)
Expand All @@ -26,6 +26,21 @@ export const lfsr_rollback_bit = (s: Crypto1State, in_: number, isEncrypted: boo
return ret;
}

/**
* Rollback the shift register in order to get previous states (for bytes)
* @param s State
* @param in_ Word
* @param isEncrypted Encrypted?
* @returns {number}
*/
export const lfsr_rollback_byte = (s: Crypto1State, in_: number, isEncrypted: boolean = false): number => {
let ret: number = 0;
for (let i = 7; i >= 0; --i) {
ret |= lfsr_rollback_bit(s, bit(in_, i), isEncrypted) << i;
}
return ret;
}

/**
* Rollback the shift register in order to get previous states (for words (uint32))
* @param s State
Expand Down
17 changes: 16 additions & 1 deletion src/crypto1.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Crypto1State } from "./state";
import { bebit, filter, parity, swapendian } from "./utils";
import { bebit, bit, filter, parity, swapendian } from "./utils";

export const LF_POLY_ODD: number = 0x29CE5C;
export const LF_POLY_EVEN: number = 0x870804;
Expand Down Expand Up @@ -33,6 +33,21 @@ export const crypto1_word = (s: Crypto1State, in_: number, isEncrypted: boolean
return ret;
}

/**
* Proceed Crypto1 encryption/decryption process (for bytes)
* @param s State
* @param in_ Word
* @param isEncrypted Encrypted?
* @returns {number}
*/
export const crypto1_byte = (s: Crypto1State, in_: number, isEncrypted: boolean = false): number => {
let i: number, ret: number = 0;
for (i = 0; i < 8; ++i) {
ret |= crypto1_bit(s, bit(in_, i), isEncrypted) << i;
}
return ret;
}

/**
* Proceed Crypto1 encryption/decryption process (for bits)
* @param s State
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { recovery32, recovery64, lfsr_recovery32, lfsr_recovery64, lfsr_rollback_bit, lfsr_rollback_word } from "./crapto1"
export { recovery32, recovery64, lfsr_recovery32, lfsr_recovery64, lfsr_rollback_bit, lfsr_rollback_byte, lfsr_rollback_word } from "./crapto1"
export { Crypto1State } from "./state"
export { prng_successor, crypto1_bit, crypto1_word } from "./crypto1"
export { prng_successor, crypto1_bit, crypto1_byte, crypto1_word } from "./crypto1"

0 comments on commit 354c5d4

Please sign in to comment.