Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for wzAES zip. #675

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .jshintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
lib/ctrGladman.js
94 changes: 94 additions & 0 deletions lib/aesWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use strict';
var sjcl = require('sjcl');

var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
var global = getGlobal();
global.sjcl = sjcl;

require('sjcl/core/bitArray');
require('sjcl/core/sha1');
require('sjcl/core/sha256');
require('sjcl/core/codecBytes');
require('sjcl/core/codecString');
require('sjcl/core/random');
require('sjcl/core/aes');
require('sjcl/core/pbkdf2');
require('sjcl/core/hmac');
require('./ctrGladman');

sjcl.misc.hmacSha1 = function (key) {
sjcl.misc.hmac.call(this, key, sjcl.hash.sha1);
};
sjcl.misc.hmacSha1.prototype = new sjcl.misc.hmac("");
sjcl.misc.hmacSha1.prototype.constructor = sjcl.misc.hmacSha1;

function AesWorker(action, options) {
this._crypto = sjcl;
this._aesAction = action;
this._password = options.password;
this._keyLen = 8 * options.strength + 8;
this._macLen = this._keyLen;
this._saltLen = this._keyLen / 2;
this._version = options.version;
this._passVerifyLen = 2;
}

AesWorker.prototype.processData = function (data) {
var hmac;
var iv = [0, 0, 0, 0];

var salt = this._aesAction === "Encrypt" ?
this._crypto.random.randomWords(this._saltLen / 4) :
this._crypto.codec.bytes.toBits(data.slice(0, this._saltLen));
var derivedKey = this._crypto.misc.pbkdf2(this._password, salt, 1000, (this._macLen + this._keyLen + this._passVerifyLen) * 8, this._crypto.misc.hmacSha1);

var aesKey = this._crypto.bitArray.bitSlice(derivedKey, 0, this._keyLen * 8);
var macKey = this._crypto.bitArray.bitSlice(derivedKey, this._keyLen * 8, (this._keyLen + this._macLen) * 8);
var derivedPassVerifier = this._crypto.bitArray.bitSlice(derivedKey, (this._keyLen + this._macLen) * 8);

if (this._aesAction === "Encrypt") {
var encryptedData = this._crypto.mode.ctrGladman.encrypt(new this._crypto.cipher.aes(aesKey), this._crypto.codec.bytes.toBits(data), iv);
encryptedData = this._crypto.bitArray.clamp(encryptedData, (data.length) * 8);

hmac = new this._crypto.misc.hmacSha1(macKey);
var macData = hmac.encrypt(encryptedData);
macData = this._crypto.bitArray.clamp(macData, 10 * 8);

var fileData = this._crypto.bitArray.concat(salt, derivedPassVerifier);
fileData = this._crypto.bitArray.concat(fileData, encryptedData);
fileData = this._crypto.bitArray.concat(fileData, macData);
return Uint8Array.from(this._crypto.codec.bytes.fromBits(fileData));
} else {
var passVerifyValue = this._crypto.codec.bytes.toBits(data.slice(this._saltLen, this._saltLen + this._passVerifyLen));
if (!this._crypto.bitArray.equal(passVerifyValue, derivedPassVerifier)) {
throw new Error("Encrypted zip: incorrect password");
}

var encryptedValue = this._crypto.codec.bytes.toBits(data.slice(this._saltLen + this._passVerifyLen, -10));
var macValue = this._crypto.codec.bytes.toBits(data.slice(-10));
// if AE-2 format check mac
if (this._version === 2) {
hmac = new this._crypto.misc.hmacSha1(macKey);
var macVerifier = hmac.encrypt(encryptedValue);
macVerifier = this._crypto.bitArray.clamp(macVerifier, 10 * 8);
if (!this._crypto.bitArray.equal(macValue, macVerifier)) {
throw new Error("Corrupted zip: CRC failed");
}
}

var decryptData = this._crypto.mode.ctrGladman.decrypt(new this._crypto.cipher.aes(aesKey), encryptedValue, iv);
return Uint8Array.from(this._crypto.codec.bytes.fromBits(decryptData));
}
};

exports.encryptWorker = function (encryptOptions) {
return new AesWorker("Encrypt", encryptOptions);
};
exports.decryptWorker = function (decryptOptions) {
return new AesWorker("Decrypt", decryptOptions);
};
110 changes: 110 additions & 0 deletions lib/ctrGladman.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/** @fileOverview CTR mode implementation.
*
* Special thanks to Roy Nicholson for pointing out a bug in our
* implementation.
*
* @author Emily Stark
* @author Mike Hamburg
* @author Dan Boneh
*/

/**
* CTR mode with CBC MAC.
* @namespace
*/
sjcl.mode.ctrGladman = {
/** The name of the mode.
* @constant
*/
name: "ctrGladman",

/** Encrypt in CTR mode.
* @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes.
* @param {bitArray} plaintext The plaintext data.
* @param {bitArray} iv The initialization value. It must be 128 bits.
* @param {bitArray} [adata=[]] The authenticated data. Must be empty.
* @return The encrypted data, an array of bytes.
* @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits or if any adata is specified.
*/
encrypt: function (prf, plaintext, iv, adata) {
return sjcl.mode.ctrGladman._calculate(prf, plaintext, iv, adata);
},

/** Decrypt in CTR mode.
* @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes.
* @param {bitArray} ciphertext The ciphertext data.
* @param {bitArray} iv The initialization value. It must be 128 bits.
* @param {bitArray} [adata=[]] The authenticated data. It must be empty.
* @return The decrypted data, an array of bytes.
* @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits or if any adata is specified.
* @throws {sjcl.exception.corrupt} if if the message is corrupt.
*/
decrypt: function (prf, ciphertext, iv, adata) {
return sjcl.mode.ctrGladman._calculate(prf, ciphertext, iv, adata);
},

incWord: function (word) {
if (((word >> 24) & 0xff) === 0xff) { //overflow
var b1 = (word >> 16) & 0xff;
var b2 = (word >> 8) & 0xff;
var b3 = word & 0xff;

if (b1 === 0xff) { // overflow b1
b1 = 0;
if (b2 === 0xff) {
b2 = 0;
if (b3 === 0xff) {
b3 = 0;
} else {
++b3;
}
} else {
++b2;
}
} else {
++b1;
}

word = 0;
word += (b1 << 16);
word += (b2 << 8);
word += b3;
} else {
word += (0x01 << 24);
}
return word;
},

incCounter: function (counter) {
if ((counter[0] = this.incWord(counter[0])) === 0) {
// encr_data in fileenc.c from Dr Brian Gladman's counts only with DWORD j < 8
counter[1] = this.incWord(counter[1]);
}
return counter;
},

_calculate: function (prf, data, iv, adata) {
var l, bl, res, c, d, e, i;
if (adata && adata.length) {
throw new sjcl.exception.invalid("ctr can't authenticate data");
}
if (sjcl.bitArray.bitLength(iv) !== 128) {
throw new sjcl.exception.invalid("ctr iv must be 128 bits");
}
if (!(l = data.length)) {
return [];
}
c = iv.slice(0);
d = data.slice(0);
bl = sjcl.bitArray.bitLength(d);
for (i = 0; i < l; i += 4) {
this.incCounter(c);
e = prf.encrypt(c);
d[i] ^= e[0];
d[i + 1] ^= e[1];
d[i + 2] ^= e[2];
d[i + 3] ^= e[3];
}
return sjcl.bitArray.clamp(d, bl);
}
};
Loading