summaryrefslogtreecommitdiff
path: root/frontend/beta
authorGiulio Cesare Solaroli <giulio.cesare@clipperz.com>2013-04-19 15:09:28 (UTC)
committer Giulio Cesare Solaroli <giulio.cesare@clipperz.com>2013-04-19 15:09:28 (UTC)
commit074e70457c90344b3c1cb236105638d692a0066b (patch) (unidiff)
treec5ffabd3eaf74cbeb69974beacdb5a5f8c235adc /frontend/beta
parent48c9280c9a255f2a85ad5729830df884e64a9c5d (diff)
downloadclipperz-074e70457c90344b3c1cb236105638d692a0066b.zip
clipperz-074e70457c90344b3c1cb236105638d692a0066b.tar.gz
clipperz-074e70457c90344b3c1cb236105638d692a0066b.tar.bz2
Fixed an issue on the AES-CTR block mode
The previous version of the CTR encoding was incrementing the counter in a weird way, mixing up data from the previous block. The current fix can correctly decrypt data encoded with AES-CTR using other libraries/languages (currently tested only with Python).
Diffstat (limited to 'frontend/beta') (more/less context) (ignore whitespace changes)
-rw-r--r--frontend/beta/js/Clipperz/Crypto/AES_2.js829
-rw-r--r--frontend/beta/js/Clipperz/PM/Crypto.js91
-rw-r--r--frontend/beta/properties/beta.properties.json1
3 files changed, 889 insertions, 32 deletions
diff --git a/frontend/beta/js/Clipperz/Crypto/AES_2.js b/frontend/beta/js/Clipperz/Crypto/AES_2.js
new file mode 100644
index 0000000..9735d17
--- a/dev/null
+++ b/frontend/beta/js/Clipperz/Crypto/AES_2.js
@@ -0,0 +1,829 @@
1/*
2
3Copyright 2008-2013 Clipperz Srl
4
5This file is part of Clipperz, the online password manager.
6For further information about its features and functionalities please
7refer to http://www.clipperz.com.
8
9* Clipperz is free software: you can redistribute it and/or modify it
10 under the terms of the GNU Affero General Public License as published
11 by the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14* Clipperz is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 See the GNU Affero General Public License for more details.
18
19* You should have received a copy of the GNU Affero General Public
20 License along with Clipperz. If not, see http://www.gnu.org/licenses/.
21
22*/
23
24try { if (typeof(Clipperz.ByteArray) == 'undefined') { throw ""; }} catch (e) {
25 throw "Clipperz.Crypto.AES_2 depends on Clipperz.ByteArray!";
26}
27
28 //Dependency commented to avoid a circular reference
29//try { if (typeof(Clipperz.Crypto.PRNG) == 'undefined') { throw ""; }} catch (e) {
30 //throw "Clipperz.Crypto.AES_2 depends on Clipperz.Crypto.PRNG!";
31//}
32
33if (typeof(Clipperz.Crypto.AES_2) == 'undefined') { Clipperz.Crypto.AES_2 = {}; }
34
35//#############################################################################
36
37Clipperz.Crypto.AES_2.DeferredExecutionContext = function(args) {
38 args = args || {};
39
40 this._key = args.key;
41 this._message = args.message;
42 this._result = args.message.clone();
43 this._nonce = args.nonce;
44 this._messageLength = this._message.length();
45
46 this._messageArray = this._message.arrayValues();
47 this._resultArray = this._result.arrayValues();
48 this._nonceArray = this._nonce.arrayValues();
49
50 this._executionStep = 0;
51
52 return this;
53}
54
55Clipperz.Crypto.AES_2.DeferredExecutionContext.prototype = MochiKit.Base.update(null, {
56
57 'key': function() {
58 return this._key;
59 },
60
61 'message': function() {
62 return this._message;
63 },
64
65 'messageLength': function() {
66 return this._messageLength;
67 },
68
69 'result': function() {
70 return new Clipperz.ByteArray(this.resultArray());
71 },
72
73 'nonce': function() {
74 return this._nonce;
75 },
76
77 'messageArray': function() {
78 return this._messageArray;
79 },
80
81 'resultArray': function() {
82 return this._resultArray;
83 },
84
85 'nonceArray': function() {
86 return this._nonceArray;
87 },
88
89 'elaborationChunkSize': function() {
90 return Clipperz.Crypto.AES_2.DeferredExecution.chunkSize;
91 },
92
93 'executionStep': function() {
94 return this._executionStep;
95 },
96
97 'setExecutionStep': function(aValue) {
98 this._executionStep = aValue;
99 },
100
101 'pause': function(aValue) {
102 return MochiKit.Async.wait(Clipperz.Crypto.AES_2.DeferredExecution.pauseTime, aValue);
103 },
104
105 //-----------------------------------------------------------------------------
106 __syntaxFix__: "syntax fix"
107
108});
109
110//#############################################################################
111
112Clipperz.Crypto.AES_2.Key = function(args) {
113 args = args || {};
114
115 this._key = args.key;
116 this._keySize = args.keySize || this.key().length();
117
118 if (this.keySize() == 128/8) {
119 this._b = 176;
120 this._numberOfRounds = 10;
121 } else if (this.keySize() == 256/8) {
122 this._b = 240;
123 this._numberOfRounds = 14;
124 } else {
125 MochiKit.Logging.logError("AES unsupported key size: " + (this.keySize() * 8) + " bits");
126 throw Clipperz.Crypto.AES_2.exception.UnsupportedKeySize;
127 }
128
129 this._stretchedKey = null;
130
131 return this;
132}
133
134Clipperz.Crypto.AES_2.Key.prototype = MochiKit.Base.update(null, {
135
136 'asString': function() {
137 return "Clipperz.Crypto.AES_2.Key (" + this.key().toHexString() + ")";
138 },
139
140 //-----------------------------------------------------------------------------
141
142 'key': function() {
143 return this._key;
144 },
145
146 'keySize': function() {
147 return this._keySize;
148 },
149
150 'b': function() {
151 return this._b;
152 },
153
154 'numberOfRounds': function() {
155 return this._numberOfRounds;
156 },
157 //=========================================================================
158
159 'keyScheduleCore': function(aWord, aRoundConstantsIndex) {
160 varresult;
161 var sbox;
162
163 sbox = Clipperz.Crypto.AES_2.sbox();
164
165 result = [sbox[aWord[1]] ^ Clipperz.Crypto.AES_2.roundConstants()[aRoundConstantsIndex],
166 sbox[aWord[2]],
167 sbox[aWord[3]],
168 sbox[aWord[0]]];
169
170 return result;
171 },
172
173 //-----------------------------------------------------------------------------
174
175 'xorWithPreviousStretchValues': function(aKey, aWord, aPreviousWordIndex) {
176 varresult;
177 var i,c;
178
179 result = [];
180 c = 4;
181 for (i=0; i<c; i++) {
182 result[i] = aWord[i] ^ aKey.byteAtIndex(aPreviousWordIndex + i);
183 }
184
185 return result;
186 },
187
188 //-----------------------------------------------------------------------------
189
190 'sboxShakeup': function(aWord) {
191 var result;
192 var sbox;
193 var i,c;
194
195 result = [];
196 sbox = Clipperz.Crypto.AES_2.sbox();
197 c =4;
198 for (i=0; i<c; i++) {
199 result[i] = sbox[aWord[i]];
200 }
201
202 return result;
203 },
204
205 //-----------------------------------------------------------------------------
206
207 'stretchKey': function(aKey) {
208 varcurrentWord;
209 varkeyLength;
210 varpreviousStretchIndex;
211 var i,c;
212
213 keyLength = aKey.length();
214 previousStretchIndex = keyLength - this.keySize();
215
216 currentWord = [aKey.byteAtIndex(keyLength - 4),
217 aKey.byteAtIndex(keyLength - 3),
218 aKey.byteAtIndex(keyLength - 2),
219 aKey.byteAtIndex(keyLength - 1)];
220 currentWord = this.keyScheduleCore(currentWord, keyLength / this.keySize());
221
222 if (this.keySize() == 256/8) {
223 c = 8;
224 } else if (this.keySize() == 128/8){
225 c = 4;
226 }
227
228 for (i=0; i<c; i++) {
229 if (i == 4) {
230 //fifth streatch word
231 currentWord = this.sboxShakeup(currentWord);
232 }
233
234 currentWord = this.xorWithPreviousStretchValues(aKey, currentWord, previousStretchIndex + (i*4));
235 aKey.appendBytes(currentWord);
236 }
237
238 return aKey;
239 },
240
241 //-----------------------------------------------------------------------------
242
243 'stretchedKey': function() {
244 if (this._stretchedKey == null) {
245 var stretchedKey;
246
247 stretchedKey = this.key().clone();
248
249 while (stretchedKey.length() < this.keySize()) {
250 stretchedKey.appendByte(0);
251 }
252
253 while (stretchedKey.length() < this.b()) {
254 stretchedKey = this.stretchKey(stretchedKey);
255 }
256
257 this._stretchedKey = stretchedKey.split(0, this.b());
258 }
259
260 return this._stretchedKey;
261 },
262
263 //=========================================================================
264 __syntaxFix__: "syntax fix"
265});
266
267//#############################################################################
268
269Clipperz.Crypto.AES_2.State = function(args) {
270 args = args || {};
271
272 this._data = args.block.slice(0);
273 this._key = args.key;
274
275 return this;
276}
277
278Clipperz.Crypto.AES_2.State.prototype = MochiKit.Base.update(null, {
279
280 'key': function() {
281 return this._key;
282 },
283
284 //-----------------------------------------------------------------------------
285
286 'data': function() {
287 return this._data;
288 },
289
290 'setData': function(aValue) {
291 this._data = aValue;
292 },
293
294 //=========================================================================
295
296 'addRoundKey': function(aRoundNumber) {
297 //each byte of the state is combined with the round key; each round key is derived from the cipher key using a key schedule.
298 vardata;
299 varstretchedKey;
300 varfirstStretchedKeyIndex;
301 var i,c;
302
303 data = this.data();
304 stretchedKey = this.key().stretchedKey();
305 firstStretchedKeyIndex = aRoundNumber * (128/8);
306 c = 128/8;
307 for (i=0; i<c; i++) {
308 data[i] = data[i] ^ stretchedKey.byteAtIndex(firstStretchedKeyIndex + i);
309 }
310 },
311
312 //-----------------------------------------------------------------------------
313
314 'subBytes': function() {
315 // a non-linear substitution step where each byte is replaced with another according to a lookup table.
316 var i,c;
317 vardata;
318 var sbox;
319
320 data = this.data();
321 sbox = Clipperz.Crypto.AES_2.sbox();
322
323 c = 16;
324 for (i=0; i<c; i++) {
325 data[i] = sbox[data[i]];
326 }
327 },
328
329 //-----------------------------------------------------------------------------
330
331 'shiftRows': function() {
332 //a transposition step where each row of the state is shifted cyclically a certain number of steps.
333 varnewValue;
334 vardata;
335 varshiftMapping;
336 vari,c;
337
338 newValue = new Array(16);
339 data = this.data();
340 shiftMapping = Clipperz.Crypto.AES_2.shiftRowMapping();
341 // [0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11];
342 c = 16;
343 for (i=0; i<c; i++) {
344 newValue[i] = data[shiftMapping[i]];
345 }
346 for (i=0; i<c; i++) {
347 data[i] = newValue[i];
348 }
349 },
350
351 //-----------------------------------------------------------------------------
352/*
353 'mixColumnsWithValues': function(someValues) {
354 varresult;
355 vara;
356 var i,c;
357
358 c = 4;
359 result = [];
360 a = [];
361 for (i=0; i<c; i++) {
362 a[i] = [];
363 a[i][1] = someValues[i]
364 if ((a[i][1] & 0x80) == 0x80) {
365 a[i][2] = (a[i][1] << 1) ^ 0x11b;
366 } else {
367 a[i][2] = a[i][1] << 1;
368 }
369
370 a[i][3] = a[i][2] ^ a[i][1];
371 }
372
373 for (i=0; i<c; i++) {
374 varx;
375
376 x = Clipperz.Crypto.AES_2.mixColumnsMatrix()[i];
377 result[i] = a[0][x[0]] ^ a[1][x[1]] ^ a[2][x[2]] ^ a[3][x[3]];
378 }
379
380 return result;
381 },
382
383 'mixColumns': function() {
384 //a mixing operation which operates on the columns of the state, combining the four bytes in each column using a linear transformation.
385 var data;
386 var i, c;
387
388 data = this.data();
389 c = 4;
390 for(i=0; i<c; i++) {
391 varblockIndex;
392 var mixedValues;
393
394 blockIndex = i * 4;
395 mixedValues = this.mixColumnsWithValues([data[blockIndex + 0],
396 data[blockIndex + 1],
397 data[blockIndex + 2],
398 data[blockIndex + 3]]);
399 data[blockIndex + 0] = mixedValues[0];
400 data[blockIndex + 1] = mixedValues[1];
401 data[blockIndex + 2] = mixedValues[2];
402 data[blockIndex + 3] = mixedValues[3];
403 }
404 },
405*/
406
407 'mixColumns': function() {
408 //a mixing operation which operates on the columns of the state, combining the four bytes in each column using a linear transformation.
409 var data;
410 var i, c;
411 var a_1;
412 var a_2;
413
414 a_1 = new Array(4);
415 a_2 = new Array(4);
416
417 data = this.data();
418 c = 4;
419 for(i=0; i<c; i++) {
420 varblockIndex;
421 var ii, cc;
422
423 blockIndex = i * 4;
424
425 cc = 4;
426 for (ii=0; ii<cc; ii++) {
427 var value;
428
429 value = data[blockIndex + ii];
430 a_1[ii] = value;
431 a_2[ii] = (value & 0x80) ? ((value << 1) ^ 0x011b) : (value << 1);
432 }
433
434 data[blockIndex + 0] = a_2[0] ^ a_1[1] ^ a_2[1] ^ a_1[2] ^ a_1[3];
435 data[blockIndex + 1] = a_1[0] ^ a_2[1] ^ a_1[2] ^ a_2[2] ^ a_1[3];
436 data[blockIndex + 2] = a_1[0] ^ a_1[1] ^ a_2[2] ^ a_1[3] ^ a_2[3];
437 data[blockIndex + 3] = a_1[0] ^ a_2[0] ^ a_1[1] ^ a_1[2] ^ a_2[3];
438 }
439 },
440
441 //=========================================================================
442
443 'spinRound': function(aRoundNumber) {
444 this.addRoundKey(aRoundNumber);
445 this.subBytes();
446 this.shiftRows();
447 this.mixColumns();
448 },
449
450 'spinLastRound': function() {
451 this.addRoundKey(this.key().numberOfRounds() - 1);
452 this.subBytes();
453 this.shiftRows();
454 this.addRoundKey(this.key().numberOfRounds());
455 },
456
457 //=========================================================================
458
459 'encrypt': function() {
460 vari,c;
461
462 c = this.key().numberOfRounds() - 1;
463 for (i=0; i<c; i++) {
464 this.spinRound(i);
465 }
466
467 this.spinLastRound();
468 },
469
470 //=========================================================================
471 __syntaxFix__: "syntax fix"
472});
473
474//#############################################################################
475
476Clipperz.Crypto.AES_2.VERSION = "0.1";
477Clipperz.Crypto.AES_2.NAME = "Clipperz.Crypto.AES_2";
478
479MochiKit.Base.update(Clipperz.Crypto.AES_2, {
480
481 //http://www.cs.eku.edu/faculty/styer/460/Encrypt/JS-AES.html
482 //http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
483 //http://en.wikipedia.org/wiki/Rijndael_key_schedule
484 //http://en.wikipedia.org/wiki/Rijndael_S-box
485
486 '__repr__': function () {
487 return "[" + this.NAME + " " + this.VERSION + "]";
488 },
489
490 'toString': function () {
491 return this.__repr__();
492 },
493
494 //=============================================================================
495
496 '_sbox': null,
497 'sbox': function() {
498 if (Clipperz.Crypto.AES_2._sbox == null) {
499 Clipperz.Crypto.AES_2._sbox = [
5000x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
5010xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
5020xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
5030x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
5040x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
5050x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
5060xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
5070x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
5080xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
5090x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
5100xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
5110xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
5120xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
5130x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
5140xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
5150x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
516 ];
517 }
518
519 return Clipperz.Crypto.AES_2._sbox;
520 },
521
522 //-----------------------------------------------------------------------------
523 //
524 // 0 4 8 12 0 4 812
525 // 1 5 9 13 => 5 9 131
526 // 2 6 10 14 10 14 26
527 // 3 7 11 15 15 3 711
528 //
529 '_shiftRowMapping': null,
530 'shiftRowMapping': function() {
531 if (Clipperz.Crypto.AES_2._shiftRowMapping == null) {
532 Clipperz.Crypto.AES_2._shiftRowMapping = [0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11];
533 }
534
535 return Clipperz.Crypto.AES_2._shiftRowMapping;
536 },
537
538 //-----------------------------------------------------------------------------
539
540 '_mixColumnsMatrix': null,
541 'mixColumnsMatrix': function() {
542 if (Clipperz.Crypto.AES_2._mixColumnsMatrix == null) {
543 Clipperz.Crypto.AES_2._mixColumnsMatrix = [[2, 3, 1 ,1],
544 [1, 2, 3, 1],
545 [1, 1, 2, 3],
546 [3, 1, 1, 2] ];
547 }
548
549 return Clipperz.Crypto.AES_2._mixColumnsMatrix;
550 },
551
552 '_roundConstants': null,
553 'roundConstants': function() {
554 if (Clipperz.Crypto.AES_2._roundConstants == null) {
555 Clipperz.Crypto.AES_2._roundConstants = [ , 1, 2, 4, 8, 16, 32, 64, 128, 27, 54, 108, 216, 171, 77, 154];
556 // Clipperz.Crypto.AES_2._roundConstants = [ , 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a];
557 }
558
559 return Clipperz.Crypto.AES_2._roundConstants;
560 },
561
562 //=============================================================================
563
564 'incrementNonce': function(nonce) {
565 var i;
566 var done;
567
568 done = false;
569 i = nonce.length - 1;
570
571 while ((i>=0) && (done == false)) {
572 var currentByteValue;
573
574 currentByteValue = nonce[i];
575
576 if (currentByteValue == 0xff) {
577 nonce[i] = 0;
578 if (i>= 0) {
579 i --;
580 } else {
581 done = true;
582 }
583 } else {
584 nonce[i] = currentByteValue + 1;
585 done = true;
586 }
587 }
588 },
589
590 //-----------------------------------------------------------------------------
591
592 'encryptBlock': function(aKey, aBlock) {
593 varresult;
594 varstate;
595
596 state = new Clipperz.Crypto.AES_2.State({block:aBlock, key:aKey});
597//is(state.data(), 'before');
598 state.encrypt();
599 result = state.data();
600
601 return result;
602 },
603
604 //-----------------------------------------------------------------------------
605
606 'encryptBlocks': function(aKey, aMessage, aNonce) {
607 varresult;
608 var nonce;
609 var self;
610 varmessageIndex;
611 varmessageLength;
612 var blockSize;
613
614 self = Clipperz.Crypto.AES_2;
615 blockSize = 128/8;
616 messageLength = aMessage.length;
617 nonce = aNonce;
618
619 result = aMessage;
620 messageIndex = 0;
621 while (messageIndex < messageLength) {
622 var encryptedBlock;
623 var i,c;
624
625 encryptedBlock = self.encryptBlock(aKey, nonce);
626
627 if ((messageLength - messageIndex) > blockSize) {
628 c = blockSize;
629 } else {
630 c = messageLength - messageIndex;
631 }
632
633 for (i=0; i<c; i++) {
634 result[messageIndex + i] = result[messageIndex + i] ^ encryptedBlock[i];
635 }
636
637 messageIndex += blockSize;
638 self.incrementNonce(nonce);
639 }
640
641 return result;
642 },
643
644 //-----------------------------------------------------------------------------
645
646 'encrypt': function(aKey, someData, aNonce) {
647 var result;
648 var nonce;
649 varencryptedData;
650 var key;
651
652 key = new Clipperz.Crypto.AES_2.Key({key:aKey});
653 nonce = aNonce ? aNonce.clone() : Clipperz.Crypto.PRNG.defaultRandomGenerator().getRandomBytes(128/8);
654
655 encryptedData = Clipperz.Crypto.AES_2.encryptBlocks(key, someData.arrayValues(), nonce.arrayValues());
656
657 result = nonce.appendBytes(encryptedData);
658
659 return result;
660 },
661
662 //-----------------------------------------------------------------------------
663
664 'decrypt': function(aKey, someData) {
665 var result;
666 var nonce;
667 var encryptedData;
668 var decryptedData;
669 vardataIterator;
670 var key;
671
672 key = new Clipperz.Crypto.AES_2.Key({key:aKey});
673
674 encryptedData = someData.arrayValues();
675 nonce = encryptedData.slice(0, (128/8));
676 encryptedData = encryptedData.slice(128/8);
677 decryptedData = Clipperz.Crypto.AES_2.encryptBlocks(key, encryptedData, nonce);
678
679 result = new Clipperz.ByteArray(decryptedData);
680
681 return result;
682 },
683
684 //=============================================================================
685
686 'deferredEncryptExecutionChunk': function(anExecutionContext) {
687 varresult;
688 var nonce;
689 var self;
690 varmessageIndex;
691 varmessageLength;
692 var blockSize;
693 var executionLimit;
694
695 self = Clipperz.Crypto.AES_2;
696 blockSize = 128/8;
697 messageLength = anExecutionContext.messageArray().length;
698 nonce = anExecutionContext.nonceArray();
699 result = anExecutionContext.resultArray();
700
701 messageIndex = anExecutionContext.executionStep();
702 executionLimit = messageIndex + anExecutionContext.elaborationChunkSize();
703 executionLimit = Math.min(executionLimit, messageLength);
704
705 while (messageIndex < executionLimit) {
706 var encryptedBlock;
707 var i,c;
708
709 encryptedBlock = self.encryptBlock(anExecutionContext.key(), nonce);
710
711 if ((executionLimit - messageIndex) > blockSize) {
712 c = blockSize;
713 } else {
714 c = executionLimit - messageIndex;
715 }
716
717 for (i=0; i<c; i++) {
718 result[messageIndex + i] = result[messageIndex + i] ^ encryptedBlock[i];
719 }
720
721 messageIndex += blockSize;
722 self.incrementNonce(nonce);
723 }
724 anExecutionContext.setExecutionStep(messageIndex);
725
726 return anExecutionContext;
727 },
728
729 //-----------------------------------------------------------------------------
730
731 'deferredEncryptBlocks': function(anExecutionContext) {
732 vardeferredResult;
733 varmessageSize;
734 var i,c;
735 var now;
736
737 messageSize = anExecutionContext.messageLength();
738
739 deferredResult = new MochiKit.Async.Deferred();
740//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("Clipperz.Crypto.AES_2.deferredEncryptBlocks - START: " + res); return res;});
741 // deferredResult.addCallback(MochiKit.Base.method(anExecutionContext, 'pause'));
742
743 c = Math.ceil(messageSize / anExecutionContext.elaborationChunkSize());
744 for (i=0; i<c; i++) {
745//deferredResult.addBoth(function(res) {now = new Date(); return res;});
746//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("Clipperz.Crypto.AES_2.deferredEncryptBlocks - : (" + i + ") - " + res); return res;});
747 deferredResult.addCallback(Clipperz.Crypto.AES_2.deferredEncryptExecutionChunk);
748//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("[" + (new Date() - now) + "]Clipperz.Crypto.AES_2.deferredEncryptBlocks"); return res;});
749//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("Clipperz.Crypto.AES_2.deferredEncryptBlocks - : (" + i + ") -- " + res); return res;});
750 deferredResult.addCallback(MochiKit.Base.method(anExecutionContext, 'pause'));
751//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("Clipperz.Crypto.AES_2.deferredEncryptBlocks - : (" + i + ") --- " + res); return res;});
752 }
753//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("Clipperz.Crypto.AES_2.deferredEncryptBlocks - END: " + res); return res;});
754
755 deferredResult.callback(anExecutionContext);
756
757 return deferredResult;
758 },
759
760 //-----------------------------------------------------------------------------
761
762 'deferredEncrypt': function(aKey, someData, aNonce) {
763 var deferredResult;
764 varexecutionContext;
765 var result;
766 var nonce;
767 var key;
768
769 key = new Clipperz.Crypto.AES_2.Key({key:aKey});
770 nonce = aNonce ? aNonce.clone() : Clipperz.Crypto.PRNG.defaultRandomGenerator().getRandomBytes(128/8);
771
772 executionContext = new Clipperz.Crypto.AES_2.DeferredExecutionContext({key:key, message:someData, nonce:nonce});
773
774 deferredResult = new MochiKit.Async.Deferred();
775//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("Clipperz.Crypto.AES_2.deferredEncrypt - 1: " + res); return res;});
776 deferredResult.addCallback(Clipperz.Crypto.AES_2.deferredEncryptBlocks);
777//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("Clipperz.Crypto.AES_2.deferredEncrypt - 2: " + res); return res;});
778 deferredResult.addCallback(function(anExecutionContext) {
779 var result;
780
781 result = anExecutionContext.nonce().clone();
782 result.appendBytes(anExecutionContext.resultArray());
783
784 return result;
785 });
786//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("Clipperz.Crypto.AES_2.deferredEncrypt - 3: " + res); return res;});
787 deferredResult.callback(executionContext)
788
789 return deferredResult;
790 },
791
792 //-----------------------------------------------------------------------------
793
794 'deferredDecrypt': function(aKey, someData) {
795 var deferredResult
796 var nonce;
797 var message;
798 var key;
799
800 key = new Clipperz.Crypto.AES_2.Key({key:aKey});
801 nonce = someData.split(0, (128/8));
802 message = someData.split(128/8);
803 executionContext = new Clipperz.Crypto.AES_2.DeferredExecutionContext({key:key, message:message, nonce:nonce});
804
805 deferredResult = new MochiKit.Async.Deferred();
806 deferredResult.addCallback(Clipperz.Crypto.AES_2.deferredEncryptBlocks);
807 deferredResult.addCallback(function(anExecutionContext) {
808 return anExecutionContext.result();
809 });
810 deferredResult.callback(executionContext);
811
812 return deferredResult;
813 },
814
815 //-----------------------------------------------------------------------------
816 __syntaxFix__: "syntax fix"
817
818});
819
820//#############################################################################
821
822Clipperz.Crypto.AES_2.DeferredExecution = {
823 'chunkSize': 4096, // 1024 4096 8192 1638432768;
824 'pauseTime': 0.2
825}
826
827Clipperz.Crypto.AES_2.exception = {
828 'UnsupportedKeySize': new MochiKit.Base.NamedError("Clipperz.Crypto.AES_2.exception.UnsupportedKeySize")
829};
diff --git a/frontend/beta/js/Clipperz/PM/Crypto.js b/frontend/beta/js/Clipperz/PM/Crypto.js
index ad16ff0..e1e87ec 100644
--- a/frontend/beta/js/Clipperz/PM/Crypto.js
+++ b/frontend/beta/js/Clipperz/PM/Crypto.js
@@ -45,33 +45,33 @@ MochiKit.Base.update(Clipperz.PM.Crypto, {
45 'communicationProtocol': { 45 'communicationProtocol': {
46 'currentVersion': '0.2', 46 'currentVersion': '0.2',
47 'versions': { 47 'versions': {
48 '0.1': Clipperz.PM.Connection.SRP['1.0'],//Clipperz.Crypto.SRP.versions['1.0'].Connection, 48 '0.1': Clipperz.PM.Connection.SRP['1.0'],//Clipperz.Crypto.SRP.versions['1.0'].Connection,
49 '0.2': Clipperz.PM.Connection.SRP['1.1']//Clipperz.Crypto.SRP.versions['1.1'].Connection, 49 '0.2': Clipperz.PM.Connection.SRP['1.1']//Clipperz.Crypto.SRP.versions['1.1'].Connection,
50 }, 50 },
51 'fallbackVersions': { 51 'fallbackVersions': {
52 'current':'0.1', 52 'current':'0.1',
53 '0.2': '0.1', 53 '0.2': '0.1',
54 '0.1': null 54 '0.1': null
55 } 55 }
56 }, 56 },
57 57
58 //------------------------------------------------------------------------- 58 //-------------------------------------------------------------------------
59 59
60 'encryptingFunctions': { 60 'encryptingFunctions': {
61 'currentVersion': '0.3', 61 'currentVersion': '0.4',
62 'versions': { 62 'versions': {
63 63
64 //##################################################################### 64 //#####################################################################
65 65
66 '0.1': { 66 '0.1': {
67 'encrypt': function(aKey, aValue) { 67 'encrypt': function(aKey, aValue) {
68 return Clipperz.Crypto.Base.encryptUsingSecretKey(aKey, Clipperz.Base.serializeJSON(aValue)); 68 return Clipperz.Crypto.Base.encryptUsingSecretKey(aKey, Clipperz.Base.serializeJSON(aValue));
69 }, 69 },
70 70
71 'deferredEncrypt': function(aKey, aValue) { 71 'deferredEncrypt': function(aKey, aValue) {
72 var deferredResult; 72 var deferredResult;
73 73
74 deferredResult = new MochiKit.Async.Deferred(); 74 deferredResult = new MochiKit.Async.Deferred();
75 deferredResult.addCallback(Clipperz.PM.Crypto.encryptingFunctions.versions['0.1'].encrypt, aKey, aValue); 75 deferredResult.addCallback(Clipperz.PM.Crypto.encryptingFunctions.versions['0.1'].encrypt, aKey, aValue);
76 deferredResult.callback(); 76 deferredResult.callback();
77 77
@@ -282,33 +282,33 @@ MochiKit.Base.update(Clipperz.PM.Crypto, {
282 } catch (exception) { 282 } catch (exception) {
283 MochiKit.Logging.logError("Error while decrypting data"); 283 MochiKit.Logging.logError("Error while decrypting data");
284 throw Clipperz.Crypto.Base.exception.CorruptedMessage; 284 throw Clipperz.Crypto.Base.exception.CorruptedMessage;
285 } 285 }
286 } else { 286 } else {
287 result = null; 287 result = null;
288 } 288 }
289 289
290 return result; 290 return result;
291 }, 291 },
292 292
293 'deferredDecrypt': function(aKey, aValue) { 293 'deferredDecrypt': function(aKey, aValue) {
294 var deferredResult; 294 var deferredResult;
295 // var now; 295 // var now;
296 296
297 deferredResult = new MochiKit.Async.Deferred(); 297 deferredResult = new MochiKit.Async.Deferred();
298 now = new Date; 298 // now = new Date;
299 299
300//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("[" + (new Date() - now) + "] Clipperz.PM.Crypto.deferredDecrypt - 1: " + res); return res;}); 300//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("[" + (new Date() - now) + "] Clipperz.PM.Crypto.deferredDecrypt - 1: " + res); return res;});
301 if (aValue != null) { 301 if (aValue != null) {
302 var key, value; 302 var key, value;
303 var decryptedData; 303 var decryptedData;
304 var decryptedValue; 304 var decryptedValue;
305 305
306 key = Clipperz.Crypto.SHA.sha_d256(new Clipperz.ByteArray(aKey)); 306 key = Clipperz.Crypto.SHA.sha_d256(new Clipperz.ByteArray(aKey));
307//MochiKit.Logging.logDebug("[" + (new Date() - now) + "] computed key"); 307//MochiKit.Logging.logDebug("[" + (new Date() - now) + "] computed key");
308 value = new Clipperz.ByteArray().appendBase64String(aValue); 308 value = new Clipperz.ByteArray().appendBase64String(aValue);
309//MochiKit.Logging.logDebug("[" + (new Date() - now) + "] appendedBase64String"); 309//MochiKit.Logging.logDebug("[" + (new Date() - now) + "] appendedBase64String");
310 310
311//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("[" + (new Date() - now) + "] Clipperz.PM.Crypto.deferredDecrypt - 1.1: " /* + res*/); return res;}); 311//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("[" + (new Date() - now) + "] Clipperz.PM.Crypto.deferredDecrypt - 1.1: " /* + res*/); return res;});
312 deferredResult.addCallback(Clipperz.Crypto.AES.deferredDecrypt, key, value); 312 deferredResult.addCallback(Clipperz.Crypto.AES.deferredDecrypt, key, value);
313//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("[" + (new Date() - now) + "] Clipperz.PM.Crypto.deferredDecrypt - 2: " /* + res*/); return res;}); 313//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("[" + (new Date() - now) + "] Clipperz.PM.Crypto.deferredDecrypt - 2: " /* + res*/); return res;});
314 deferredResult.addCallback(MochiKit.Async.wait, 0.1); 314 deferredResult.addCallback(MochiKit.Async.wait, 0.1);
@@ -327,106 +327,133 @@ MochiKit.Base.update(Clipperz.PM.Crypto, {
327 }) 327 })
328//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("[" + (new Date() - now) + "] Clipperz.PM.Crypto.deferredDecrypt - 7: " /* + res*/); return res;}); 328//deferredResult.addBoth(function(res) {MochiKit.Logging.logDebug("[" + (new Date() - now) + "] Clipperz.PM.Crypto.deferredDecrypt - 7: " /* + res*/); return res;});
329 } else { 329 } else {
330 deferredResult.addCallback(function() { 330 deferredResult.addCallback(function() {
331 return null; 331 return null;
332 }); 332 });
333 } 333 }
334 deferredResult.callback(); 334 deferredResult.callback();
335 335
336 return deferredResult; 336 return deferredResult;
337 }, 337 },
338 338
339 'hash': Clipperz.Crypto.SHA.sha_d256 339 'hash': Clipperz.Crypto.SHA.sha_d256
340 }, 340 },
341 341
342 //##################################################################### 342 //#####################################################################
343/* 343
344 '0.4': { 344 '0.4': {
345 'encrypt': function(aKey, aValue, aNonce) { 345 'encrypt': function(aKey, aValue, aNonce) {
346 var result; 346 var result;
347 varkey, value; 347 varkey, value;
348 var data; 348 var data;
349 var dataToEncrypt; 349 var dataToEncrypt;
350 var encryptedData; 350 var encryptedData;
351 351
352//MochiKit.Logging.logDebug(">>> [" + (new Date()).valueOf() + "] Clipperz.PM.Crypto.versions[0.3].encrypt");
353 key = Clipperz.Crypto.SHA.sha_d256(new Clipperz.ByteArray(aKey)); 352 key = Clipperz.Crypto.SHA.sha_d256(new Clipperz.ByteArray(aKey));
354//MochiKit.Logging.logDebug("--- [" + (new Date()).valueOf() + "] Clipperz.PM.Crypto.versions[0.3].encrypt - 1");
355 value = Clipperz.Base.serializeJSON(aValue); 353 value = Clipperz.Base.serializeJSON(aValue);
356//MochiKit.Logging.logDebug("--- [" + (new Date()).valueOf() + "] Clipperz.PM.Crypto.versions[0.3].encrypt - 2");
357/ *
358//MochiKit.Logging.logDebug("--> encrypt.fullSize: " + value.length);
359 value = value.replace(/":{"label":"/g, '":{l:"');
360 value = value.replace(/":{"key":"/g, '":{k:"');
361 value = value.replace(/":{"notes":"/g, '":{n:"');
362 value = value.replace(/":{"record":"/g, '":{r:"');
363 value = value.replace(/", "label":"/g, '",l:"');
364 value = value.replace(/", "favicon":"/g,'",f:"');
365//MochiKit.Logging.logDebug("<-- encrypt.compressed: " + value.length);
366* /
367 data = new Clipperz.ByteArray(value); 354 data = new Clipperz.ByteArray(value);
368//MochiKit.Logging.logDebug("--- [" + (new Date()).valueOf() + "] Clipperz.PM.Crypto.versions[0.3].encrypt - 3"); 355 encryptedData = Clipperz.Crypto.AES_2.encrypt(key, data, aNonce);
369 encryptedData = Clipperz.Crypto.AES.encrypt(key, data, aNonce);
370//MochiKit.Logging.logDebug("--- [" + (new Date()).valueOf() + "] Clipperz.PM.Crypto.versions[0.3].encrypt - 4");
371 result = encryptedData.toBase64String(); 356 result = encryptedData.toBase64String();
372//MochiKit.Logging.logDebug("<<< [" + (new Date()).valueOf() + "] Clipperz.PM.Crypto.versions[0.3].encrypt");
373 357
374 return result; 358 return result;
375 }, 359 },
360
361 'deferredEncrypt': function(aKey, aValue, aNonce) {
362 var deferredResult;
363 varkey, value;
364 var data;
365 var dataToEncrypt;
366 var encryptedData;
367
368 key = Clipperz.Crypto.SHA.sha_d256(new Clipperz.ByteArray(aKey));
369 value = Clipperz.Base.serializeJSON(aValue);
370 data = new Clipperz.ByteArray(value);
371
372 deferredResult = new MochiKit.Async.Deferred()
373 deferredResult.addCallback(Clipperz.Crypto.AES_2.deferredEncrypt, key, data, aNonce);
374 deferredResult.addCallback(function(aResult) {
375 return aResult.toBase64String();
376 })
377 deferredResult.callback();
378
379 return deferredResult;
380 },
376 381
377 'decrypt': function(aKey, aValue) { 382 'decrypt': function(aKey, aValue) {
378 var result; 383 var result;
379 384
380 if (aValue != null) { 385 if (aValue != null) {
381 var key, value; 386 var key, value;
382 var decryptedData; 387 var decryptedData;
383 var decryptedValue; 388 var decryptedValue;
384 389
385 key = Clipperz.Crypto.SHA.sha_d256(new Clipperz.ByteArray(aKey)); 390 key = Clipperz.Crypto.SHA.sha_d256(new Clipperz.ByteArray(aKey));
386 value = new Clipperz.ByteArray().appendBase64String(aValue); 391 value = new Clipperz.ByteArray().appendBase64String(aValue);
387 392
388 decryptedData = Clipperz.Crypto.AES.decrypt(key, value); 393 decryptedData = Clipperz.Crypto.AES_2.decrypt(key, value);
389 394
390 value = decryptedData.asString(); 395 value = decryptedData.asString();
391/ *
392 value = value.replace(/":{l:"/g,'":{"label":"');
393 value = value.replace(/":{k:"/g,'":{"key":"');
394 value = value.replace(/":{n:"/g,'":{"notes":"');
395 value = value.replace(/":{r:"/g,'":{"record":"');
396 value = value.replace(/",l:"/g, '", "label":"');
397 value = value.replace(/",f:"/g, '", "favicon":"');
398* /
399 try { 396 try {
400 result = Clipperz.Base.evalJSON(value); 397 result = Clipperz.Base.evalJSON(value);
401 } catch (exception) { 398 } catch (exception) {
402 MochiKit.Logging.logError("Error while decrypting data"); 399 MochiKit.Logging.logError("Error while decrypting data");
403 throw Clipperz.Crypto.Base.exception.CorruptedMessage; 400 throw Clipperz.Crypto.Base.exception.CorruptedMessage;
404 } 401 }
405
406
407 } else { 402 } else {
408 result = null; 403 result = null;
409 } 404 }
410 405
411 return result; 406 return result;
412 }, 407 },
413 408
409 'deferredDecrypt': function(aKey, aValue) {
410 var deferredResult;
411
412 deferredResult = new MochiKit.Async.Deferred();
413 if (aValue != null) {
414 var key, value;
415 var decryptedData;
416 var decryptedValue;
417
418 key = Clipperz.Crypto.SHA.sha_d256(new Clipperz.ByteArray(aKey));
419 value = new Clipperz.ByteArray().appendBase64String(aValue);
420 deferredResult.addCallback(Clipperz.Crypto.AES_2.deferredDecrypt, key, value);
421 deferredResult.addCallback(MochiKit.Async.wait, 0.1);
422 deferredResult.addCallback(function(aResult) {
423 return aResult.asString();
424 });
425 deferredResult.addCallback(MochiKit.Async.wait, 0.1);
426 deferredResult.addCallback(Clipperz.Base.evalJSON);
427 deferredResult.addErrback(function(anError) {
428 MochiKit.Logging.logError("Error while decrypting data");
429 throw Clipperz.Crypto.Base.exception.CorruptedMessage;
430 })
431 } else {
432 deferredResult.addCallback(function() {
433 return null;
434 });
435 }
436 deferredResult.callback();
437
438 return deferredResult;
439 },
440
414 'hash': Clipperz.Crypto.SHA.sha_d256 441 'hash': Clipperz.Crypto.SHA.sha_d256
415 }, 442 },
416*/ 443
417 //##################################################################### 444 //#####################################################################
418 __syntaxFix__: "syntax fix" 445 __syntaxFix__: "syntax fix"
419 } 446 }
420 }, 447 },
421 448
422 //------------------------------------------------------------------------- 449 //-------------------------------------------------------------------------
423 450
424 'encrypt': function(aKey, aValue, aVersion) { 451 'encrypt': function(aKey, aValue, aVersion) {
425 return Clipperz.PM.Crypto.encryptingFunctions.versions[aVersion].encrypt(aKey, aValue); 452 return Clipperz.PM.Crypto.encryptingFunctions.versions[aVersion].encrypt(aKey, aValue);
426 }, 453 },
427 454
428 'deferredEncrypt': function(aKey, aValue, aVersion) { 455 'deferredEncrypt': function(aKey, aValue, aVersion) {
429 return Clipperz.PM.Crypto.encryptingFunctions.versions[aVersion].deferredEncrypt(aKey, aValue); 456 return Clipperz.PM.Crypto.encryptingFunctions.versions[aVersion].deferredEncrypt(aKey, aValue);
430 }, 457 },
431 458
432 'encryptWithCurrentVersion': function(aKey, aValue) { 459 'encryptWithCurrentVersion': function(aKey, aValue) {
diff --git a/frontend/beta/properties/beta.properties.json b/frontend/beta/properties/beta.properties.json
index bfa152d..7d34677 100644
--- a/frontend/beta/properties/beta.properties.json
+++ b/frontend/beta/properties/beta.properties.json
@@ -64,32 +64,33 @@
64 "YUI-extensions/CSS.js", 64 "YUI-extensions/CSS.js",
65 65
66 "JSON/json2.js", 66 "JSON/json2.js",
67 67
68 "Clipperz/ByteArray.js", 68 "Clipperz/ByteArray.js",
69 "Clipperz/Base.js", 69 "Clipperz/Base.js",
70 "Clipperz/CSVProcessor.js", 70 "Clipperz/CSVProcessor.js",
71 "Clipperz/KeePassExportProcessor.js", 71 "Clipperz/KeePassExportProcessor.js",
72 "Clipperz/Date.js", 72 "Clipperz/Date.js",
73 "Clipperz/DOM.js", 73 "Clipperz/DOM.js",
74 "Clipperz/Signal.js", 74 "Clipperz/Signal.js",
75 "Clipperz/Style.js", 75 "Clipperz/Style.js",
76 "Clipperz/Set.js", 76 "Clipperz/Set.js",
77 "Clipperz/NotificationCenter.js", 77 "Clipperz/NotificationCenter.js",
78 "Clipperz/Crypto/SHA.js", 78 "Clipperz/Crypto/SHA.js",
79 "Clipperz/Crypto/AES.js", 79 "Clipperz/Crypto/AES.js",
80 "Clipperz/Crypto/AES_2.js",
80 "Clipperz/Crypto/PRNG.js", 81 "Clipperz/Crypto/PRNG.js",
81 "Clipperz/Crypto/BigInt.js", 82 "Clipperz/Crypto/BigInt.js",
82 "Clipperz/Crypto/Base.js", 83 "Clipperz/Crypto/Base.js",
83 "Clipperz/Crypto/SRP.js", 84 "Clipperz/Crypto/SRP.js",
84 "Clipperz/Crypto/RSA.js", 85 "Clipperz/Crypto/RSA.js",
85 "Clipperz/PM/Strings/Strings_en-US.js", 86 "Clipperz/PM/Strings/Strings_en-US.js",
86 "Clipperz/PM/Strings/Strings_it-IT.js", 87 "Clipperz/PM/Strings/Strings_it-IT.js",
87 "Clipperz/PM/Strings/Strings_pt-BR.js", 88 "Clipperz/PM/Strings/Strings_pt-BR.js",
88 "Clipperz/PM/Strings/Strings_ja-JP.js", 89 "Clipperz/PM/Strings/Strings_ja-JP.js",
89 "Clipperz/PM/Strings/Strings_zh-CN.js", 90 "Clipperz/PM/Strings/Strings_zh-CN.js",
90 "Clipperz/PM/Strings/Strings_es-ES.js", 91 "Clipperz/PM/Strings/Strings_es-ES.js",
91 "Clipperz/PM/Strings/Strings_fr-FR.js", 92 "Clipperz/PM/Strings/Strings_fr-FR.js",
92 "Clipperz/PM/Strings/Strings_ru-RU.js", 93 "Clipperz/PM/Strings/Strings_ru-RU.js",
93 "Clipperz/PM/Strings.js", 94 "Clipperz/PM/Strings.js",
94 "Clipperz/PM/Strings/MessagePanelConfigurations.js", 95 "Clipperz/PM/Strings/MessagePanelConfigurations.js",
95 "Clipperz/PM/Date.js", 96 "Clipperz/PM/Date.js",