diff --git a/README.md b/README.md index 11a9f72..a3250ac 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/examples/mfkey32.ts b/examples/mfkey32.ts new file mode 100644 index 0000000..1e7d27d --- /dev/null +++ b/examples/mfkey32.ts @@ -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] + ' ') + 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; + } +} \ No newline at end of file diff --git a/examples/mfkey64.ts b/examples/mfkey64.ts new file mode 100644 index 0000000..11c83b6 --- /dev/null +++ b/examples/mfkey64.ts @@ -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] + ' ') + 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`) \ No newline at end of file diff --git a/src/crapto1.ts b/src/crapto1.ts index 2d3ce37..b3d407d 100644 --- a/src/crapto1.ts +++ b/src/crapto1.ts @@ -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) @@ -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 diff --git a/src/crypto1.ts b/src/crypto1.ts index ce70873..40a8e1c 100644 --- a/src/crypto1.ts +++ b/src/crypto1.ts @@ -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; @@ -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 diff --git a/src/index.ts b/src/index.ts index d9d6b8d..3ed7422 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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" \ No newline at end of file +export { prng_successor, crypto1_bit, crypto1_byte, crypto1_word } from "./crypto1" \ No newline at end of file