Skip to content

Commit

Permalink
fix base32 and hmac
Browse files Browse the repository at this point in the history
  • Loading branch information
pipobscure committed Jul 15, 2022
1 parent e24507c commit c97f62a
Show file tree
Hide file tree
Showing 11 changed files with 437 additions and 342 deletions.
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
node_modules/
npm-debug.log

**/*.js
**/*.d.ts
*.js
*.d.ts

!otp.d.ts
!rollup.config.js
12 changes: 12 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"printWidth": 120,
"trailingComma": "all",
"useTabs": true,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"bracketSpacing": true,
"arrowParens": "always",
"quoteProps": "consistent",
"endOfLine": "lf"
}
67 changes: 67 additions & 0 deletions lib/base32.t.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { describe, it } from '@xutl/test';
import { strictEqual } from 'node:assert';

import { randomBytes } from 'node:crypto';
import { encode, decode, encodeChunk, decodeChunk } from './base32.js';

describe('base32', () => {
describe('chunk', () => {
const data = Buffer.from('8479efe183', 'hex');
const encd = 'QR467YMD';
it('encode', () => {
const actual = encodeChunk(data);
strictEqual(actual, encd);
});
it('decode', () => {
const actual = Buffer.alloc(5);
decodeChunk(encd.split(''), actual);
strictEqual(actual.toString('hex'), data.toString('hex'));
});
});
describe('whole', () => {
const data = Buffer.from('4373acf0d34e7c15', 'hex');
const encd = 'INZ2Z4GTJZ6BK===';
it('encode', () => {
const actual = encode(data);
strictEqual(actual, encd);
});
it('decode', () => {
const actual = Buffer.from(decode(encd));
strictEqual(actual.toString('hex'), data.toString('hex'));
});
});
describe('64', () => {
const data = Buffer.from(
'0b17fcff5b73f9b5141cbfa33da655d3c243295127d91c92d291ab0c2698bf9067a5840ebc30fb83804f4df0b4f343368dd671d9cb6be1e1950fc6250615e86d',
'hex',
);
const encd =
'BML7Z723OP43KFA4X6RT3JSV2PBEGKKRE7MRZEWSSGVQYJUYX6IGPJMEB26DB64DQBHU34FU6NBTNDOWOHM4W27B4GKQ7RRFAYK6Q3I=';
it('encode', () => {
const actual = encode(data);
strictEqual(actual, encd);
});
it('decode', () => {
const actual = Buffer.from(decode(encd));
strictEqual(actual.toString('hex'), data.toString('hex'));
});
});
it('encodes(64)', () => {
const data = randomBytes(64);
const encoded = encode(data);
const decoded = Buffer.from(decode(encoded));
strictEqual(decoded.toString('hex'), data.toString('hex'));
});
it('encodes(128)', () => {
const data = randomBytes(64);
const encoded = encode(data);
const decoded = Buffer.from(decode(encoded));
strictEqual(decoded.toString('hex'), data.toString('hex'));
});
it('decodes(64)', () => {
const data = '7E3WMHGCDN45PYLUCVAJ7FDST3WGXLF7AB6CRDXBRGQGWS3OR4HA====';
const decoded = decode(data);
const reen = encode(decoded);
strictEqual(reen, data);
});
});
190 changes: 102 additions & 88 deletions lib/base32.ts
Original file line number Diff line number Diff line change
@@ -1,111 +1,125 @@
const VOCAB = "ABCDEFGHIJKLMNOPQRSTUVWXYZ23456789=".split("");
const PAD = ["=", "=", "=", "=", "=", "=", "=", "="];
const VOCAB = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ23456789='.split('');
const PAD = ['=', '=', '=', '=', '=', '=', '=', '='];

function encodeChunk(data: Uint8Array) {
const b1 = data.length > 0 ? data[0] : 0;
const b2 = data.length > 1 ? data[1] : 0;
const b3 = data.length > 2 ? data[2] : 0;
const b4 = data.length > 3 ? data[3] : 0;
const b5 = data.length > 4 ? data[4] : 0;
export function encodeChunk(data: Uint8Array) {
const b1 = data.length > 0 ? data[0] : 0;
const b2 = data.length > 1 ? data[1] : 0;
const b3 = data.length > 2 ? data[2] : 0;
const b4 = data.length > 3 ? data[3] : 0;
const b5 = data.length > 4 ? data[4] : 0;

const chars = [map(mask(b1 >> 3)), map(mask((b1 << 2) | (b2 >> 5))), map(mask(b2 >> 1)), map(mask((b2 << 4) | (b3 >> 4))), map(mask((b3 << 1) | (b4 >> 7))), map(mask(b4 >> 2)), map(mask((b4 << 3) | (b5 >> 5))), map(mask(b5))];
const chars = [
map(mask(b1 >> 3)), // [12345]678 >> 12345
map(mask((b1 << 2) | (b2 >> 6))), // 12345678 << 67800 | 12345678 >> 00012
map(mask(b2 >> 1)), // 12345678 >> 34567
map(mask((b2 << 4) | (b3 >> 4))), // 12345678 << 80000 | 12345678 >> 01234
map(mask((b3 << 1) | (b4 >> 7))), // 12345678 << 56780 | 12345678 >> 00001
map(mask(b4 >> 2)), // 12345678 >> 23456
map(mask((b4 << 3) | (b5 >> 5))), // 12345678 << 78000 | 12345678 >> 00123
map(mask(b5)), // 12345678 >> 45678
];

switch (data.length) {
case 0:
return "";
case 1:
chars.slice(0, 2).concat(PAD.slice(2)).join("");
case 2:
chars.slice(0, 4).concat(PAD.slice(4)).join("");
case 3:
chars.slice(0, 5).concat(PAD.slice(5)).join("");
[chars[0], chars[1], chars[2], chars[3], chars[4], "=", "=", "="].join("");
case 4:
chars.slice(0, 7).concat(PAD.slice(7)).join("");
default:
return chars.join("");
}
switch (data.length) {
case 0:
return '';
case 1:
return chars.slice(0, 2).concat(PAD).slice(0, 8).join('');
case 2:
return chars.slice(0, 4).concat(PAD).slice(0, 8).join('');
case 3:
return chars.slice(0, 5).concat(PAD).slice(0, 8).join('');
case 4:
return chars.slice(0, 7).concat(PAD).slice(0, 8).join('');
case 5:
return chars.slice(0, 8).concat(PAD).slice(0, 8).join('');
default:
return chars.join('');
}
}

function mask(n: number): number {
return n & 0b00011111;
return n & 0b00011111;
}
function map(n: number): string {
return n > -1 && n < VOCAB.length ? VOCAB[n] : "=";
return n > -1 && n < VOCAB.length ? VOCAB[n] : '=';
}

export function encode(data: Uint8Array) {
let offset = 0;
const chunks: string[] = [];
while (offset < data.length) {
const subset = data.subarray(offset, offset + 5);
chunks.push(encodeChunk(subset));
offset += subset.length;
}
return chunks.join("");
let offset = 0;
const chunks: string[] = [];
while (offset < data.length) {
const subset = data.subarray(offset, offset + 5);
chunks.push(encodeChunk(subset));
offset += subset.length;
}
return chunks.join('');
}

function decodeChar(data: string): number {
const index = VOCAB.indexOf(data);
if (index < 0) throw new Error("invalid character: " + data);
if (index === VOCAB.length - 1) return 0;
return Math.max(index, 0);
const index = VOCAB.indexOf(data);
if (index < 0) throw new Error('invalid character: ' + data);
if (index === VOCAB.length - 1) return 0;
return Math.max(index, 0);
}
function decodeChunk(data: string[], dest: Uint8Array) {
const c1 = decodeChar(data[0]);
const c2 = decodeChar(data[1]);
const c3 = decodeChar(data[2]);
const c4 = decodeChar(data[3]);
const c5 = decodeChar(data[4]);
const c6 = decodeChar(data[5]);
const c7 = decodeChar(data[6]);
const c8 = decodeChar(data[7]);
export function decodeChunk(data: string[], dest: Uint8Array) {
const c1 = decodeChar(data[0]);
const c2 = decodeChar(data[1]);
const c3 = data[2] ? decodeChar(data[2]) : undefined;
const c4 = data[3] ? decodeChar(data[3]) : undefined;
const c5 = data[4] ? decodeChar(data[4]) : undefined;
const c6 = data[5] ? decodeChar(data[5]) : undefined;
const c7 = data[6] ? decodeChar(data[6]) : undefined;
const c8 = data[7] ? decodeChar(data[7]) : undefined;

dest[0] = byte((c1 << 3) | (c2 >> 2));
dest[2] = byte((c2 << 5) | (c3 << 1) | (c4 >> 4));
dest[3] = byte((c4 << 4) | (c5 >> 1));
dest[5] = byte((c5 << 7) | (c6 << 2) | (c7 >> 3));
dest[6] = byte((c7 << 5) | c8);
dest[0] = byte((c1 << 3) | (c2 >> 2));
if (defined(c3, c4)) dest[1] = byte((c2 << 6) | (c3 << 1) | (c4 >> 4)); // xxx12 34567 8xxxx
if (defined(c4, c5)) dest[2] = byte((c4 << 4) | (c5 >> 1)); // x1234 1234x
if (defined(c5, c6, c7)) dest[3] = byte((c5 << 7) | (c6 << 2) | (c7 >> 3)); // xxxx1 23456 78xxx
if (defined(c7, c8)) dest[4] = byte((c7 << 5) | c8); // xx123 45678
}

function defined(...args: (number | undefined)[]) {
for (const arg of args) if (arg === undefined) return false;
return true;
}
function byte(n: number) {
return n & 0xff;
return n & 0xff;
}

export function decode(data: string) {
data = data
.split("s+")
.map((s) => s.trim())
.join("");
const dest = new Uint8Array((data.length * 5) / 8);
const chars = data.split("");
let coff = 0;
let boff = 0;
let fin = 5;
while (fin == 5 && coff < chars.length && boff < dest.length) {
const chunk = chars.slice(coff, coff + 8);
if (chunk.indexOf("=") > -1) {
chunk.splice(chunk.indexOf("="), 8);
switch (chunk.length) {
case 2:
fin = 1;
break;
case 4:
fin = 2;
break;
case 5:
fin = 3;
break;
case 7:
fin = 4;
break;
default:
throw new Error("invalid padding");
}
}
decodeChunk(chunk, dest.subarray(boff, boff + 5));
coff += 8;
boff += 5;
}
return dest.subarray(0, dest.length - 5 + fin);
data = data
.split(/\s+/)
.map((s) => s.trim())
.join('');
const dest = new Uint8Array((data.length * 5) / 8);
const chars = data.split('');
let coff = 0;
let boff = 0;
let fin = 5;
while (fin == 5 && coff < chars.length && boff < dest.length) {
const chunk = chars.slice(coff, coff + 8);
if (chunk.indexOf('=') > -1) {
chunk.splice(chunk.indexOf('='), 8);
switch (chunk.length) {
case 2:
fin = 1;
break;
case 4:
fin = 2;
break;
case 5:
fin = 3;
break;
case 7:
fin = 4;
break;
default:
throw new Error('invalid padding');
}
}
decodeChunk(chunk, dest.subarray(boff, boff + 5));
coff += 8;
boff += 5;
}
return dest.subarray(0, dest.length - 5 + fin);
}
16 changes: 0 additions & 16 deletions lib/hash.ts

This file was deleted.

47 changes: 0 additions & 47 deletions lib/hmac.ts

This file was deleted.

Loading

0 comments on commit c97f62a

Please sign in to comment.