forked from cthackers/adm-zip
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
207 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters