Subversion Repositories Applications.papyrus

Compare Revisions

Ignore whitespace Rev 2149 → Rev 2150

/trunk/api/js/dojo1.0/dojox/_sql/LICENSE
New file
0,0 → 1,9
License Disclaimer:
 
All contents of this directory are Copyright (c) the Dojo Foundation, with the
following exceptions:
-------------------------------------------------------------------------------
 
_crypto.js - internally uses AES algorithm
* AES algorithm copyright Chris Veness (CLA signed and permission given to use code under BSD license)
Taken from http://www.movable-type.co.uk/scripts/aes.html
/trunk/api/js/dojo1.0/dojox/_sql/common.js
New file
0,0 → 1,535
if(!dojo._hasResource["dojox._sql.common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox._sql.common"] = true;
dojo.provide("dojox._sql.common");
 
dojo.require("dojox._sql._crypto");
 
// summary:
// Executes a SQL expression.
// description:
// There are four ways to call this:
// 1) Straight SQL: dojox.sql("SELECT * FROM FOOBAR");
// 2) SQL with parameters: dojox.sql("INSERT INTO FOOBAR VALUES (?)", someParam)
// 3) Encrypting particular values:
// dojox.sql("INSERT INTO FOOBAR VALUES (ENCRYPT(?))", someParam, "somePassword", callback)
// 4) Decrypting particular values:
// dojox.sql("SELECT DECRYPT(SOMECOL1), DECRYPT(SOMECOL2) FROM
// FOOBAR WHERE SOMECOL3 = ?", someParam,
// "somePassword", callback)
//
// For encryption and decryption the last two values should be the the password for
// encryption/decryption, and the callback function that gets the result set.
//
// Note: We only support ENCRYPT(?) statements, and
// and DECRYPT(*) statements for now -- you can not have a literal string
// inside of these, such as ENCRYPT('foobar')
//
// Note: If you have multiple columns to encrypt and decrypt, you can use the following
// convenience form to not have to type ENCRYPT(?)/DECRYPT(*) many times:
//
// dojox.sql("INSERT INTO FOOBAR VALUES (ENCRYPT(?, ?, ?))",
// someParam1, someParam2, someParam3,
// "somePassword", callback)
//
// dojox.sql("SELECT DECRYPT(SOMECOL1, SOMECOL2) FROM
// FOOBAR WHERE SOMECOL3 = ?", someParam,
// "somePassword", callback)
dojox.sql = new Function("return dojox.sql._exec(arguments);");
 
dojo.mixin(dojox.sql, {
dbName: null,
// summary:
// If true, then we print out any SQL that is executed
// to the debug window
debug: (dojo.exists("dojox.sql.debug")?dojox.sql.debug:false),
 
open: function(dbName){
if(this._dbOpen && (!dbName || dbName == this.dbName)){
return;
}
if(!this.dbName){
this.dbName = "dot_store_"
+ window.location.href.replace(/[^0-9A-Za-z_]/g, "_");
//console.debug("Using Google Gears database " + this.dbName);
}
if(!dbName){
dbName = this.dbName;
}
try{
this._initDb();
this.db.open(dbName);
this._dbOpen = true;
}catch(exp){
throw exp.message||exp;
}
},
 
close: function(dbName){
// on Internet Explorer, Google Gears throws an exception
// "Object not a collection", when we try to close the
// database -- just don't close it on this platform
// since we are running into a Gears bug; the Gears team
// said it's ok to not close a database connection
if(dojo.isIE){ return; }
if(!this._dbOpen && (!dbName || dbName == this.dbName)){
return;
}
if(!dbName){
dbName = this.dbName;
}
try{
this.db.close(dbName);
this._dbOpen = false;
}catch(exp){
throw exp.message||exp;
}
},
_exec: function(params){
try{
// get the Gears Database object
this._initDb();
// see if we need to open the db; if programmer
// manually called dojox.sql.open() let them handle
// it; otherwise we open and close automatically on
// each SQL execution
if(!this._dbOpen){
this.open();
this._autoClose = true;
}
// determine our parameters
var sql = null;
var callback = null;
var password = null;
 
var args = dojo._toArray(params);
 
sql = args.splice(0, 1)[0];
 
// does this SQL statement use the ENCRYPT or DECRYPT
// keywords? if so, extract our callback and crypto
// password
if(this._needsEncrypt(sql) || this._needsDecrypt(sql)){
callback = args.splice(args.length - 1, 1)[0];
password = args.splice(args.length - 1, 1)[0];
}
 
// 'args' now just has the SQL parameters
 
// print out debug SQL output if the developer wants that
if(this.debug){
this._printDebugSQL(sql, args);
}
 
// handle SQL that needs encryption/decryption differently
// do we have an ENCRYPT SQL statement? if so, handle that first
if(this._needsEncrypt(sql)){
var crypto = new dojox.sql._SQLCrypto("encrypt", sql,
password, args,
callback);
return; // encrypted results will arrive asynchronously
}else if(this._needsDecrypt(sql)){ // otherwise we have a DECRYPT statement
var crypto = new dojox.sql._SQLCrypto("decrypt", sql,
password, args,
callback);
return; // decrypted results will arrive asynchronously
}
 
// execute the SQL and get the results
var rs = this.db.execute(sql, args);
// Gears ResultSet object's are ugly -- normalize
// these into something JavaScript programmers know
// how to work with, basically an array of
// JavaScript objects where each property name is
// simply the field name for a column of data
rs = this._normalizeResults(rs);
if(this._autoClose){
this.close();
}
return rs;
}catch(exp){
exp = exp.message||exp;
console.debug("SQL Exception: " + exp);
if(this._autoClose){
try{
this.close();
}catch(e){
console.debug("Error closing database: "
+ e.message||e);
}
}
throw exp;
}
},
 
_initDb: function(){
if(!this.db){
try{
this.db = google.gears.factory.create('beta.database', '1.0');
}catch(exp){
dojo.setObject("google.gears.denied", true);
dojox.off.onFrameworkEvent("coreOperationFailed");
throw "Google Gears must be allowed to run";
}
}
},
 
_printDebugSQL: function(sql, args){
var msg = "dojox.sql(\"" + sql + "\"";
for(var i = 0; i < args.length; i++){
if(typeof args[i] == "string"){
msg += ", \"" + args[i] + "\"";
}else{
msg += ", " + args[i];
}
}
msg += ")";
console.debug(msg);
},
 
_normalizeResults: function(rs){
var results = [];
if(!rs){ return []; }
while(rs.isValidRow()){
var row = {};
for(var i = 0; i < rs.fieldCount(); i++){
var fieldName = rs.fieldName(i);
var fieldValue = rs.field(i);
row[fieldName] = fieldValue;
}
results.push(row);
rs.next();
}
rs.close();
return results;
},
 
_needsEncrypt: function(sql){
return /encrypt\([^\)]*\)/i.test(sql);
},
 
_needsDecrypt: function(sql){
return /decrypt\([^\)]*\)/i.test(sql);
}
});
 
// summary:
// A private class encapsulating any cryptography that must be done
// on a SQL statement. We instantiate this class and have it hold
// it's state so that we can potentially have several encryption
// operations happening at the same time by different SQL statements.
dojo.declare("dojox.sql._SQLCrypto", null, {
constructor: function(action, sql, password, args, callback){
if(action == "encrypt"){
this._execEncryptSQL(sql, password, args, callback);
}else{
this._execDecryptSQL(sql, password, args, callback);
}
},
_execEncryptSQL: function(sql, password, args, callback){
// strip the ENCRYPT/DECRYPT keywords from the SQL
var strippedSQL = this._stripCryptoSQL(sql);
// determine what arguments need encryption
var encryptColumns = this._flagEncryptedArgs(sql, args);
// asynchronously encrypt each argument that needs it
var self = this;
this._encrypt(strippedSQL, password, args, encryptColumns, function(finalArgs){
// execute the SQL
var error = false;
var resultSet = [];
var exp = null;
try{
resultSet = dojox.sql.db.execute(strippedSQL, finalArgs);
}catch(execError){
error = true;
exp = execError.message||execError;
}
// was there an error during SQL execution?
if(exp != null){
if(dojox.sql._autoClose){
try{ dojox.sql.close(); }catch(e){}
}
callback(null, true, exp.toString());
return;
}
// normalize SQL results into a JavaScript object
// we can work with
resultSet = dojox.sql._normalizeResults(resultSet);
if(dojox.sql._autoClose){
dojox.sql.close();
}
// are any decryptions necessary on the result set?
if(dojox.sql._needsDecrypt(sql)){
// determine which of the result set columns needs decryption
var needsDecrypt = self._determineDecryptedColumns(sql);
 
// now decrypt columns asynchronously
// decrypt columns that need it
self._decrypt(resultSet, needsDecrypt, password, function(finalResultSet){
callback(finalResultSet, false, null);
});
}else{
callback(resultSet, false, null);
}
});
},
 
_execDecryptSQL: function(sql, password, args, callback){
// strip the ENCRYPT/DECRYPT keywords from the SQL
var strippedSQL = this._stripCryptoSQL(sql);
// determine which columns needs decryption; this either
// returns the value *, which means all result set columns will
// be decrypted, or it will return the column names that need
// decryption set on a hashtable so we can quickly test a given
// column name; the key is the column name that needs
// decryption and the value is 'true' (i.e. needsDecrypt["someColumn"]
// would return 'true' if it needs decryption, and would be 'undefined'
// or false otherwise)
var needsDecrypt = this._determineDecryptedColumns(sql);
// execute the SQL
var error = false;
var resultSet = [];
var exp = null;
try{
resultSet = dojox.sql.db.execute(strippedSQL, args);
}catch(execError){
error = true;
exp = execError.message||execError;
}
// was there an error during SQL execution?
if(exp != null){
if(dojox.sql._autoClose){
try{ dojox.sql.close(); }catch(e){}
}
callback(resultSet, true, exp.toString());
return;
}
// normalize SQL results into a JavaScript object
// we can work with
resultSet = dojox.sql._normalizeResults(resultSet);
if(dojox.sql._autoClose){
dojox.sql.close();
}
// decrypt columns that need it
this._decrypt(resultSet, needsDecrypt, password, function(finalResultSet){
callback(finalResultSet, false, null);
});
},
 
_encrypt: function(sql, password, args, encryptColumns, callback){
//console.debug("_encrypt, sql="+sql+", password="+password+", encryptColumns="+encryptColumns+", args="+args);
this._totalCrypto = 0;
this._finishedCrypto = 0;
this._finishedSpawningCrypto = false;
this._finalArgs = args;
for(var i = 0; i < args.length; i++){
if(encryptColumns[i]){
// we have an encrypt() keyword -- get just the value inside
// the encrypt() parantheses -- for now this must be a ?
var sqlParam = args[i];
var paramIndex = i;
// update the total number of encryptions we know must be done asynchronously
this._totalCrypto++;
// FIXME: This currently uses DES as a proof-of-concept since the
// DES code used is quite fast and was easy to work with. Modify dojox.sql
// to be able to specify a different encryption provider through a
// a SQL-like syntax, such as dojox.sql("SET ENCRYPTION BLOWFISH"),
// and modify the dojox.crypto.Blowfish code to be able to work using
// a Google Gears Worker Pool
// do the actual encryption now, asychronously on a Gears worker thread
dojox._sql._crypto.encrypt(sqlParam, password, dojo.hitch(this, function(results){
// set the new encrypted value
this._finalArgs[paramIndex] = results;
this._finishedCrypto++;
// are we done with all encryption?
if(this._finishedCrypto >= this._totalCrypto
&& this._finishedSpawningCrypto){
callback(this._finalArgs);
}
}));
}
}
this._finishedSpawningCrypto = true;
},
 
_decrypt: function(resultSet, needsDecrypt, password, callback){
//console.debug("decrypt, resultSet="+resultSet+", needsDecrypt="+needsDecrypt+", password="+password);
this._totalCrypto = 0;
this._finishedCrypto = 0;
this._finishedSpawningCrypto = false;
this._finalResultSet = resultSet;
for(var i = 0; i < resultSet.length; i++){
var row = resultSet[i];
// go through each of the column names in row,
// seeing if they need decryption
for(var columnName in row){
if(needsDecrypt == "*" || needsDecrypt[columnName]){
this._totalCrypto++;
var columnValue = row[columnName];
// forming a closure here can cause issues, with values not cleanly
// saved on Firefox/Mac OS X for some of the values above that
// are needed in the callback below; call a subroutine that will form
// a closure inside of itself instead
this._decryptSingleColumn(columnName, columnValue, password, i,
function(finalResultSet){
callback(finalResultSet);
});
}
}
}
this._finishedSpawningCrypto = true;
},
 
_stripCryptoSQL: function(sql){
// replace all DECRYPT(*) occurrences with a *
sql = sql.replace(/DECRYPT\(\*\)/ig, "*");
// match any ENCRYPT(?, ?, ?, etc) occurrences,
// then replace with just the question marks in the
// middle
var matches = sql.match(/ENCRYPT\([^\)]*\)/ig);
if(matches != null){
for(var i = 0; i < matches.length; i++){
var encryptStatement = matches[i];
var encryptValue = encryptStatement.match(/ENCRYPT\(([^\)]*)\)/i)[1];
sql = sql.replace(encryptStatement, encryptValue);
}
}
// match any DECRYPT(COL1, COL2, etc) occurrences,
// then replace with just the column names
// in the middle
matches = sql.match(/DECRYPT\([^\)]*\)/ig);
if(matches != null){
for(var i = 0; i < matches.length; i++){
var decryptStatement = matches[i];
var decryptValue = decryptStatement.match(/DECRYPT\(([^\)]*)\)/i)[1];
sql = sql.replace(decryptStatement, decryptValue);
}
}
return sql;
},
 
_flagEncryptedArgs: function(sql, args){
// capture literal strings that have question marks in them,
// and also capture question marks that stand alone
var tester = new RegExp(/([\"][^\"]*\?[^\"]*[\"])|([\'][^\']*\?[^\']*[\'])|(\?)/ig);
var matches;
var currentParam = 0;
var results = [];
while((matches = tester.exec(sql)) != null){
var currentMatch = RegExp.lastMatch+"";
 
// are we a literal string? then ignore it
if(/^[\"\']/.test(currentMatch)){
continue;
}
 
// do we have an encrypt keyword to our left?
var needsEncrypt = false;
if(/ENCRYPT\([^\)]*$/i.test(RegExp.leftContext)){
needsEncrypt = true;
}
 
// set the encrypted flag
results[currentParam] = needsEncrypt;
 
currentParam++;
}
return results;
},
 
_determineDecryptedColumns: function(sql){
var results = {};
 
if(/DECRYPT\(\*\)/i.test(sql)){
results = "*";
}else{
var tester = /DECRYPT\((?:\s*\w*\s*\,?)*\)/ig;
var matches;
while(matches = tester.exec(sql)){
var lastMatch = new String(RegExp.lastMatch);
var columnNames = lastMatch.replace(/DECRYPT\(/i, "");
columnNames = columnNames.replace(/\)/, "");
columnNames = columnNames.split(/\s*,\s*/);
dojo.forEach(columnNames, function(column){
if(/\s*\w* AS (\w*)/i.test(column)){
column = column.match(/\s*\w* AS (\w*)/i)[1];
}
results[column] = true;
});
}
}
 
return results;
},
 
_decryptSingleColumn: function(columnName, columnValue, password, currentRowIndex,
callback){
//console.debug("decryptSingleColumn, columnName="+columnName+", columnValue="+columnValue+", currentRowIndex="+currentRowIndex)
dojox._sql._crypto.decrypt(columnValue, password, dojo.hitch(this, function(results){
// set the new decrypted value
this._finalResultSet[currentRowIndex][columnName] = results;
this._finishedCrypto++;
// are we done with all encryption?
if(this._finishedCrypto >= this._totalCrypto
&& this._finishedSpawningCrypto){
//console.debug("done with all decrypts");
callback(this._finalResultSet);
}
}));
}
});
 
}
/trunk/api/js/dojo1.0/dojox/_sql/demos/customers/customers.html
New file
0,0 → 1,292
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 
<html>
<head>
<script type="text/javascript"
src="../../../../dojo/dojo.js" djConfig="isDebug: false"></script>
<script type="text/javascript" src="../../../../dojox/off/offline.js"></script>
<style type="text/css">
body{
padding: 2em;
}
#dataTable{
margin-top: 2em;
}
button{
margin-left: 1em;
}
th, tr, td{
text-align: left;
}
table{
text-align: center;
clear: both;
}
#cryptoContainer{
float: left;
width: 60%;
}
#numRowsContainer{
float: right;
width: 40%;
}
#numRowsContainer input{
margin-left: 1.5em;
width: 5em;
}
.table-columns{
font-weight: bold;
}
</style>
<script>
dojo.require("dojox.sql");
dojo.connect(window, "onload", function(){
// draw our customer table on the screen
createTable();
// create our customer table in the database
dojox.sql("DROP TABLE IF EXISTS CUSTOMERS");
dojox.sql("CREATE TABLE CUSTOMERS ("
+ "last_name TEXT, "
+ "first_name TEXT, "
+ "social_security TEXT"
+ ")"
);
});
function createTable(){
// get number of rows to create
var NUM_ROWS = document.getElementById("numRows").value;
if(!NUM_ROWS){
alert("Please enter the number of "
+ "customer rows the table should have");
return;
}
var table = document.getElementById("dataTable");
if(table){
table.parentNode.removeChild(table);
}
table = document.createElement("table");
table.setAttribute("id", "dataTable");
table.setAttribute("border", 1);
// if we don't use IE's craptacular proprietary table methods
// we get strange display glitches
var tr = (dojo.isIE) ? table.insertRow() : document.createElement("tr");
tr.className = "table-columns";
var th = (dojo.isIE) ? tr.insertCell() : document.createElement("th");
th.appendChild(document.createTextNode("Last Name"));
if(!dojo.isIE){
tr.appendChild(th);
}
th = (dojo.isIE) ? tr.insertCell() : document.createElement("th");
th.appendChild(document.createTextNode("First Name"));
if(!dojo.isIE){
tr.appendChild(th);
}
th = (dojo.isIE) ? tr.insertCell() : document.createElement("th");
th.appendChild(document.createTextNode("Social Security"));
if(!dojo.isIE){
tr.appendChild(th);
table.appendChild(tr);
}
for(var i = 1; i <= NUM_ROWS; i++){
tr = (dojo.isIE) ? table.insertRow() : document.createElement("tr");
tr.className = "data-item";
var elem = (dojo.isIE) ? tr.insertCell() : document.createElement("td");
elem.className = "last-name";
var lastName = "Doe" + i;
elem.appendChild(document.createTextNode(lastName));
if(!dojo.isIE){
tr.appendChild(elem);
}
elem = (dojo.isIE) ? tr.insertCell() : document.createElement("td");
elem.className = "first-name";
var firstName = "John" + i;
elem.appendChild(document.createTextNode(firstName));
if(!dojo.isIE){
tr.appendChild(elem);
}
elem = elem = (dojo.isIE) ? tr.insertCell() : document.createElement("td");
elem.className = "social-security";
var ss = 513121500 + i;
ss = new String(ss);
ss = ss.slice(0, 3) + "-" + ss.slice(3, 5) + "-" + ss.slice(5);
elem.appendChild(document.createTextNode(ss));
if(!dojo.isIE){
tr.appendChild(elem);
table.appendChild(tr);
}
}
document.body.appendChild(table);
// reset button state
dojo.byId("encrypt").disabled = false;
dojo.byId("decrypt").disabled = true;
}
function readTable(){
var data = [];
var rows = dojo.query(".data-item");
dojo.forEach(rows, function(row){
var td = row.getElementsByTagName("td");
var lastName = td[0].childNodes[0].nodeValue;
var firstName = td[1].childNodes[0].nodeValue;
var ssNumber = td[2].childNodes[0].nodeValue;
 
data.push({lastName: lastName, firstName: firstName, ssNumber: ssNumber,
toString: function(){
return "{lastName: " + lastName
+ ", firstName: " + firstName
+ ", ssNumber: " + ssNumber
+ "}";
}});
});
return data;
}
function setData(data){
var rows = document.getElementsByTagName("tr");
for(var i = 1; i < rows.length; i++){
var customer = data[i - 1];
var td = rows[i].getElementsByTagName("td");
td[2].childNodes[0].nodeValue = customer.social_security;
}
}
function encrypt(){
// disable our buttons
dojo.byId("encrypt").disabled = true;
dojo.byId("decrypt").disabled = true;
var data = readTable();
var password = document.getElementById("password").value;
// delete any old data
dojox.sql("DELETE FROM CUSTOMERS");
// insert new data
insertCustomers(data, 0, password);
}
function insertCustomers(data, i, password){
var nextIndex = i + 1;
if(i >= data.length){
var savedRows = dojox.sql("SELECT * FROM CUSTOMERS");
setData(savedRows);
return;
}
dojox.sql("INSERT INTO CUSTOMERS VALUES (?, ?, ENCRYPT(?))",
data[i].lastName, data[i].firstName,
data[i].ssNumber,
password,
function(results, error, errorMsg){
// enable our buttons
dojo.byId("encrypt").disabled = true;
dojo.byId("decrypt").disabled = false;
if(error == true){
alert(errorMsg);
return;
}
insertCustomers(data, nextIndex, password);
}
);
}
function decrypt(){
// disable our buttons
dojo.byId("encrypt").disabled = true;
dojo.byId("decrypt").disabled = true;
var password = document.getElementById("password").value;
dojox.sql("SELECT last_name, first_name, DECRYPT(social_security) FROM CUSTOMERS",
password,
function(results, error, errorMsg){
// enable our buttons
dojo.byId("encrypt").disabled = false;
dojo.byId("decrypt").disabled = true;
if(error == true){
alert(errorMsg);
return;
}
setData(results);
}
);
}
</script>
</head>
<body>
<h1>Dojo SQL Cryptography</h1>
<h2>Instructions</h2>
<p>This demo shows Dojo Offline's SQL encryption technologies. In the table below, we have a
sample SQL table that has three columns of data: a last name, a first name, and
a social security number. We don't want to store the social security numbers
in the clear, just in case they are downloaded for offline use to a laptop and the
laptop is stolen.</p>
<p>To use this demo, enter a password and press the ENCRYPT button to see the Social Security column encrypt. Enter
the same password and press DECRYPT to see it decrypt. If you enter an incorrect password and
press DECRYPT, the Social Security column will remain encrypted and only show gibberish.</p>
<p>Under the covers we use 256-bit AES encryption and your password to derive the crypto key; we use
a facility in Google Gears to do the cryptography in such a way that the browser does not lock up
during processing. Dojo Offline ties this cryptography into Dojo SQL, providing convenient ENCRYPT()
and DECRYPT() SQL keywords you can use to easily have this functionality in your
own offline applications. To learn how you can use this feature
<a href="http://docs.google.com/View?docid=dhkhksk4_8gdp9gr#crypto" target="_blank">see here</a>.</p>
<div id="cryptoContainer">
<label for="password">
Password:
</label>
<input type="input" name="password" id="password" value="sample_password">
<button id="encrypt" onclick="window.setTimeout(encrypt, 1)">Encrypt</button>
<button id="decrypt" onclick="window.setTimeout(decrypt, 1)" disabled="true">Decrypt</button>
</div>
<div id="numRowsContainer">
<label for="numRows">
Number of Customer Rows in Table:
</label>
<input id="numRows" type="input" value="30">
<button onclick="createTable()">Update</button>
</div>
</body>
</html>
/trunk/api/js/dojo1.0/dojox/_sql/_crypto.js
New file
0,0 → 1,443
if(!dojo._hasResource["dojox._sql._crypto"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox._sql._crypto"] = true;
// Taken from http://www.movable-type.co.uk/scripts/aes.html by
// Chris Veness (CLA signed); adapted for Dojo and Google Gears Worker Pool
// by Brad Neuberg, bkn3@columbia.edu
 
dojo.provide("dojox._sql._crypto");
 
dojo.mixin(dojox._sql._crypto,{
// _POOL_SIZE:
// Size of worker pool to create to help with crypto
_POOL_SIZE: 100,
encrypt: function(plaintext, password, callback){
// summary:
// Use Corrected Block TEA to encrypt plaintext using password
// (note plaintext & password must be strings not string objects).
// Results will be returned to the 'callback' asychronously.
this._initWorkerPool();
var msg ={plaintext: plaintext, password: password};
msg = dojo.toJson(msg);
msg = "encr:" + String(msg);
this._assignWork(msg, callback);
},
 
decrypt: function(ciphertext, password, callback){
// summary:
// Use Corrected Block TEA to decrypt ciphertext using password
// (note ciphertext & password must be strings not string objects).
// Results will be returned to the 'callback' asychronously.
this._initWorkerPool();
var msg ={ciphertext: ciphertext, password: password};
msg = dojo.toJson(msg);
msg = "decr:" + String(msg);
this._assignWork(msg, callback);
},
_initWorkerPool: function(){
// bugs in Google Gears prevents us from dynamically creating
// and destroying workers as we need them -- the worker
// pool functionality stops working after a number of crypto
// cycles (probably related to a memory leak in Google Gears).
// this is too bad, since it results in much simpler code.
// instead, we have to create a pool of workers and reuse them. we
// keep a stack of 'unemployed' Worker IDs that are currently not working.
// if a work request comes in, we pop off the 'unemployed' stack
// and put them to work, storing them in an 'employed' hashtable,
// keyed by their Worker ID with the value being the callback function
// that wants the result. when an employed worker is done, we get
// a message in our 'manager' which adds this worker back to the
// unemployed stack and routes the result to the callback that
// wanted it. if all the workers were employed in the past but
// more work needed to be done (i.e. it's a tight labor pool ;)
// then the work messages are pushed onto
// a 'handleMessage' queue as an object tuple{msg: msg, callback: callback}
if(!this._manager){
try{
this._manager = google.gears.factory.create("beta.workerpool", "1.0");
this._unemployed = [];
this._employed ={};
this._handleMessage = [];
var self = this;
this._manager.onmessage = function(msg, sender){
// get the callback necessary to serve this result
var callback = self._employed["_" + sender];
// make this worker unemployed
self._employed["_" + sender] = undefined;
self._unemployed.push("_" + sender);
// see if we need to assign new work
// that was queued up needing to be done
if(self._handleMessage.length){
var handleMe = self._handleMessage.shift();
self._assignWork(handleMe.msg, handleMe.callback);
}
// return results
callback(msg);
}
var workerInit = "function _workerInit(){"
+ "gearsWorkerPool.onmessage = "
+ String(this._workerHandler)
+ ";"
+ "}";
var code = workerInit + " _workerInit();";
// create our worker pool
for(var i = 0; i < this._POOL_SIZE; i++){
this._unemployed.push("_" + this._manager.createWorker(code));
}
}catch(exp){
throw exp.message||exp;
}
}
},
 
_assignWork: function(msg, callback){
// can we immediately assign this work?
if(!this._handleMessage.length && this._unemployed.length){
// get an unemployed worker
var workerID = this._unemployed.shift().substring(1); // remove _
// list this worker as employed
this._employed["_" + workerID] = callback;
// do the worke
this._manager.sendMessage(msg, workerID);
}else{
// we have to queue it up
this._handleMessage ={msg: msg, callback: callback};
}
},
 
_workerHandler: function(msg, sender){
/* Begin AES Implementation */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [§5.1.1]
var Sbox = [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];
 
// Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
var Rcon = [ [0x00, 0x00, 0x00, 0x00],
[0x01, 0x00, 0x00, 0x00],
[0x02, 0x00, 0x00, 0x00],
[0x04, 0x00, 0x00, 0x00],
[0x08, 0x00, 0x00, 0x00],
[0x10, 0x00, 0x00, 0x00],
[0x20, 0x00, 0x00, 0x00],
[0x40, 0x00, 0x00, 0x00],
[0x80, 0x00, 0x00, 0x00],
[0x1b, 0x00, 0x00, 0x00],
[0x36, 0x00, 0x00, 0x00] ];
 
/*
* AES Cipher function: encrypt 'input' with Rijndael algorithm
*
* takes byte-array 'input' (16 bytes)
* 2D byte-array key schedule 'w' (Nr+1 x Nb bytes)
*
* applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
*
* returns byte-array encrypted value (16 bytes)
*/
function Cipher(input, w) { // main Cipher function [§5.1]
var Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
 
var state = [[],[],[],[]]; // initialise 4xNb byte-array 'state' with input [§3.4]
for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i];
 
state = AddRoundKey(state, w, 0, Nb);
 
for (var round=1; round<Nr; round++) {
state = SubBytes(state, Nb);
state = ShiftRows(state, Nb);
state = MixColumns(state, Nb);
state = AddRoundKey(state, w, round, Nb);
}
 
state = SubBytes(state, Nb);
state = ShiftRows(state, Nb);
state = AddRoundKey(state, w, Nr, Nb);
 
var output = new Array(4*Nb); // convert state to 1-d array before returning [§3.4]
for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)];
return output;
}
 
 
function SubBytes(s, Nb) { // apply SBox to state S [§5.1.1]
for (var r=0; r<4; r++) {
for (var c=0; c<Nb; c++) s[r][c] = Sbox[s[r][c]];
}
return s;
}
 
 
function ShiftRows(s, Nb) { // shift row r of state S left by r bytes [§5.1.2]
var t = new Array(4);
for (var r=1; r<4; r++) {
for (var c=0; c<4; c++) t[c] = s[r][(c+r)%Nb]; // shift into temp copy
for (var c=0; c<4; c++) s[r][c] = t[c]; // and copy back
} // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
return s; // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
}
 
 
function MixColumns(s, Nb) { // combine bytes of each col of state S [§5.1.3]
for (var c=0; c<4; c++) {
var a = new Array(4); // 'a' is a copy of the current column from 's'
var b = new Array(4); // 'b' is a•{02} in GF(2^8)
for (var i=0; i<4; i++) {
a[i] = s[i][c];
b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;
}
// a[n] ^ b[n] is a•{03} in GF(2^8)
s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
}
return s;
}
 
 
function AddRoundKey(state, w, rnd, Nb) { // xor Round Key into state S [§5.1.4]
for (var r=0; r<4; r++) {
for (var c=0; c<Nb; c++) state[r][c] ^= w[rnd*4+c][r];
}
return state;
}
 
 
function KeyExpansion(key) { // generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2]
var Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
var Nk = key.length/4 // key length (in words): 4/6/8 for 128/192/256-bit keys
var Nr = Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys
 
var w = new Array(Nb*(Nr+1));
var temp = new Array(4);
 
for (var i=0; i<Nk; i++) {
var r = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]];
w[i] = r;
}
 
for (var i=Nk; i<(Nb*(Nr+1)); i++) {
w[i] = new Array(4);
for (var t=0; t<4; t++) temp[t] = w[i-1][t];
if (i % Nk == 0) {
temp = SubWord(RotWord(temp));
for (var t=0; t<4; t++) temp[t] ^= Rcon[i/Nk][t];
} else if (Nk > 6 && i%Nk == 4) {
temp = SubWord(temp);
}
for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t];
}
 
return w;
}
 
function SubWord(w) { // apply SBox to 4-byte word w
for (var i=0; i<4; i++) w[i] = Sbox[w[i]];
return w;
}
 
function RotWord(w) { // rotate 4-byte word w left by one byte
w[4] = w[0];
for (var i=0; i<4; i++) w[i] = w[i+1];
return w;
}
 
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
/*
* Use AES to encrypt 'plaintext' with 'password' using 'nBits' key, in 'Counter' mode of operation
* - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
* for each block
* - outputblock = cipher(counter, key)
* - cipherblock = plaintext xor outputblock
*/
function AESEncryptCtr(plaintext, password, nBits) {
if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys
// for this example script, generate the key by applying Cipher to 1st 16/24/32 chars of password;
// for real-world applications, a more secure approach would be to hash the password e.g. with SHA-1
var nBytes = nBits/8; // no bytes in key
var pwBytes = new Array(nBytes);
for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;
 
var key = Cipher(pwBytes, KeyExpansion(pwBytes));
 
key = key.concat(key.slice(0, nBytes-16)); // key is now 16/24/32 bytes long
 
// initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in 1st 8 bytes,
// block counter in 2nd 8 bytes
var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
var counterBlock = new Array(blockSize); // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
var nonce = (new Date()).getTime(); // milliseconds since 1-Jan-1970
 
// encode nonce in two stages to cater for JavaScript 32-bit limit on bitwise ops
for (var i=0; i<4; i++) counterBlock[i] = (nonce >>> i*8) & 0xff;
for (var i=0; i<4; i++) counterBlock[i+4] = (nonce/0x100000000 >>> i*8) & 0xff;
 
// generate key schedule - an expansion of the key into distinct Key Rounds for each round
var keySchedule = KeyExpansion(key);
 
var blockCount = Math.ceil(plaintext.length/blockSize);
var ciphertext = new Array(blockCount); // ciphertext as array of strings
for (var b=0; b<blockCount; b++) {
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
// again done in two stages for 32-bit ops
for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff;
for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8)
 
var cipherCntr = Cipher(counterBlock, keySchedule); // -- encrypt counter block --
// calculate length of final block:
var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1;
 
var ct = '';
for (var i=0; i<blockLength; i++) { // -- xor plaintext with ciphered counter byte-by-byte --
var plaintextByte = plaintext.charCodeAt(b*blockSize+i);
var cipherByte = plaintextByte ^ cipherCntr[i];
ct += String.fromCharCode(cipherByte);
}
// ct is now ciphertext for this block
 
ciphertext[b] = escCtrlChars(ct); // escape troublesome characters in ciphertext
}
 
// convert the nonce to a string to go on the front of the ciphertext
var ctrTxt = '';
for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]);
ctrTxt = escCtrlChars(ctrTxt);
 
// use '-' to separate blocks, use Array.join to concatenate arrays of strings for efficiency
return ctrTxt + '-' + ciphertext.join('-');
}
 
 
/*
* Use AES to decrypt 'ciphertext' with 'password' using 'nBits' key, in Counter mode of operation
*
* for each block
* - outputblock = cipher(counter, key)
* - cipherblock = plaintext xor outputblock
*/
function AESDecryptCtr(ciphertext, password, nBits) {
if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys
 
var nBytes = nBits/8; // no bytes in key
var pwBytes = new Array(nBytes);
for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;
var pwKeySchedule = KeyExpansion(pwBytes);
var key = Cipher(pwBytes, pwKeySchedule);
key = key.concat(key.slice(0, nBytes-16)); // key is now 16/24/32 bytes long
 
var keySchedule = KeyExpansion(key);
 
ciphertext = ciphertext.split('-'); // split ciphertext into array of block-length strings
 
// recover nonce from 1st element of ciphertext
var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
var counterBlock = new Array(blockSize);
var ctrTxt = unescCtrlChars(ciphertext[0]);
for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i);
 
var plaintext = new Array(ciphertext.length-1);
 
for (var b=1; b<ciphertext.length; b++) {
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
for (var c=0; c<4; c++) counterBlock[15-c] = ((b-1) >>> c*8) & 0xff;
for (var c=0; c<4; c++) counterBlock[15-c-4] = ((b/0x100000000-1) >>> c*8) & 0xff;
 
var cipherCntr = Cipher(counterBlock, keySchedule); // encrypt counter block
 
ciphertext[b] = unescCtrlChars(ciphertext[b]);
 
var pt = '';
for (var i=0; i<ciphertext[b].length; i++) {
// -- xor plaintext with ciphered counter byte-by-byte --
var ciphertextByte = ciphertext[b].charCodeAt(i);
var plaintextByte = ciphertextByte ^ cipherCntr[i];
pt += String.fromCharCode(plaintextByte);
}
// pt is now plaintext for this block
 
plaintext[b-1] = pt; // b-1 'cos no initial nonce block in plaintext
}
 
return plaintext.join('');
}
 
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
function escCtrlChars(str) { // escape control chars which might cause problems handling ciphertext
return str.replace(/[\0\t\n\v\f\r\xa0!-]/g, function(c) { return '!' + c.charCodeAt(0) + '!'; });
} // \xa0 to cater for bug in Firefox; include '-' to leave it free for use as a block marker
 
function unescCtrlChars(str) { // unescape potentially problematic control characters
return str.replace(/!\d\d?\d?!/g, function(c) { return String.fromCharCode(c.slice(1,-1)); });
}
 
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
function encrypt(plaintext, password){
return AESEncryptCtr(plaintext, password, 256);
}
 
function decrypt(ciphertext, password){
return AESDecryptCtr(ciphertext, password, 256);
}
/* End AES Implementation */
var cmd = msg.substr(0,4);
var arg = msg.substr(5);
if(cmd == "encr"){
arg = eval("(" + arg + ")");
var plaintext = arg.plaintext;
var password = arg.password;
var results = encrypt(plaintext, password);
gearsWorkerPool.sendMessage(String(results), sender);
}else if(cmd == "decr"){
arg = eval("(" + arg + ")");
var ciphertext = arg.ciphertext;
var password = arg.password;
var results = decrypt(ciphertext, password);
gearsWorkerPool.sendMessage(String(results), sender);
}
}
});
 
}