14

I need to create a ZIP file in node.js, protected by a password.

I am using "node-zip" module, that unfortunately doesn't support password protection:

var zip = new require('node-zip')();
zip.file('test.file', 'hello there');
var data = zip.generate({base64:false,compression:'DEFLATE'});

Looking other node modules to create ZIP files, I haven't found any that support password protection.

greuze
  • 4,250
  • 5
  • 43
  • 62
  • 1
    Google "create zip node js"? Try http://stackoverflow.com/questions/5754153/zip-archives-in-node-js After reading the first link it seems spawning a command line tool is the way to go. – Mörre Feb 12 '13 at 09:59
  • 1
    I found [a plugin](https://github.com/ksoichiro/node-archiver-zip-encryptable) for node-archiver to create encrypted zip. It works on stream as well. But I didn't test it. – Amit Kumar Gupta Jul 14 '20 at 06:38

5 Answers5

17

If you work on linux then you can do it with the help of zip (command line utility in most linux distributions). Just include the following in you app.

spawn = require('child_process').spawn;
zip = spawn('zip',['-P', 'password' , 'archive.zip', 'complete path to archive file']);
zip .on('exit', function(code) {
...// Do something with zipfile archive.zip
...// which will be in same location as file/folder given
});

If you want to zip a folder just put another argument '-r', before the folder path instead of file path.

Remember this spawns separate thread, from main process, so it is non blocking. For more info on child_process look here http://nodejs.org/api/child_process.html

user568109
  • 47,225
  • 17
  • 99
  • 123
  • Your solutions seems to be ok, but I need to create the file "on the fly", I mean the files don't exist in the file system (they contain sensitive data). As fas as i know, you cannot pass zip command a file structure with content, right? – greuze Feb 12 '13 at 11:42
  • 2
    You mean to compress whatever data you have in variable, not on file. Zip also accepts a single dash ("-") as the name of a file to be compressed, in which case it will read the file from standard input. You can just write the file contents into stdin of spawned child process zip.stdin.write(data); Look for stdin examples on the same page. http://nodejs.org/api/child_process.html#child_process_child_stdin – user568109 Feb 12 '13 at 11:56
  • I need to write two files inside the zip, with concrete names (I tried with "-" as filename, but the name remains "-" inside the zip file). I am afraid I must use a temporary directory to unzip the files I created with node-zip, and then compress again with zip command using password :( – greuze Feb 12 '13 at 12:22
14

For anyone who ends up here like I did, I tried several packages in node but ended up using this one: https://www.npmjs.com/package/minizip-asm.js

It supports passwords (using AES) and is really easy to use. I'm surprised it doesn't have that many downloads given that it's the only one I found supporting passwords.

james
  • 618
  • 7
  • 9
13

I had the same issue and couldn't find the package to do it, so I've written one on my own, as a plugin to archiver package. Pure JS, no external zip software needed.

Here it is - https://www.npmjs.com/package/archiver-zip-encrypted. Supports both legacy Zip 2.0 encryption and AES-256 encryption from WinZip.

yozh
  • 1,213
  • 2
  • 10
  • 18
0

The solution I'm using (I don't know a better way to do it) is:

var contenido1 = 'contenido super secreto';
var contenido2 = 'otro contenido';
var password = 'pass';
var nombreFichero = 'fichero'

var nodezip = new require('node-zip')();
var fs = require("fs");
nodezip.file('test1.txt', contenido1);
nodezip.file('test2.txt', contenido2);
var data = nodezip.generate({base64:false,compression:'DEFLATE'});
fs.writeFile(nombreFichero + '.zip', data, 'binary');

var exec = require('child_process').exec,
    child;

child = exec('unzip ' + nombreFichero + '.zip -d ' + nombreFichero +
             ' && zip -junk-paths --password ' + password + ' ' + nombreFichero + '-p.zip ' + nombreFichero + '/*' +
             ' && rm -rf ' + nombreFichero + ' && rm -f ' + nombreFichero + '.zip',
  function (error, stdout, stderr) {
    console.log('stdout: ' + stdout);
    console.log('stderr: ' + stderr);
    if (error !== null) {
      console.log('exec error: ' + error);
    }
});

It generates a temporary zip file (without password), and then with several commands, upzip, zip with password and remove temporary files.

greuze
  • 4,250
  • 5
  • 43
  • 62
0

For anyone looking out to create password protected zip file in NextJS (compatible with NodeJS as well) server side, please refer this :

You can place this code under api folder in NextJS

import { NextApiRequest, NextApiResponse } from 'next';
import formidable from 'formidable';
import { join } from 'path';
import { spawn } from 'child_process';

export const config = {
  api: {
    bodyParser: false,
  },
};

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  return new Promise<void>((resolve, reject) => {
    try {
      const form = new formidable.IncomingForm();

      form.parse(req, async (err, fields, files) => {
        if (err) {
          console.error('Error parsing form data:', err);
          res.status(500).json({ error: 'Failed to parse form data' });
          reject(err);
          return;
        }

        const password: string = fields.password.toString();
        const myFile = fields.myFile;

        if (!myFile) {
          res.status(400).json({ error: 'File not found in request' });
          return;
        }

        // Decode the Base64 string into a buffer
        const fileBuffer = Buffer.from(Array.isArray(myFile) ? myFile[0] : myFile, 'base64');

        // Save the file buffer to a temporary file
        const tempFilePath = join(process.cwd(), 'zippedFiles', 'temp_file');
        require('fs').writeFileSync(tempFilePath, fileBuffer);

        // Create a password-protected zip using the `zip` command with `-j` flag
        // `-j` flag will store just the file, not the directories
        const zipFilePath = join(process.cwd(), 'zippedFiles', 'protected.zip');
        const zipProcess = spawn('zip', ['-j', '-P', password, zipFilePath, tempFilePath]);

        zipProcess.on('exit', (code) => {
          if (code === 0) {
            // Zip file creation successful
            res.status(200).json({ zipPath: 'protected.zip' });
            resolve();
          } else {
            // Zip file creation failed
            console.error('Error creating password-protected zip file');
            res.status(500).json({ error: 'Failed to create zip file' });
            reject(new Error('Failed to create zip file'));
          }

          // Delete the temporary file
          require('fs').unlinkSync(tempFilePath);
        });
      });
    } catch (error) {
      console.error('Error generating password-protected zip:', error);
      res.status(500).json({ error: 'Failed to generate zip file' });
      reject(error);
    }
  });
}

I am also providing sample client side code for end to end implementation

import axios from 'axios';

const createPasswordProtectedZip = async (selectedFile: File, password: string) => {

  let data = undefined
  const base64Data = await readFileAsBase64(selectedFile);

  const formData = new FormData();
  formData.append('myFile', base64Data);
  formData.append('password', password);


  data = await axios.post('/api/download-zip', formData).then((res: any) => {
    console.log('File response:', res.data);
    return res.data
  }).catch((e: any) => {
    console.log('File error:', e);
    return undefined
  });

  return data

};

const readFileAsBase64 = (file: File) => {
  return new Promise<string>((resolve, reject) => {
    const reader = new FileReader();

    reader.onload = () => {
      const result = reader.result;
      if (typeof result === 'string') {
        const base64Data = result.split(',')[1];
        if (base64Data) {
          resolve(base64Data);
        } else {
          reject(new Error('Failed to read the file.'));
        }
      } else {
        reject(new Error('Failed to read the file.'));
      }
    };

    reader.onerror = (error) => {
      reject(error);
    };

    reader.readAsDataURL(file);
  });
};
export default createPasswordProtectedZip;

Please note that I am using typescript.

Vikas Chowdhury
  • 737
  • 8
  • 15