@li0ard/crapto1_ts
Version:
Crapto1 TypeScript implement
294 lines (293 loc) • 11.1 kB
JavaScript
import { prng_successor } from "./crypto1";
import { LF_POLY_EVEN, LF_POLY_ODD, crypto1_word } from "./crypto1";
import { Crypto1State } from "./state";
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)
* @param s State
* @param input Input bit
* @param isEncrypted Is input bit encrypted?
* @returns {number} LFSR output bit
*/
export const lfsr_rollback_bit = (s, input, isEncrypted = false) => {
let ret;
let t;
s.odd &= 0xffffff;
t = s.odd;
s.odd = s.even;
s.even = t;
let out = s.even & 1;
out ^= LF_POLY_EVEN & (s.even >>= 1);
out ^= LF_POLY_ODD & s.odd;
out ^= (input !== 0) ? 1 : 0;
out ^= (ret = s.peekCrypto1Bit) & ((isEncrypted) ? 1 : 0);
s.even |= parity(out) << 23;
return ret;
};
/**
* Rollback the shift register in order to get previous states (for bytes)
* @param s State
* @param input Input byte
* @param isEncrypted Is input byte encrypted?
* @returns {number} LFSR output byte
*/
export const lfsr_rollback_byte = (s, input, isEncrypted = false) => {
let ret = 0;
for (let i = 7; i >= 0; --i) {
ret |= lfsr_rollback_bit(s, bit(input, i), isEncrypted) << i;
}
return ret;
};
/**
* Rollback the shift register in order to get previous states (for words (uint32))
* @param s State
* @param input Input word
* @param isEncrypted Is input word encrypted?
* @returns {number} LFSR output word
*/
export const lfsr_rollback_word = (s, input, isEncrypted = false) => {
let ret = 0;
for (let i = 31; i >= 0; --i) {
ret |= lfsr_rollback_bit(s, bebit(input, i), isEncrypted) << (i ^ 24);
}
return ret;
};
/** Recursively narrow down the search space, 4 bits of keystream at a time */
const recover = (odd, o_head, o_tail, oks, even, e_head, e_tail, eks, rem, sl, s, input) => {
let o, e, i;
if (rem === -1) {
for (e = e_head; e <= e_tail; ++e) {
even[e] = even[e] << 1 ^ parity(even[e] & LF_POLY_EVEN) ^ (((input & 4) !== 0) ? 1 : 0);
for (o = o_head; o <= o_tail; ++o, ++s) {
sl[s].even = odd[o];
sl[s].odd = even[e] ^ parity(odd[o] & LF_POLY_ODD);
sl[s + 1].odd = sl[s + 1].even = 0;
}
}
return s;
}
for (i = 0; (i < 4) && (rem-- !== 0); i++) {
oks >>>= 1;
eks >>>= 1;
input >>>= 2;
o_tail = extend_table(odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0);
if (o_head > o_tail)
return s;
e_tail = extend_table(even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, input & 3);
if (e_head > e_tail)
return s;
}
quicksort(odd, o_head, o_tail);
quicksort(even, e_head, e_tail);
while (o_tail >= o_head && e_tail >= e_head) {
if (((odd[o_tail] ^ even[e_tail]) >>> 24) === 0) {
o_tail = binsearch(odd, o_head, o = o_tail);
e_tail = binsearch(even, e_head, e = e_tail);
s = recover(odd, o_tail--, o, oks, even, e_tail--, e, eks, rem, sl, s, input);
}
else if ((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) {
o_tail = binsearch(odd, o_head, o_tail) - 1;
}
else {
e_tail = binsearch(even, e_head, e_tail) - 1;
}
}
return s;
};
/**
* Recovery possible states from keystream from two's partial auth's
* @param ks2 Keystream (32 -> 63)
* @param input Value that was fed into lfsr at time keystream was generated
* @returns {Crypto1State[]}
*/
export const lfsr_recovery32 = (ks2, input) => {
const statelist = Array.from({ length: 1 << 18 }, () => new Crypto1State());
let stl = 0;
const odd = Array(1 << 21).fill(0);
const even = Array(1 << 21).fill(0);
let odd_head = 0, odd_tail = -1, oks = 0;
let even_head = 0, even_tail = -1, eks = 0;
for (let i = 31; i >= 0; i -= 2) {
oks = oks << 1 | bebit(ks2, i);
}
for (let i = 30; i >= 0; i -= 2) {
eks = eks << 1 | bebit(ks2, i);
}
statelist[stl].odd = statelist[stl].even = 0;
for (let i = 1 << 20; i >= 0; --i) {
if (filter(i) === (oks & 1)) {
odd[++odd_tail] = i;
}
if (filter(i) === (eks & 1)) {
even[++even_tail] = i;
}
}
for (let i = 0; i < 4; i++) {
odd_tail = extend_table_simple(odd, odd_tail, (oks >>>= 1) & 1);
even_tail = extend_table_simple(even, even_tail, (eks >>>= 1) & 1);
}
input = (input >>> 16 & 0xff) | (input << 16) | (input & 0xff00);
recover(odd, odd_head, odd_tail, oks, even, even_head, even_tail, eks, 11, statelist, 0, input << 1);
return statelist;
};
/**
* Recovery possible states from keystreams from one full auth
* @param ks2 Keystream (32 -> 63)
* @param ks3 Keystream (64 -> 95)
* @returns {Crypto1State[]}
*/
export const lfsr_recovery64 = (ks2, ks3) => {
const S1 = [0x62141, 0x310A0, 0x18850, 0x0C428, 0x06214, 0x0310A,
0x85E30, 0xC69AD, 0x634D6, 0xB5CDE, 0xDE8DA, 0x6F46D,
0xB3C83, 0x59E41, 0xA8995, 0xD027F, 0x6813F, 0x3409F, 0x9E6FA];
const S2 = [0x3A557B00, 0x5D2ABD80, 0x2E955EC0, 0x174AAF60, 0x0BA557B0,
0x05D2ABD8, 0x0449DE68, 0x048464B0, 0x42423258, 0x278192A8,
0x156042D0, 0x0AB02168, 0x43F89B30, 0x61FC4D98, 0x765EAD48,
0x7D8FDD20, 0x7EC7EE90, 0x7F63F748, 0x79117020];
const T1 = [0x4F37D, 0x279BE, 0x97A6A, 0x4BD35, 0x25E9A, 0x12F4D, 0x097A6, 0x80D66,
0xC4006, 0x62003, 0xB56B4, 0x5AB5A, 0xA9318, 0xD0F39, 0x6879C, 0xB057B,
0x582BD, 0x2C15E, 0x160AF, 0x8F6E2, 0xC3DC4, 0xE5857, 0x72C2B, 0x39615,
0x98DBF, 0xC806A, 0xE0680, 0x70340, 0x381A0, 0x98665, 0x4C332, 0xA272C];
const T2 = [0x3C88B810, 0x5E445C08, 0x2982A580, 0x14C152C0, 0x4A60A960,
0x253054B0, 0x52982A58, 0x2FEC9EA8, 0x1156C4D0, 0x08AB6268,
0x42F53AB0, 0x217A9D58, 0x161DC528, 0x0DAE6910, 0x46D73488,
0x25CB11C0, 0x52E588E0, 0x6972C470, 0x34B96238, 0x5CFC3A98,
0x28DE96C8, 0x12CFC0E0, 0x4967E070, 0x64B3F038, 0x74F97398,
0x7CDC3248, 0x38CE92A0, 0x1C674950, 0x0E33A4A8, 0x01B959D0,
0x40DCACE8, 0x26CEDDF0];
const C1 = [0x846B5, 0x4235A, 0x211AD];
const C2 = [0x1A822E0, 0x21A822E0, 0x21A822E0];
let oks = Array(32).fill(0);
let eks = Array(32).fill(0);
let hi = Array(32).fill(0);
let win = 0;
let low = 0;
let table = Array(1 << 16).fill(0);
let statelist = [];
for (let i = 30; i >= 0; i -= 2) {
oks[i >> 1] = bebit(ks2, i);
oks[16 + (i >> 1)] = bebit(ks3, i);
}
for (let i = 31; i >= 0; i -= 2) {
eks[i >> 1] = bebit(ks2, i);
eks[16 + (i >> 1)] = bebit(ks3, i);
}
for (let i = 0xfffff; i >= 0; i--) {
if (filter(i) != oks[0])
continue;
let tail = 0;
table[tail] = i;
for (let j = 1; tail >= 0 && j < 29; j++) {
tail = extend_table_simple(table, tail, oks[j]);
}
if (tail < 0)
continue;
for (let j = 0; j < 19; ++j) {
low = low << 1 | evenParity32(i & S1[j]);
}
for (let j = 0; j < 32; ++j) {
hi[j] = evenParity32(i & T1[j]);
}
for (; tail >= 0; --tail) {
let needContinue = false;
for (let j = 0; j < 3; j++) {
table[tail] = table[tail] << 1;
table[tail] |= evenParity32((i & C1[j]) ^ (table[tail] & C2[j]));
if (filter(table[tail]) != oks[29 + j]) {
needContinue = true;
break;
}
}
if (needContinue)
continue;
for (let j = 0; j < 19; j++) {
win = win << 1 | evenParity32(table[tail] & S2[j]);
}
win ^= low;
for (let j = 0; j < 32; ++j) {
win = win << 1 ^ hi[j] ^ evenParity32(table[tail] & T2[j]);
if (filter(win) != eks[j]) {
needContinue = true;
break;
}
}
if (needContinue)
continue;
table[tail] = table[tail] << 1 | evenParity32(LF_POLY_EVEN & table[tail]);
statelist.push(new Crypto1State(table[tail] ^ evenParity32(LF_POLY_ODD & win), win));
}
}
return statelist;
};
/**
* Recovery by two sets of 32 bit keystream authentication
* @param uid UID
* @param chal Tag challenge #1 (aka `nt`)
* @param rchal Reader challenge #1 (aka `{nr_0}`)
* @param rresp Reader response #1 (aka `{ar_0}`)
* @param chal2 Tag challenge #2 (aka `nt1`)
* @param rchal2 Reader challenge #2 (aka `{nr_1}`)
* @param rresp2 Reader response #2 (aka `{ar_1}`)
* @returns {bigint}
*/
export const recovery32 = (uid, chal, rchal, rresp, chal2, rchal2, rresp2) => {
const s = lfsr_recovery32(rresp ^ prng_successor(chal, 64), 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))) {
return key;
}
}
return -1n;
};
/**
* Recovery by one set of full 64 bit keystream authentication
* @param uid UID
* @param chal Tag challenge (aka `nt`)
* @param rchal Reader challenge (aka `{nr}`)
* @param rresp Reader response (aka `{ar}`)
* @param tresp Tag response (aka `{at}`)
* @returns {bigint}
*/
export const recovery64 = (uid, chal, rchal, rresp, tresp) => {
let ks2 = rresp ^ prng_successor(chal, 64);
let ks3 = tresp ^ prng_successor(chal, 96);
let s = lfsr_recovery64(ks2, ks3)[0];
lfsr_rollback_word(s, 0);
lfsr_rollback_word(s, 0);
lfsr_rollback_word(s, rchal, true);
lfsr_rollback_word(s, uid ^ chal);
return s.lfsr;
};
/**
* Recovery by partial nested authentication
*
* @author doegox
* @param uid UID
* @param chal Tag challenge (aka `nt`)
* @param enc_chal Encrypted tag challenge (aka `{nt}`)
* @param rchal Reader challenge (aka `{nr}`)
* @param rresp Reader response (aka `{ar}`)
* @returns {bigint}
*/
export const recoveryNested = (uid, chal, enc_chal, rchal, rresp) => {
let ar = prng_successor(chal, 64);
let ks0 = enc_chal ^ chal;
let ks2 = rresp ^ ar;
let s = lfsr_recovery32(ks0, uid ^ chal);
for (let t = 0; (s[t].odd !== 0) || (s[t].even !== 0); t++) {
crypto1_word(s[t], rchal, true);
if (ks2 == crypto1_word(s[t], 0)) {
lfsr_rollback_word(s[t], 0);
lfsr_rollback_word(s[t], rchal, true);
lfsr_rollback_word(s[t], uid ^ chal);
return s[t].lfsr;
}
}
return -1n;
};