Skip to content

Commit

Permalink
feat(Blowfish): add CFB and OFB block modes
Browse files Browse the repository at this point in the history
  • Loading branch information
aykxt committed Mar 14, 2021
1 parent bd08fa8 commit 1f61da9
Show file tree
Hide file tree
Showing 13 changed files with 235 additions and 69 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ A collection of useful crypto algorithms written in Typescript.

- AES (Advanced Encryption Standard)
- Blowfish
- ECB and CBC block modes
- ECB, CBC, CFB and OFB block modes

## Examples

Expand Down
4 changes: 2 additions & 2 deletions benchmarks/aes.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { bench, runBenchmarks } from "../dev_deps.ts";
import { AesCbc, AesCfb, AesEcb, AesOfb } from "../src/aes/mod.ts";
import { AES as GodCryptoAES } from "https://deno.land/x/god_crypto@v1.4.9/aes.ts";
import { parseBenchmarkArgs } from "./utils/parseBenchmarkArgs.ts";
import { args } from "./utils/benchmarkArgs.ts";

const { runs: _runs, ...opts } = parseBenchmarkArgs();
const { runs: _runs, ...opts } = args;
const runs = _runs || 25;

// deno-fmt-ignore
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/all.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { runBenchmarks } from "../dev_deps.ts";
import { parseBenchmarkArgs } from "./utils/parseBenchmarkArgs.ts";
import { args } from "./utils/benchmarkArgs.ts";
import "./aes.ts";
import "./blowfish.ts";

const { runs: _, ...opts } = parseBenchmarkArgs();
const { runs: _, ...opts } = args;

runBenchmarks(opts);
47 changes: 44 additions & 3 deletions benchmarks/blowfish.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { bench, runBenchmarks } from "../dev_deps.ts";
import { BlowfishCbc, BlowfishEcb } from "../src/blowfish/mod.ts";
import { parseBenchmarkArgs } from "./utils/parseBenchmarkArgs.ts";
import {
BlowfishCbc,
BlowfishCfb,
BlowfishEcb,
BlowfishOfb,
} from "../src/blowfish/mod.ts";
import { args } from "./utils/benchmarkArgs.ts";

const { runs: _runs, ...opts } = parseBenchmarkArgs();
const { runs: _runs, ...opts } = args;
const runs = _runs || 25;

const key = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
Expand Down Expand Up @@ -54,6 +59,42 @@ bench({
},
});

bench({
name: "Blowfish-CFB 2MiB Encrypt",
runs,
func(b) {
const cipher = new BlowfishCfb(key, iv);

b.start();
cipher.encrypt(data);
b.stop();
},
});

bench({
name: "Blowfish-CFB 2MiB Decrypt",
runs,
func(b) {
const bf = new BlowfishCfb(key, iv);
b.start();
bf.decrypt(data);
b.stop();
},
});

bench({
// Encryption and decryption are the same
name: "Blowfish-OFB 2MiB Encrypt/Decrypt",
runs,
func(b) {
const cipher = new BlowfishOfb(key, iv);

b.start();
cipher.encrypt(data);
b.stop();
},
});

if (import.meta.main) {
runBenchmarks(opts);
}
13 changes: 13 additions & 0 deletions benchmarks/utils/benchmarkArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { parseArgs } from "../../dev_deps.ts";

const flags = parseArgs(Deno.args, {
string: ["only", "skip"],
boolean: "silent",
});

export const args = {
runs: typeof flags.runs === "number" ? flags.runs : undefined,
only: flags.only ? new RegExp(flags.only) : undefined,
skip: flags.skip ? new RegExp(flags.skip) : undefined,
silent: flags.silent as boolean,
};
27 changes: 0 additions & 27 deletions benchmarks/utils/parseBenchmarkArgs.ts

This file was deleted.

7 changes: 6 additions & 1 deletion blowfish.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
export { BlowfishCbc, BlowfishEcb } from "./src/blowfish/mod.ts";
export {
BlowfishCbc,
BlowfishCfb,
BlowfishEcb,
BlowfishOfb,
} from "./src/blowfish/mod.ts";
export { Padding } from "./src/utils/padding.ts";
7 changes: 6 additions & 1 deletion mod.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
export { BlowfishCbc, BlowfishEcb } from "./blowfish.ts";
export {
BlowfishCbc,
BlowfishCfb,
BlowfishEcb,
BlowfishOfb,
} from "./blowfish.ts";
export { AesCbc, AesCfb, AesEcb, AesOfb } from "./aes.ts";
export { Padding } from "./src/utils/padding.ts";
2 changes: 1 addition & 1 deletion src/aes/cipher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AES } from "./aes.ts";
import { pad, Padding, unpad } from "../utils/padding.ts";
import { BlockCipher } from "../common/blockcipher.ts";
import type { BlockCipher } from "../common/blockcipher.ts";

function checkBlockSize(size: number) {
if (size % AES.BLOCK_SIZE !== 0) {
Expand Down
139 changes: 119 additions & 20 deletions src/blowfish/cipher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { Blowfish } from "./blowfish.ts";
import { pad, Padding, unpad } from "../utils/padding.ts";
import { BlockCipher } from "../common/blockcipher.ts";
import type { BlockCipher } from "../common/blockcipher.ts";

function checkBlockSize(size: number) {
if (size % Blowfish.BLOCK_SIZE !== 0) {
throw new Error("Invalid data size (must be multiple of 8 bytes)");
}
}

function checkIvSize(size: number) {
if (size != Blowfish.BLOCK_SIZE) {
throw new Error("Invalid initialization vector size (must be 8 bytes)");
}
}

export class BlowfishEcb implements BlockCipher {
#bf: Blowfish;
Expand Down Expand Up @@ -28,9 +40,7 @@ export class BlowfishEcb implements BlockCipher {
}

decrypt(data: Uint8Array): Uint8Array {
if ((data.length % Blowfish.BLOCK_SIZE) !== 0) {
throw new Error("invalid data size (must be multiple of 8 bytes)");
}
checkBlockSize(data.length);

const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const decrypted = new Uint8Array(data.length);
Expand All @@ -56,9 +66,7 @@ export class BlowfishCbc implements BlockCipher {
iv: Uint8Array,
private padding: Padding = Padding.NONE,
) {
if (iv.length != Blowfish.BLOCK_SIZE) {
throw new Error("Invalid initialization vector size (must be 8 bytes)");
}
checkIvSize(iv.length);

const ivView = new DataView(iv.buffer, iv.byteOffset, iv.byteLength);

Expand All @@ -74,32 +82,27 @@ export class BlowfishCbc implements BlockCipher {
const encrypted = new Uint8Array(data.length);
const encryptedView = new DataView(encrypted.buffer);
for (let i = 0; i < data.length; i += Blowfish.BLOCK_SIZE) {
let l = view.getUint32(i);
let r = view.getUint32(i + 4);
[l, r] = [this.#prevL ^ l, this.#prevR ^ r];
[l, r] = this.#bf.encrypt(l, r);
[this.#prevL, this.#prevR] = [l, r];
encryptedView.setUint32(i, l);
encryptedView.setUint32(i + 4, r);
[this.#prevL, this.#prevR] = this.#bf.encrypt(
this.#prevL ^ view.getUint32(i),
this.#prevR ^ view.getUint32(i + 4),
);
encryptedView.setUint32(i, this.#prevL);
encryptedView.setUint32(i + 4, this.#prevR);
}
return encrypted;
}

decrypt(data: Uint8Array): Uint8Array {
if ((data.length % Blowfish.BLOCK_SIZE) !== 0) {
throw new Error("Invalid data size (must be multiple of 8 data)");
}
checkBlockSize(data.length);

const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const decrypted = new Uint8Array(data.length);
const decryptedView = new DataView(decrypted.buffer);

let prevLTmp;
let prevRTmp;
for (let i = 0; i < data.length; i += Blowfish.BLOCK_SIZE) {
let l = view.getUint32(i);
let r = view.getUint32(i + 4);
[prevLTmp, prevRTmp] = [l, r];
const [prevLTmp, prevRTmp] = [l, r];
[l, r] = this.#bf.decrypt(l, r);
[l, r] = [this.#prevL ^ l, this.#prevR ^ r];
[this.#prevL, this.#prevR] = [prevLTmp, prevRTmp];
Expand All @@ -110,3 +113,99 @@ export class BlowfishCbc implements BlockCipher {
return unpad(decrypted, this.padding, Blowfish.BLOCK_SIZE);
}
}

export class BlowfishCfb implements BlockCipher {
#bf: Blowfish;
#prevL: number;
#prevR: number;

constructor(
key: Uint8Array,
iv: Uint8Array,
private padding: Padding = Padding.NONE,
) {
checkIvSize(iv.length);

const ivView = new DataView(iv.buffer, iv.byteOffset, iv.byteLength);

this.#prevL = ivView.getUint32(0);
this.#prevR = ivView.getUint32(4);
this.#bf = new Blowfish(key);
}

encrypt(data: Uint8Array): Uint8Array {
data = pad(data, this.padding, Blowfish.BLOCK_SIZE);

const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const encrypted = new Uint8Array(data.length);
const encryptedView = new DataView(encrypted.buffer);

for (let i = 0; i < data.length; i += Blowfish.BLOCK_SIZE) {
[this.#prevL, this.#prevR] = this.#bf.encrypt(this.#prevL, this.#prevR);
this.#prevL ^= view.getUint32(i);
this.#prevR ^= view.getUint32(i + 4);
encryptedView.setUint32(i, this.#prevL);
encryptedView.setUint32(i + 4, this.#prevR);
}

return encrypted;
}

decrypt(data: Uint8Array): Uint8Array {
checkBlockSize(data.length);

const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const decrypted = new Uint8Array(data.length);
const decryptedView = new DataView(decrypted.buffer);

for (let i = 0; i < data.length; i += Blowfish.BLOCK_SIZE) {
[this.#prevL, this.#prevR] = this.#bf.encrypt(this.#prevL, this.#prevR);
const segL = view.getUint32(i);
const segR = view.getUint32(i + 4);
decryptedView.setUint32(i, this.#prevL ^ segL);
decryptedView.setUint32(i + 4, this.#prevR ^ segR);
this.#prevL = segL;
this.#prevR = segR;
}

return unpad(decrypted, this.padding, Blowfish.BLOCK_SIZE);
}
}

export class BlowfishOfb implements BlockCipher {
#bf: Blowfish;
#prevL: number;
#prevR: number;

constructor(
key: Uint8Array,
iv: Uint8Array,
private padding: Padding = Padding.NONE,
) {
checkIvSize(iv.length);

const ivView = new DataView(iv.buffer, iv.byteOffset, iv.byteLength);

this.#prevL = ivView.getUint32(0);
this.#prevR = ivView.getUint32(4);
this.#bf = new Blowfish(key);
}

encrypt(data: Uint8Array): Uint8Array {
data = pad(data, this.padding, Blowfish.BLOCK_SIZE);

const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const encrypted = new Uint8Array(data.length);
const encryptedView = new DataView(encrypted.buffer);

for (let i = 0; i < data.length; i += Blowfish.BLOCK_SIZE) {
[this.#prevL, this.#prevR] = this.#bf.encrypt(this.#prevL, this.#prevR);
encryptedView.setUint32(i, view.getUint32(i) ^ this.#prevL);
encryptedView.setUint32(i + 4, view.getUint32(i + 4) ^ this.#prevR);
}

return encrypted;
}

decrypt = this.encrypt;
}
7 changes: 6 additions & 1 deletion src/blowfish/mod.ts
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
export { BlowfishCbc, BlowfishEcb } from "./cipher.ts";
export {
BlowfishCbc,
BlowfishCfb,
BlowfishEcb,
BlowfishOfb,
} from "./cipher.ts";
2 changes: 1 addition & 1 deletion src/hmac/mod.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createHash, SupportedAlgorithm } from "../../deps.ts";

export type { SupportedAlgorithm };
export type { SupportedAlgorithm } from "../../deps.ts";

const blockSizes: Record<SupportedAlgorithm, number> = {
"sha3-512": 72,
Expand Down
Loading

0 comments on commit 1f61da9

Please sign in to comment.