diff --git a/README.md b/README.md index e45a630..c98887f 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ assert(plaintext.equals(enigma.decrypt(ciphertext)), 'decrypted ciphertext shoul All the following block cipher mode algorithms ported from libmcrypt * CBC +* PCBC * CFB (CFB8) * CTR * ECB diff --git a/addon/lib/mode-suite.h b/addon/lib/mode-suite.h index 9a3749a..4a0d38e 100644 --- a/addon/lib/mode-suite.h +++ b/addon/lib/mode-suite.h @@ -1,5 +1,6 @@ #include "mode/cbc.h" +#include "mode/pcbc.h" #include "mode/cfb.h" #include "mode/ctr.h" #include "mode/ecb.h" diff --git a/addon/lib/mode/pcbc.cc b/addon/lib/mode/pcbc.cc new file mode 100644 index 0000000..e36e739 --- /dev/null +++ b/addon/lib/mode/pcbc.cc @@ -0,0 +1,99 @@ + +#include "pcbc.h" +#include + +namespace cryptian { + +namespace mode { + +namespace pcbc { + +bool Pcbc::isPaddingRequired() { + return true; +} + +std::vector Cipher::transform(const std::vector chunk) { + + const std::size_t blockSize = _algorithm->getBlockSize(); + const std::size_t blocksCount = std::floor(chunk.size() / blockSize); + + std::vector blocks(blockSize * blocksCount); + + char* block = new char[blockSize](); + char* propagation = new char[blockSize](); + + for (size_t i = 0 ; i < blocksCount; i++) { + + size_t offset = i * blockSize; + + std::copy_n(chunk.begin() + offset, blockSize, block); + std::copy_n(chunk.begin() + offset, blockSize, propagation); + + for (size_t j = 0; j < blockSize; j++) { + block[j] ^= _register[j]; + } + + std::vector cipher = _algorithm->encrypt(std::vector(block, block + blockSize)); + + std::copy(cipher.begin(), cipher.end(), blocks.begin() + offset); + + std::copy(cipher.begin(), cipher.end(), _register.begin()); + + for (size_t j = 0; j < blockSize; j++) { + _register[j] ^= propagation[j]; + } + + } + + delete[] block; + delete[] propagation; + + return blocks; +} + + +std::vector Decipher::transform(const std::vector chunk) { + + const std::size_t blockSize = _algorithm->getBlockSize(); + const std::size_t blocksCount = std::floor(chunk.size() / blockSize); + + std::vector blocks(blockSize * blocksCount); + + char* block = new char[blockSize](); + char* propagation = new char[blockSize](); + + for (size_t i = 0 ; i < blocksCount; i++) { + + size_t offset = i * blockSize; + + std::copy_n(chunk.begin() + offset, blockSize, block); + std::copy_n(chunk.begin() + offset, blockSize, propagation); + + std::vector cipher = _algorithm->decrypt(std::vector(block, block + blockSize)); + + for (size_t j = 0; j < blockSize; j++) { + cipher[j] ^= _register[j]; + } + + std::copy(cipher.begin(), cipher.end(), blocks.begin() + offset); + + std::copy(cipher.begin(), cipher.end(), _register.begin()); + + for (size_t j = 0; j < blockSize; j++) { + _register[j] ^= propagation[j]; + } + + } + + delete[] block; + delete[] propagation; + + return blocks; + +} + +}; + +}; + +}; \ No newline at end of file diff --git a/addon/lib/mode/pcbc.h b/addon/lib/mode/pcbc.h new file mode 100644 index 0000000..e748880 --- /dev/null +++ b/addon/lib/mode/pcbc.h @@ -0,0 +1,31 @@ + +#include + +namespace cryptian { + +namespace mode { + +namespace pcbc { + +class Pcbc : public cryptian::mode::ModeBase { +public: + bool isPaddingRequired(); +}; + + +class Cipher : public Pcbc { +public: + std::vector transform(const std::vector); + +}; + +class Decipher: public Pcbc { +public: + std::vector transform(const std::vector); +}; + +}; + +}; + +}; diff --git a/addon/src/node/cryptian.cc b/addon/src/node/cryptian.cc index 9d19f2c..d244bcb 100644 --- a/addon/src/node/cryptian.cc +++ b/addon/src/node/cryptian.cc @@ -66,6 +66,7 @@ NAN_MODULE_INIT(Init) { EXPORT_ALGORITHM_STREAM(Wake) EXPORT_MODE(cbc) + EXPORT_MODE(pcbc) EXPORT_MODE(cfb) EXPORT_MODE(ctr) EXPORT_MODE(ecb) diff --git a/binding.gyp b/binding.gyp index 04890eb..5f9ef64 100644 --- a/binding.gyp +++ b/binding.gyp @@ -21,6 +21,7 @@ "addon/lib/algorithm/xtea.cc", "addon/lib/algorithm/dummy.cc", "addon/lib/mode/cbc.cc", + "addon/lib/mode/pcbc.cc", "addon/lib/mode/cfb.cc", "addon/lib/mode/ctr.cc", "addon/lib/mode/ecb.cc", diff --git a/package-lock.json b/package-lock.json index df81f43..73d5a0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cryptian", - "version": "0.0.6", + "version": "0.0.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cryptian", - "version": "0.0.6", + "version": "0.0.7", "license": "MIT", "dependencies": { "bindings": "^1.5.0", diff --git a/src/index.ts b/src/index.ts index 88c35a5..487842a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ export type ModeCipherDecipher = { export enum ModeList { Cbc = 'cbc', + Pcbc = 'pcbc', Cfb = 'cfb', Ctr = 'ctr', Ecb = 'ecb', diff --git a/test/mode/pcbc.ts b/test/mode/pcbc.ts new file mode 100644 index 0000000..983e2c7 --- /dev/null +++ b/test/mode/pcbc.ts @@ -0,0 +1,115 @@ + + +import {expect} from '@jest/globals'; + +import { randomBytes } from 'crypto'; +import assert from 'assert'; +import cryptian from '../..'; + +const {algorithm: {Dummy}, mode: {pcbc}} = cryptian; + + +(typeof pcbc === 'object' ? describe : describe.skip) ('pcbc', () => { + + const plaintext = Buffer.from('0a4e0adf528e898594699188930c6247493f3c17fce03723e123517d62533c4a', 'hex'); + const ciphertext = Buffer.from('09c31ca738fad69e97e487f0f9783d5c4ab22a6f96946838e2ae470508276351', 'hex'); + + const iv = Buffer.from('038d16786a745f1b', 'hex'); + const dummy = new Dummy(); + + + describe('cipher', () => { + + it('undivided', () => { + const cipher = new pcbc.Cipher(dummy, iv); + + assert(ciphertext.equals(cipher.transform(plaintext)), 'transformed plaintext should be equal to ciphertext'); + }); + + it('divided', () => { + const cipher = new pcbc.Cipher(dummy, iv); + const part = Buffer.concat([ + cipher.transform(plaintext.slice(0, 8)), + cipher.transform(plaintext.slice(8)) + ]); + + assert(ciphertext.equals(part), 'transformed plaintext should be equal to ciphertext'); + }); + + describe('throw padding exception', () => { + + it('getBlockSize', () => { + const cipher = new pcbc.Cipher(dummy, iv); + assert.equal(cipher.getBlockSize(), dummy.getBlockSize(), 'cipher.getBlockSize() should equal algorithm.getBlockSize()'); + }); + + it('transform using 5 bytes', () => { + const cipher = new pcbc.Cipher(dummy, iv); + + expect(() => cipher.transform(randomBytes(5))) + .toThrowError('Data size should be aligned to algorithm block size.'); + + }); + + it('transform using 12 bytes', () => { + + const cipher = new pcbc.Cipher(dummy, iv); + + expect(() => cipher.transform(randomBytes(12))) + .toThrowError('Data size should be aligned to algorithm block size.'); + + }); + + }); + + }); + + + describe('decipher', () => { + + + it('undivided', () => { + const decipher = new pcbc.Decipher(dummy, iv); + + assert(plaintext.equals(decipher.transform(ciphertext)), 'transformed ciphertext should be equal to plaintext'); + }); + + + it('divided', () => { + const decipher = new pcbc.Decipher(dummy, iv); + const part = Buffer.concat([ + decipher.transform(ciphertext.slice(0, 8)), + decipher.transform(ciphertext.slice(8)) + ]); + + assert(plaintext.equals(part), 'transformed ciphertext should be equal to plaintext'); + }); + + + describe('throw padding exception', () => { + + it('getBlockSize', () => { + const decipher = new pcbc.Decipher(dummy, iv); + assert.equal(decipher.getBlockSize(), dummy.getBlockSize(), 'decipher.getBlockSize() should equal algorithm.getBlockSize()'); + }); + + + it('transform using 5 bytes', () => { + const decipher = new pcbc.Decipher(dummy, iv); + + expect(() => decipher.transform(randomBytes(5))).toThrowError('Data size should be aligned to algorithm block size.'); + + }); + + + it('transform using 12 bytes', () => { + const decipher = new pcbc.Decipher(dummy, iv); + + expect(() => decipher.transform(randomBytes(12))).toThrowError('Data size should be aligned to algorithm block size.'); + }); + + }); + + }); + +}); \ No newline at end of file