diff --git a/adm-zip.js b/adm-zip.js index 38c104c..a34ef60 100644 --- a/adm-zip.js +++ b/adm-zip.js @@ -63,9 +63,9 @@ module.exports = function (/**String*/input) { * * @return Buffer or Null in case of error */ - readFile: function (/**Object*/entry) { + readFile: function (/**Object*/entry, /*String, Buffer*/pass) { var item = getEntry(entry); - return item && item.getData() || null; + return item && item.getData(pass) || null; }, /** @@ -482,7 +482,7 @@ module.exports = function (/**String*/input) { * Test the archive * */ - test: function () { + test: function (pass) { if (!_zip) { return false; } @@ -492,7 +492,7 @@ module.exports = function (/**String*/input) { if (entry.isDirectory) { continue; } - var content = _zip.entries[entry].getData(); + var content = _zip.entries[entry].getData(pass); if (!content) { return false; } @@ -510,7 +510,7 @@ module.exports = function (/**String*/input) { * @param overwrite If the file already exists at the target path, the file will be overwriten if this is true. * Default is FALSE */ - extractAllTo: function (/**String*/targetPath, /**Boolean*/overwrite) { + extractAllTo: function (/**String*/targetPath, /**Boolean*/overwrite, /*String, Buffer*/pass) { overwrite = overwrite || false; if (!_zip) { throw new Error(Utils.Errors.NO_ZIP); @@ -521,7 +521,7 @@ module.exports = function (/**String*/input) { Utils.makeDir(entryName); return; } - var content = entry.getData(); + var content = entry.getData(pass); if (!content) { throw new Error(Utils.Errors.CANT_EXTRACT_FILE); } diff --git a/methods/index.js b/methods/index.js index ddcbba6..0994aac 100644 --- a/methods/index.js +++ b/methods/index.js @@ -1,2 +1,3 @@ exports.Deflater = require("./deflater"); -exports.Inflater = require("./inflater"); \ No newline at end of file +exports.Inflater = require("./inflater"); +exports.ZipCrypto = require("./zipcrypto"); \ No newline at end of file diff --git a/methods/zipcrypto.js b/methods/zipcrypto.js new file mode 100644 index 0000000..0cf8d32 --- /dev/null +++ b/methods/zipcrypto.js @@ -0,0 +1,77 @@ +// 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 + } + } + 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); + } + // 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); + } + + // 1. Stage initialize key + const pass = (Buffer.isBuffer(pwd)) ? pwd : Buffer.from(pwd); + for(let i=0; i< pass.length; i++){ + updateKeys(pass[i]); + } + + // return decrypter function + return function (/*Buffer*/data){ + if (!Buffer.isBuffer(data)){ + throw 'decrypter needs Buffer' + } + // 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 + } + return result; + } +} + +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 + 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]; + + // does password meet expectations + if (check !== testbyte){ + throw 'ADM-ZIP: Wrong Password'; + } + + // decode content + return decrypter(data.slice(12)); +} + +module.exports = {decrypt}; diff --git a/zipEntry.js b/zipEntry.js index 01f702e..d5dddd0 100644 --- a/zipEntry.js +++ b/zipEntry.js @@ -34,7 +34,7 @@ module.exports = function (/*Buffer*/input) { return true; } - function decompress(/*Boolean*/async, /*Function*/callback, /*String*/pass) { + function decompress(/*Boolean*/async, /*Function*/callback, /*String, Buffer*/pass) { if(typeof callback === 'undefined' && typeof async === 'string') { pass=async; async=void 0; @@ -55,8 +55,10 @@ module.exports = function (/*Buffer*/input) { } if (_entryHeader.encripted){ - if (async && callback) callback(Buffer.alloc(0), Utils.Errors.UNKNOWN_METHOD); - throw new Error(Utils.Errors.UNKNOWN_METHOD); + if ('string' !== typeof pass && !Buffer.isBuffer(pass)){ + throw new Error('ADM-ZIP: Incompatible password parameter'); + } + compressedData = Methods.ZipCrypto.decrypt(compressedData, _entryHeader, pass); } var data = Buffer.alloc(_entryHeader.size);