})();
var CipherTransformFactory = (function CipherTransformFactoryClosure() {
+ var defaultPasswordBytes = new Uint8Array([
+ 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
+ 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
+ 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
+ 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
+
function prepareKeyData(fileId, password, ownerPassword, userPassword,
flags, revision, keyLength, encryptMetadata) {
- var defaultPasswordBytes = new Uint8Array([
- 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
- 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
- 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
- 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
var hashData = new Uint8Array(100), i = 0, j, n;
if (password) {
n = Math.min(32, password.length);
var cipher, checkData;
if (revision >= 3) {
- // padded password in hashData, we can use this array for user
- // password check
- i = 32;
+ for (i = 0; i < 32; ++i)
+ hashData[i] = defaultPasswordBytes[i];
for (j = 0, n = fileId.length; j < n; ++j)
hashData[i++] = fileId[j];
cipher = new ARCFourCipher(encryptionKey);
cipher = new ARCFourCipher(derivedKey);
checkData = cipher.encryptBlock(checkData);
}
+ for (j = 0, n = checkData.length; j < n; ++j) {
+ if (userPassword[j] != checkData[j])
+ return null;
+ }
} else {
cipher = new ARCFourCipher(encryptionKey);
- checkData = cipher.encryptBlock(hashData.subarray(0, 32));
- }
- for (j = 0, n = checkData.length; j < n; ++j) {
- if (userPassword[j] != checkData[j])
- error('incorrect password');
+ checkData = cipher.encryptBlock(defaultPasswordBytes);
+ for (j = 0, n = checkData.length; j < n; ++j) {
+ if (userPassword[j] != checkData[j])
+ return null;
+ }
}
return encryptionKey;
}
+ function decodeUserPassword(password, ownerPassword, revision, keyLength) {
+ var hashData = new Uint8Array(32), i = 0, j, n;
+ n = Math.min(32, password.length);
+ for (; i < n; ++i)
+ hashData[i] = password[i];
+ j = 0;
+ while (i < 32) {
+ hashData[i++] = defaultPasswordBytes[j++];
+ }
+ var hash = calculateMD5(hashData, 0, i);
+ var keyLengthInBytes = keyLength >> 3;
+ if (revision >= 3) {
+ for (j = 0; j < 50; ++j) {
+ hash = calculateMD5(hash, 0, hash.length);
+ }
+ }
+
+ var cipher, userPassword;
+ if (revision >= 3) {
+ userPassword = ownerPassword;
+ var derivedKey = new Uint8Array(keyLengthInBytes), k;
+ for (j = 19; j >= 0; j--) {
+ for (k = 0; k < keyLengthInBytes; ++k)
+ derivedKey[k] = hash[k] ^ j;
+ cipher = new ARCFourCipher(derivedKey);
+ userPassword = cipher.encryptBlock(userPassword);
+ }
+ } else {
+ cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes));
+ userPassword = cipher.encryptBlock(ownerPassword);
+ }
+ return userPassword;
+ }
var identityName = new Name('Identity');
if (password)
passwordBytes = stringToBytes(password);
- this.encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
- ownerPassword, userPassword,
- flags, revision,
- keyLength, encryptMetadata);
+ var encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
+ ownerPassword, userPassword, flags,
+ revision, keyLength, encryptMetadata);
+ if (!encryptionKey && password) {
+ // Attempting use the password as an owner password
+ var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword,
+ revision, keyLength);
+ encryptionKey = prepareKeyData(fileIdBytes, decodedPassword,
+ ownerPassword, userPassword, flags,
+ revision, keyLength, encryptMetadata);
+ }
+
+ if (!encryptionKey)
+ error('incorrect password or encryption data');
+
+ this.encryptionKey = encryptionKey;
+
if (algorithm == 4) {
this.cf = dict.get('CF');
this.stmf = dict.get('StmF') || identityName;
});
});
+describe('CipherTransformFactory', function() {
+ function DictMock(map) {
+ this.map = map;
+ }
+ DictMock.prototype = {
+ get: function(key) {
+ return this.map[key];
+ }
+ };
+
+ var map1 = {
+ Filter: new Name('Standard'),
+ V: 2,
+ Length: 128,
+ O: unescape('%80%C3%04%96%91o%20sl%3A%E6%1B%13T%91%F2%0DV%12%E3%FF%5E%BB%' +
+ 'E9VO%D8k%9A%CA%7C%5D'),
+ U: unescape('j%0C%8D%3EY%19%00%BCjd%7D%91%BD%AA%00%18%00%00%00%00%00%00%0' +
+ '0%00%00%00%00%00%00%00%00%00'),
+ P: -1028,
+ R: 3
+ };
+ var fileID1 = unescape('%F6%C6%AF%17%F3rR%8DRM%9A%80%D1%EF%DF%18');
+
+ var map2 = {
+ Filter: new Name('Standard'),
+ V: 4,
+ Length: 128,
+ O: unescape('sF%14v.y5%27%DB%97%0A5%22%B3%E1%D4%AD%BD%9B%3C%B4%A5%89u%15%' +
+ 'B2Y%F1h%D9%E9%F4'),
+ U: unescape('%93%04%89%A9%BF%8AE%A6%88%A2%DB%C2%A0%A8gn%00%00%00%00%00%00' +
+ '%00%00%00%00%00%00%00%00%00%00'),
+ P: -1084,
+ R: 4
+ };
+ var fileID2 = unescape('%3CL_%3AD%96%AF@%9A%9D%B3%3Cx%1Cv%AC');
+
+ describe('#ctor', function() {
+ it('should accept user password', function() {
+ var factory = new CipherTransformFactory(new DictMock(map1), fileID1,
+ '123456');
+ });
+
+ it('should accept owner password', function() {
+ var factory = new CipherTransformFactory(new DictMock(map1), fileID1,
+ '654321');
+ });
+
+ it('should not accept wrong password', function() {
+ var thrown = false;
+ try {
+ var factory = new CipherTransformFactory(new DictMock(map1), fileID1,
+ 'wrong');
+ } catch (e) {
+ thrown = true;
+ }
+ expect(thrown).toEqual(true);
+ });
+
+ it('should accept no password', function() {
+ var factory = new CipherTransformFactory(new DictMock(map2), fileID2);
+ });
+ });
+});