Skip to content

Instantly share code, notes, and snippets.

@pftg
Created June 16, 2011 21:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pftg/1030279 to your computer and use it in GitHub Desktop.
Save pftg/1030279 to your computer and use it in GitHub Desktop.
var setCryptData = function (data) {
window.cryptData = data.crypto_key;
};
var submitFormWithCrypto = function (form, callback) {
$.getScript('/profiles/crypto_key?callback=setCryptData', function () {
var queryData = {
key_id: cryptData.id,
encrypted: teaEncrypt($(form).serialize(), cryptData.key)
};
$.post(form.action.replace(/^https?:/, window.location.protocol), $.param(queryData), callback, "json");
});
};
$('form#global-login-form').submit(function(e) {
e.preventDefault();
submitFormWithCrypto(this, function(response) {
if (response.errors) {
$globalNav.find('div.sign-in div.alert-row').html(response.errors.join(" ")).slideDown(250);
} else {
insertUserMenu(response.menu);
$.publish("user-info-available", response.user);
}
});
});
class ApplicationController < ActionController::Base
before_filter :decrypt_params
def ssl_required?
use_ssl? && (request.ssl? || !request.xhr?) && super
end
def decrypt_params
if params[:key_id] && params[:encrypted]
key = CryptoKey.search_and_destroy(params.delete(:key_id))
if key.nil?
render :nothing => true, :status => 401
else
query_string = Crypt::BlockTea.decrypt(params.delete(:encrypted), key)
unencrypted_params = Rack::Utils.parse_nested_query(query_string)
params.merge!(unencrypted_params)
end
end
end
end
module Crypt
module BlockTea
#
# encrypt: Use Corrected Block TEA to encrypt plaintext using password
# Return encrypted text as string
#
def encrypt(plaintext, password)
return '' if plaintext.blank? # nothing to encrypt
# 'escape' plaintext so chars outside ISO-8859-1 work in single-byte packing, but
# keep spaces as spaces (not '%20') so encrypted text doesn't grow too long, and
# convert result to longs
# v = strToLongs(escape(plaintext).gsub(/%20/,' '))
v = string_to_longs(plaintext)
if v.length == 1
v[1] = 0 # algorithm doesn't work for n<2 so fudge by adding nulls
end
k = string_to_longs(password.ljust(16).slice(0, 16)) # simply convert first 16 chars of password as key
n = v.length
z = v[n-1]
y = v[0]
delta = 0x9E3779B9
#mx, e
sum = 0
(6 + 52.0/n).floor.downto(1) { |q| # 6 + 52/n operations gives between 6 & 32 mixes on each word
sum = (sum + delta) & 0xffffffff
e = sum>>2 & 3
for p in (0...n-1)
y = v[p+1]
mx = ((z>>5 ^ ((y<<2)&0xffffffff)) + (y>>3 ^ ((z<<4)&0xffffffff)) ^ (sum^y) + (k[p&3 ^ e] ^ z)) & 0xffffffff
v[p] = (v[p] + mx) & 0xffffffff
z = v[p]
end
y = v[0]
mx = ((z>>5 ^ ((y<<2)&0xffffffff)) + (y>>3 ^ ((z<<4)&0xffffffff)) ^ (sum^y) + (k[(n-1)&3 ^ e] ^ z)) & 0xffffffff
v[n-1] = (v[n-1] + mx) & 0xffffffff
z = v[n-1]
}
ciphertext = longs_to_string(v)
ciphertext.unpack('a*').pack('m').delete("\n") # base64 encode it without newlines
end
#
# decrypt: Use Corrected Block TEA to decrypt ciphertext using password
#
def decrypt(ciphertext, password)
return '' if ciphertext.blank?
v = string_to_longs(ciphertext.unpack('m').pack("a*")) # base64 decode and convert to array of 'longs'
k = string_to_longs(password.ljust(16).slice(0, 16))
n = v.length
z = v[n-1]
y = v[0]
delta = 0x9E3779B9
#mx, e
q = (6 + 52.0/n).floor
sum = q*delta
while (sum > 0)
e = sum>>2 & 3
(n-1).downto(1) { |p|
z = v[p-1]
mx = ((z>>5 ^ ((y<<2)&0xffffffff)) + (y>>3 ^ ((z<<4)&0xffffffff)) ^ (sum^y) + (k[p&3 ^ e] ^ z)) & 0xffffffff
v[p] = (v[p] - mx) & 0xffffffff
y = v[p]
}
z = v[n-1]
mx = ((z>>5 ^ ((y<<2)&0xffffffff)) + (y>>3 ^ ((z<<4)&0xffffffff)) ^ (sum^y) + (k[0 ^ e] ^ z)) & 0xffffffff
v[0] = (v[0] - mx) & 0xffffffff
y = v[0]
sum -= delta
end
plaintext = longs_to_string(v)
# strip trailing null chars resulting from filling 4-char blocks:
plaintext.gsub(/\0+$/, '')
end
# supporting functions
def string_to_longs(s)
s = s.dup
s << [0, 0, 0].pack('c*') # Pad with at most three nulls
s.unpack('L*')
end
def longs_to_string(l) # convert array of longs back to string
l.pack('L*')
end
module_function :encrypt, :decrypt, :string_to_longs, :longs_to_string
end
end
class CryptoKey < ActiveRecord::Base
validates_presence_of :key
before_validation_on_create :set_key
def self.search_and_destroy(id)
key = self.find_by_id(id)
key.tap(&:destroy).key if key.present?
end
private
def set_key
self.key = ActiveSupport::SecureRandom.hex(32) if self.key.blank?
end
end
class CryptoKeysController < ApplicationController
ssl_required :show
def show
render :text => "#{params[:callback]}(#{CryptoKey.create.to_json});"
end
end
//
// TEAencrypt: Use Corrected Block TEA to encrypt plaintext using password
// (note plaintext & password must be strings not string objects)
//
// Return encrypted text as string
//
function teaEncrypt(plaintext, password)
{
if (plaintext.length == 0) return(''); // nothing to encrypt
// 'escape' plaintext so chars outside ISO-8859-1 work in single-byte packing, but
// keep spaces as spaces (not '%20') so encrypted text doesn't grow too long, and
// convert result to longs
// var v = strToLongs(escape(plaintext).replace(/%20/g,' '));
var v = stringToLongs(plaintext);
if (v.length == 1) v[1] = 0; // algorithm doesn't work for n<2 so fudge by adding nulls
var k = stringToLongs(password.pad(16, ' ', 1).slice(0,16)); // simply convert first 16 chars of password as key
var n = v.length;
var z = v[n-1], y = v[0], delta = 0x9E3779B9;
var mx, e, q = Math.floor(6 + 52/n), sum = 0;
while (q-- > 0) { // 6 + 52/n operations gives between 6 & 32 mixes on each word
sum += delta;
e = sum>>>2 & 3;
for (var p = 0; p < n-1; p++) {
y = v[p+1];
mx = (z>>>5 ^ y<<2) + (y>>>3 ^ z<<4) ^ (sum^y) + (k[p&3 ^ e] ^ z);
z = v[p] += mx;
}
y = v[0];
mx = (z>>>5 ^ y<<2) + (y>>>3 ^ z<<4) ^ (sum^y) + (k[p&3 ^ e] ^ z);
z = v[n-1] += mx;
}
// note use of >>> in place of >> due to lack of 'unsigned' type in JavaScript
var ciphertext = longsToString(v);
return encode64(ciphertext);
}
// supporting functions
function stringToLongs(s) { // convert string to array of longs, each containing 4 chars
// note chars must be within ISO-8859-1 (with Unicode code-point < 256) to fit 4/long
var l = new Array(Math.ceil(s.length/4));
for (var i=0; i<l.length; i++) {
// note little-endian encoding - endianness is irrelevant as long as
// it is the same in longsToStr()
l[i] = s.charCodeAt(i*4) + (s.charCodeAt(i*4+1)<<8) +
(s.charCodeAt(i*4+2)<<16) + (s.charCodeAt(i*4+3)<<24);
}
return l; // note running off the end of the string generates nulls since
} // bitwise operators treat NaN as 0
function longsToString(l) { // convert array of longs back to string
var a = new Array(l.length);
for (var i=0; i<l.length; i++) {
a[i] = String.fromCharCode(l[i] & 0xFF, l[i]>>>8 & 0xFF,
l[i]>>>16 & 0xFF, l[i]>>>24 & 0xFF);
}
return a.join(''); // use Array.join() rather than repeated string appends for efficiency
}
var keyStr = "ABCDEFGHIJKLMNOP" +
"QRSTUVWXYZabcdef" +
"ghijklmnopqrstuv" +
"wxyz0123456789+/" +
"=";
function encode64(input) {
var output = "";
var chr1, chr2, chr3 = "";
var enc1, enc2, enc3, enc4 = "";
var i = 0;
do {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
keyStr.charAt(enc1) +
keyStr.charAt(enc2) +
keyStr.charAt(enc3) +
keyStr.charAt(enc4);
chr1 = chr2 = chr3 = "";
enc1 = enc2 = enc3 = enc4 = "";
} while (i < input.length);
return output;
}
function decode64(input) {
var output = "";
var chr1, chr2, chr3 = "";
var enc1, enc2, enc3, enc4 = "";
var i = 0;
// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
var base64test = /[^A-Za-z0-9\+\/\=]/g;
if (base64test.exec(input)) {
alert("There were invalid base64 characters in the input text.\n" +
"Valid base64 characters are A-Z, a-z, 0-9, '+', '/', and '='\n" +
"Expect errors in decoding.\n\n" +
'\'' + input + '\'');
}
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
do {
enc1 = keyStr.indexOf(input.charAt(i++));
enc2 = keyStr.indexOf(input.charAt(i++));
enc3 = keyStr.indexOf(input.charAt(i++));
enc4 = keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
chr1 = chr2 = chr3 = "";
enc1 = enc2 = enc3 = enc4 = "";
} while (i < input.length);
return output;
}
String.prototype.pad = function(l, s, t){ //v1.0
return s || (s = " "), (l -= this.length) > 0 ? (s = new Array(Math.ceil(l / s.length)
+ 1).join(s)).substr(0, t = !t ? l : t == 1 ? 0 : Math.ceil(l / 2))
+ this + s.substr(0, l - t) : this;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment