Skip to content

Commit

Permalink
feat(AES): add CFB block mode
Browse files Browse the repository at this point in the history
  • Loading branch information
aykxt committed Mar 13, 2021
1 parent 5db6174 commit 39bb513
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 16 deletions.
2 changes: 1 addition & 1 deletion aes.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { AesCbc, AesEcb } from "./src/aes/mod.ts";
export { AesCbc, AesCfb, AesEcb } from "./src/aes/mod.ts";
export { Padding } from "./src/utils/padding.ts";
2 changes: 1 addition & 1 deletion mod.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { BlowfishCbc, BlowfishEcb } from "./blowfish.ts";
export { AesCbc, AesEcb } from "./aes.ts";
export { AesCbc, AesCfb, AesEcb } from "./aes.ts";
export { Padding } from "./src/utils/padding.ts";
2 changes: 1 addition & 1 deletion src/aes/aes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class AES {

constructor(key: Uint8Array) {
if (![16, 24, 32].includes(key.length)) {
throw new Error("Key must be 16, 24 or 32 bytes long");
throw new Error("Invalid key size (must be either 16, 24 or 32 bytes)");
}

const nk = key.length / 4;
Expand Down
81 changes: 71 additions & 10 deletions src/aes/cipher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ import { AES } from "./aes.ts";
import { pad, Padding, unpad } from "../utils/padding.ts";
import { BlockCipher } from "../common/blockcipher.ts";

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

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

export class AesEcb implements BlockCipher {
#aes: AES;

Expand All @@ -25,9 +37,7 @@ export class AesEcb implements BlockCipher {
}

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

const decrypted = data.slice();

Expand All @@ -48,11 +58,9 @@ export class AesCbc implements BlockCipher {
iv: Uint8Array,
private padding: Padding = Padding.NONE,
) {
if (iv.length != AES.BLOCK_SIZE) {
throw new Error("invalid initialation vector size (must be 16 bytes)");
}
checkIvSize(iv.length);

this.#prev = iv;
this.#prev = iv.slice();
this.#aes = new AES(key);
}

Expand All @@ -72,13 +80,12 @@ export class AesCbc implements BlockCipher {
this.#prev = block;
}

this.#prev = this.#prev.slice();
return encrypted;
}

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

const decrypted = data.slice();

Expand All @@ -93,6 +100,60 @@ export class AesCbc implements BlockCipher {
this.#prev = data.subarray(i, i + AES.BLOCK_SIZE);
}

this.#prev = this.#prev.slice();
return unpad(decrypted, this.padding, AES.BLOCK_SIZE);
}
}

export class AesCfb implements BlockCipher {
#aes: AES;
#prev: Uint8Array;

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

this.#prev = iv.slice();
this.#aes = new AES(key);
}

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

const encrypted = new Uint8Array(data.length);

for (let i = 0; i < data.length; i += AES.BLOCK_SIZE) {
this.#aes.encrypt(this.#prev);

for (let j = 0; j < AES.BLOCK_SIZE; j++) {
encrypted[i + j] = this.#prev[j] ^ data[i + j];
}

this.#prev = encrypted.slice(i, i + AES.BLOCK_SIZE);
}

return encrypted;
}

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

const decrypted = new Uint8Array(data.length);

for (let i = 0; i < data.length; i += 16) {
this.#aes.encrypt(this.#prev);

for (let j = 0; j < 16; j++) {
decrypted[i + j] = this.#prev[j] ^ data[i + j];
}

this.#prev = data.subarray(i, i + 16);
}

this.#prev = this.#prev.slice();
return unpad(decrypted, this.padding, AES.BLOCK_SIZE);
}
}
2 changes: 1 addition & 1 deletion src/aes/mod.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { AesCbc, AesEcb } from "./cipher.ts";
export { AesCbc, AesCfb, AesEcb } from "./cipher.ts";
28 changes: 26 additions & 2 deletions tests/aes.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assertEquals } from "../dev_deps.ts";
import { AesCbc, AesEcb } from "../aes.ts";
import { assertEquals, assertThrows } from "../dev_deps.ts";
import { AesCbc, AesCfb, AesEcb } from "../aes.ts";

// deno-fmt-ignore
const iv = new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);
Expand All @@ -19,6 +19,14 @@ Deno.test("AES-128-ECB ", () => {

const dec = cipher.decrypt(enc);
assertEquals(dec, original);

assertThrows(
() => {
new AesEcb(new Uint8Array(17));
},
Error,
"Invalid key size (must be either 16, 24 or 32 bytes)",
);
});

Deno.test("AES-192-ECB", () => {
Expand Down Expand Up @@ -54,3 +62,19 @@ Deno.test("AES-128-CBC", () => {

assertEquals(dec, original);
});

Deno.test("AES-128-CFB ", () => {
// deno-fmt-ignore
const key = new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);
const iv = new Uint8Array(16);

const original = new Uint8Array(32);

const cipher = new AesCfb(key, iv);
const decipher = new AesCfb(key, iv);

const enc = cipher.encrypt(original);
const dec = decipher.decrypt(enc);

assertEquals(dec, original);
});

0 comments on commit 39bb513

Please sign in to comment.