Skip to content

Commit

Permalink
added encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
5saviahv committed Feb 25, 2021
1 parent f8b6a33 commit f8e9c3a
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 51 deletions.
186 changes: 139 additions & 47 deletions methods/zipcrypto.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,169 @@
// node crypt, we use it for generate salt
const { randomFillSync } = require("crypto");

"use strict";

// generate CRC32 lookup table
const crctable = (new Uint32Array(256)).map((t,crc)=>{
for(let j=0;j<8;j++){
if (0 !== (crc & 1)){
crc = (crc >>> 1) ^ 0xEDB88320
}else{
crc >>>= 1
const crctable = new Uint32Array(256).map((t, crc) => {
for (let j = 0; j < 8; j++) {
if (0 !== (crc & 1)) {
crc = (crc >>> 1) ^ 0xedb88320;
} else {
crc >>>= 1;
}
}
return crc>>>0;
return crc >>> 0;
});

function make_decrypter(/*Buffer*/pwd){
// C-style uInt32 Multiply
const uMul = (a,b) => Math.imul(a, b) >>> 0;
// Initialize keys with default values
const keys = new Uint32Array([0x12345678, 0x23456789, 0x34567890]);
// crc32 byte update
const crc32update = (pCrc32, bval) => {
return crctable[(pCrc32 ^ bval) & 0xff] ^ (pCrc32 >>> 8);
// C-style uInt32 Multiply (discards higher bits, when JS multiply discards lower bits)
const uMul = (a, b) => Math.imul(a, b) >>> 0;

// crc32 byte single update (actually same function is part of utils.crc32 function :) )
const crc32update = (pCrc32, bval) => {
return crctable[(pCrc32 ^ bval) & 0xff] ^ (pCrc32 >>> 8);
};

// function for generating salt for encrytion header
const genSalt = () => {
if ("function" === typeof randomFillSync) {
return randomFillSync(Buffer.alloc(12));
} else {
// fallback if function is not defined
return genSalt.node();
}
// update keys with byteValues
const updateKeys = (byteValue) => {
keys[0] = crc32update(keys[0], byteValue);
keys[1] += keys[0] & 0xff;
keys[1] = uMul(keys[1], 134775813) + 1;
keys[2] = crc32update(keys[2], keys[1] >>> 24);
};

// salt generation with node random function (mainly as fallback)
genSalt.node = () => {
const salt = Buffer.alloc(12);
const len = salt.length;
for (let i = 0; i < len; i++) salt[i] = (Math.random() * 256) & 0xff;
return salt;
};

// general config
const config = {
genSalt
};

// Class Initkeys handles same basic ops with keys
function Initkeys(pw) {
const pass = Buffer.isBuffer(pw) ? pw : Buffer.from(pw);
this.keys = new Uint32Array([0x12345678, 0x23456789, 0x34567890]);
for (let i = 0; i < pass.length; i++) {
this.updateKeys(pass[i]);
}
}

Initkeys.prototype.updateKeys = function (byteValue) {
const keys = this.keys;
keys[0] = crc32update(keys[0], byteValue);
keys[1] += keys[0] & 0xff;
keys[1] = uMul(keys[1], 134775813) + 1;
keys[2] = crc32update(keys[2], keys[1] >>> 24);
return byteValue;
};

Initkeys.prototype.next = function () {
const k = (this.keys[2] | 2) >>> 0; // key
return (uMul(k, k ^ 1) >> 8) & 0xff; // decode
};

function make_decrypter(/*Buffer*/ pwd) {
// 1. Stage initialize key
const pass = (Buffer.isBuffer(pwd)) ? pwd : Buffer.from(pwd);
for(let i=0; i< pass.length; i++){
updateKeys(pass[i]);
}
const keys = new Initkeys(pwd);

// return decrypter function
return function (/*Buffer*/data){
if (!Buffer.isBuffer(data)){
throw 'decrypter needs Buffer'
}
return function (/*Buffer*/ data) {
// result - we create new Buffer for results
const result = Buffer.alloc(data.length);
let pos = 0;
// process input data
for(let c of data){
const k = (keys[2] | 2) >>> 0; // key
c ^= (uMul(k, k^1) >> 8) & 0xff; // decode
result[pos++] = c; // Save Value
updateKeys(c); // update keys with decoded byte
for (let c of data) {
//c ^= keys.next();
//result[pos++] = c; // decode & Save Value
result[pos++] = keys.updateKeys(c ^ keys.next()); // update keys with decoded byte
}
return result;
}
};
}

function make_encrypter(/*Buffer*/ pwd) {
// 1. Stage initialize key
const keys = new Initkeys(pwd);

// return encrypting function, result and pos is here so we dont have to merge buffers later
return function (/*Buffer*/ data, /*Buffer*/ result, /* Number */ pos = 0) {
// result - we create new Buffer for results
if (!result) result = Buffer.alloc(data.length);
// process input data
for (let c of data) {
const k = keys.next(); // save key byte
result[pos++] = c ^ k; // save val
keys.updateKeys(c); // update keys with decoded byte
}
return result;
};
}

function decrypt(/*Buffer*/ data, /*Object*/header, /*String, Buffer*/ pwd){
function decrypt(/*Buffer*/ data, /*Object*/ header, /*String, Buffer*/ pwd) {
if (!data || !Buffer.isBuffer(data) || data.length < 12) {
return Buffer.alloc(0);
}
// We Initialize and generate decrypting function

// 1. We Initialize and generate decrypting function
const decrypter = make_decrypter(pwd);

// check - for testing password
const check = header.crc >>> 24;
// decrypt salt what is always 12 bytes and is a part of file content
const testbyte = decrypter(data.slice(0, 12))[11];
// 2. decrypt salt what is always 12 bytes and is a part of file content
const salt = decrypter(data.slice(0, 12));

// does password meet expectations
if (check !== testbyte){
throw 'ADM-ZIP: Wrong Password';
// 3. does password meet expectations
if (salt[11] !== header.crc >>> 24) {
throw "ADM-ZIP: Wrong Password";
}

// decode content
// 4. decode content
return decrypter(data.slice(12));
}

module.exports = {decrypt};
// lets add way to populate salt, NOT RECOMMENDED for production but maybe useful for testing general functionality
function _salter(data) {
if (Buffer.isBuffer(data) && data.length >= 12) {
// be aware - currently salting buffer data is modified
config.genSalt = function () {
return data.slice(0, 12);
};
} else if (data === "node") {
// test salt generation with node random function
config.genSalt = genSalt.node;
} else {
// if value is not acceptable config gets reset.
config.genSalt = genSalt;
}
}

function encrypt(/*Buffer*/ data, /*Object*/ header, /*String, Buffer*/ pwd, /*Boolean*/ oldlike = false) {
// 1. test data if data is not Buffer we make buffer from it
if (data == null) data = Buffer.alloc(0);
// if data is not buffer be make buffer from it
if (!Buffer.isBuffer(data)) data = Buffer.from(data.toString());

// 2. We Initialize and generate encrypting function
const encrypter = make_encrypter(pwd);

// 3. generate salt (12-bytes of random data)
const salt = config.genSalt();
salt[11] = (header.crc >>> 24) & 0xff;

// old implementations (before PKZip 2.04g) used two byte check
if (oldlike) salt[10] = (header.crc >>> 16) & 0xff;

// 4. create output
const result = Buffer.alloc(data.length + 12);
encrypter(salt, result);

// finally encode content
return encrypter(data, result, 12);
}

module.exports = { decrypt, encrypt, _salter };
72 changes: 68 additions & 4 deletions test/methods/zipcrypto.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";
const { expect } = require("chai");
const { decrypt } = require("../../methods/zipcrypto");
const { decrypt, encrypt, _salter } = require("../../methods/zipcrypto");
const { crc32 } = require("../../util/utils");

// node crypto
Expand All @@ -16,12 +16,12 @@ describe("method - zipcrypto decrypt", () => {
pwdok: "secret",
pwdbad: "Secret",
// result
result: Buffer.from("test", "ascii"),
result: Buffer.from("test", "ascii")
};

// test invalid input data
it("handles invalid data field values / types", () => {
for (const data of [ undefined, null, "str", true, false, 6, Buffer.alloc(4) ]) {
for (const data of [undefined, null, "str", true, false, 6, Buffer.alloc(4)]) {
const result = decrypt(data, { crc: source.crc }, source.pwdok);
expect(result).to.have.lengthOf(0);
}
Expand Down Expand Up @@ -55,7 +55,71 @@ describe("method - zipcrypto decrypt", () => {
expect(result1.compare(source.result)).to.equal(0);

// test password, buffer
const result2 = decrypt( source.data, { crc: source.crc }, Buffer.from(source.pwdok, "ascii"));
const result2 = decrypt(source.data, { crc: source.crc }, Buffer.from(source.pwdok, "ascii"));
expect(result2.compare(source.result)).to.equal(0);
});
});

describe("method - zipcrypto encrypt", () => {
const source = {
crc: 0xd87f7e0c,
// data
data_str: "test",
data_buffer: Buffer.from("test", "ascii"),
salt: Buffer.from("xx+OYQ1Pkvo0ztPY", "base64"),
// 16 byte buffer as test source
data: Buffer.from("D1Q5///EbpBY6rHIZXvd3A==", "base64"),
// just data integrity check
pwdok: "secret",
// result
result: Buffer.from("D1Q5///EbpBY6rHIZXvd3A==", "base64")
};

// test binary results with known salt
it("test binary results with known salt", () => {
const head = { crc: source.crc };
// inject known salt
_salter(source.salt);
const result = encrypt(source.data_str, head, source.pwdok, false);
expect(result.compare(source.result)).to.equal(0);
// restore salting
_salter();
});

// test decryption with both password types
it("test encryption and decrytion with node random salt", () => {
const head = { crc: source.crc };
_salter("node");
// test password, string
const data_buf = Buffer.from(source.data_str);
const result1 = encrypt(source.data_str, head, source.pwdok, false);
const result2 = decrypt(result1, head, source.pwdok);
expect(result2.compare(data_buf)).to.equal(0);
_salter();
});

// test decryption with both password types
it("test encryption and decrytion with known source data", () => {
const head = { crc: source.crc };
// test password, string
const data_buf = Buffer.from(source.data_str);
const result1 = encrypt(source.data_str, head, source.pwdok, false);
const result2 = decrypt(result1, head, source.pwdok);
expect(result2.compare(data_buf)).to.equal(0);
});

// test how encrytion will handle some random data
it("test encrypting and decryting with some javascript objects", () => {
const tests = [true, null, false, undefined, {}, [], 747, new Date(), [{}]];
const head = {};

for (const test of tests) {
const data_buf = test == null ? Buffer.alloc(0) : Buffer.from(test.toString());
head.crc = crc32(data_buf);

const result1 = encrypt(test, head, source.pwdok, false);
const result2 = decrypt(result1, head, source.pwdok);
expect(result2.compare(data_buf)).to.equal(0);
}
});
});

0 comments on commit f8e9c3a

Please sign in to comment.