ig-trader
Version:
A client to programmatically spreadbet with IG
422 lines (399 loc) • 16.6 kB
JavaScript
/*----------------------------------------------------------------------------*/
// Copyright (c) 2009 pidder <www.pidder.com>
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
/*----------------------------------------------------------------------------*/
/*
* AES CBC (Cipher Block Chaining) Mode for use in pidCrypt Library
* The pidCrypt AES CBC mode is compatible with openssl aes-xxx-cbc mode
* using the same algorithms for key and iv creation and padding as openssl.
*
* Depends on pidCrypt (pidcrypt.js, pidcrypt_util.js), AES (aes_core.js)
* and MD5 (md5.js)
*
/*----------------------------------------------------------------------------*/
var pidCrypt = require('./pidcrypt.js');
var pidCryptUtil = require('./pidcrypt_util.js');
require('./md5.js');
require('./aes_core.js');
if(typeof(pidCrypt) != 'undefined' &&
typeof(pidCrypt.AES) != 'undefined' &&
typeof(pidCrypt.MD5) != 'undefined')
{
pidCrypt.AES.CBC = function () {
this.pidcrypt = new pidCrypt();
this.aes = new pidCrypt.AES(this.pidcrypt);
//shortcuts to pidcrypt methods
this.getOutput = function(){
return this.pidcrypt.getOutput();
}
this.getAllMessages = function(lnbrk){
return this.pidcrypt.getAllMessages(lnbrk);
}
this.isError = function(){
return this.pidcrypt.isError();
}
}
/**
* Initialize CBC for encryption from password.
* Note: Only for encrypt operation!
* @param password: String
* @param options {
* nBits: aes bit size (128, 192 or 256)
* }
*/
pidCrypt.AES.CBC.prototype.init = function(password, options) {
if(!options) options = {};
var pidcrypt = this.pidcrypt;
pidcrypt.setDefaults();
var pObj = this.pidcrypt.getParams(); //loading defaults
for(var o in options)
pObj[o] = options[o];
var k_iv = this.createKeyAndIv({password:password, salt: pObj.salt, bits: pObj.nBits});
pObj.key = k_iv.key;
pObj.iv = k_iv.iv;
pObj.dataOut = '';
pidcrypt.setParams(pObj)
this.aes.init();
}
/**
* Initialize CBC for encryption from password.
* @param dataIn: plain text
* @param password: String
* @param options {
* nBits: aes bit size (128, 192 or 256)
* }
*/
pidCrypt.AES.CBC.prototype.initEncrypt = function(dataIn, password, options) {
this.init(password,options);//call standard init
this.pidcrypt.setParams({dataIn:dataIn, encryptIn: pidCryptUtil.toByteArray(dataIn)})//setting input for encryption
}
/**
* Initialize CBC for decryption from encrypted text (compatible with openssl).
* see thread http://thedotnet.com/nntp/300307/showpost.aspx
* @param crypted: base64 encoded aes encrypted text
* @param passwd: String
* @param options {
* nBits: aes bit size (128, 192 or 256),
* UTF8: boolean, set to false when decrypting certificates,
* A0_PAD: boolean, set to false when decrypting certificates
* }
*/
pidCrypt.AES.CBC.prototype.initDecrypt = function(crypted, password, options){
if(!options) options = {};
var pidcrypt = this.pidcrypt;
pidcrypt.setParams({dataIn:crypted})
if(!password)
pidcrypt.appendError('pidCrypt.AES.CBC.initFromEncryption: Sorry, can not crypt or decrypt without password.\n');
var ciphertext = pidCryptUtil.decodeBase64(crypted);
if(ciphertext.indexOf('Salted__') != 0)
pidcrypt.appendError('pidCrypt.AES.CBC.initFromCrypt: Sorry, unknown encryption method.\n');
var salt = ciphertext.substr(8,8);//extract salt from crypted text
options.salt = pidCryptUtil.convertToHex(salt);//salt is always hex string
this.init(password,options);//call standard init
ciphertext = ciphertext.substr(16);
pidcrypt.setParams({decryptIn:pidCryptUtil.toByteArray(ciphertext)})
}
/**
* Init CBC En-/Decryption from given parameters.
* @param input: plain text or base64 encrypted text
* @param key: HEX String (16, 24 or 32 byte)
* @param iv: HEX String (16 byte)
* @param options {
* salt: array of bytes (8 byte),
* nBits: aes bit size (128, 192 or 256)
* }
*/
pidCrypt.AES.CBC.prototype.initByValues = function(dataIn, key, iv, options){
var pObj = {};
this.init('',options);//empty password, we are setting key, iv manually
pObj.dataIn = dataIn;
pObj.key = key
pObj.iv = iv
this.pidcrypt.setParams(pObj)
}
pidCrypt.AES.CBC.prototype.getAllMessages = function(lnbrk){
return this.pidcrypt.getAllMessages(lnbrk);
}
/**
* Creates key of length nBits and an iv form password+salt
* compatible to openssl.
* See thread http://thedotnet.com/nntp/300307/showpost.aspx
*
* @param pObj {
* password: password as String
* [salt]: salt as String, default 8 byte random salt
* [bits]: no of bits, default pidCrypt.params.nBits = 256
* }
*
* @return {iv: HEX String, key: HEX String}
*/
pidCrypt.AES.CBC.prototype.createKeyAndIv = function(pObj){
var pidcrypt = this.pidcrypt;
var retObj = {};
var count = 1;//openssl rounds
var miter = "3";
if(!pObj) pObj = {};
if(!pObj.salt) {
pObj.salt = pidcrypt.getRandomBytes(8);
pObj.salt = pidCryptUtil.convertToHex(pidCryptUtil.byteArray2String(pObj.salt));
pidcrypt.setParams({salt: pObj.salt});
}
var data00 = pObj.password + pidCryptUtil.convertFromHex(pObj.salt);
var hashtarget = '';
var result = '';
var keymaterial = [];
var loop = 0;
keymaterial[loop++] = data00;
for(var j=0; j<miter; j++){
if(j == 0)
result = data00; //initialize
else {
hashtarget = pidCryptUtil.convertFromHex(result);
hashtarget += data00;
result = hashtarget;
}
for(var c=0; c<count; c++){
result = pidCrypt.MD5(result);
}
keymaterial[loop++] = result;
}
switch(pObj.bits){
case 128://128 bit
retObj.key = keymaterial[1];
retObj.iv = keymaterial[2];
break;
case 192://192 bit
retObj.key = keymaterial[1] + keymaterial[2].substr(0,16);
retObj.iv = keymaterial[3];
break;
case 256://256 bit
retObj.key = keymaterial[1] + keymaterial[2];
retObj.iv = keymaterial[3];
break;
default:
pidcrypt.appendError('pidCrypt.AES.CBC.createKeyAndIv: Sorry, only 128, 192 and 256 bits are supported.\nBits('+typeof(pObj.bits)+') = '+pObj.bits);
}
return retObj;
}
/**
* Encrypt a text using AES encryption in CBC mode of operation
* - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
*
* one of the pidCrypt.AES.CBC init funtions must be called before execution
*
* @param byteArray: text to encrypt as array of bytes
*
* @return aes-cbc encrypted text
*/
pidCrypt.AES.CBC.prototype.encryptRaw = function(byteArray) {
var pidcrypt = this.pidcrypt;
var aes = this.aes;
var p = pidcrypt.getParams(); //get parameters for operation set by init
if(!byteArray)
byteArray = p.encryptIn;
pidcrypt.setParams({encryptIn: byteArray});
if(!p.dataIn) pidcrypt.setParams({dataIn:byteArray});
var iv = pidCryptUtil.convertFromHex(p.iv);
//PKCS5 paddding
var charDiv = p.blockSize - ((byteArray.length+1) % p.blockSize);
if(p.A0_PAD)
byteArray[byteArray.length] = 10
for(var c=0;c<charDiv;c++) byteArray[byteArray.length] = charDiv;
var nBytes = Math.floor(p.nBits/8); // nr of bytes in key
var keyBytes = new Array(nBytes);
var key = pidCryptUtil.convertFromHex(p.key);
for (var i=0; i<nBytes; i++) {
keyBytes[i] = isNaN(key.charCodeAt(i)) ? 0 : key.charCodeAt(i);
}
// generate key schedule
var keySchedule = aes.expandKey(keyBytes);
var blockCount = Math.ceil(byteArray.length/p.blockSize);
var ciphertxt = new Array(blockCount); // ciphertext as array of strings
var textBlock = [];
var state = pidCryptUtil.toByteArray(iv);
for (var b=0; b<blockCount; b++) {
// XOR last block and next data block, then encrypt that
textBlock = byteArray.slice(b*p.blockSize, b*p.blockSize+p.blockSize);
state = aes.xOr_Array(state, textBlock);
state = aes.encrypt(state.slice(), keySchedule); // -- encrypt block --
ciphertxt[b] = pidCryptUtil.byteArray2String(state);
}
var ciphertext = ciphertxt.join('');
pidcrypt.setParams({dataOut:ciphertext, encryptOut:ciphertext});
//remove all parameters from enviroment for more security is debug off
if(!pidcrypt.isDebug() && pidcrypt.clear) pidcrypt.clearParams();
return ciphertext || '';
}
/**
* Encrypt a text using AES encryption in CBC mode of operation
* - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
*
* Unicode multi-byte character safe
*
* one of the pidCrypt.AES.CBC init funtions must be called before execution
*
* @param plaintext: text to encrypt
*
* @return aes-cbc encrypted text openssl compatible
*/
pidCrypt.AES.CBC.prototype.encrypt = function(plaintext) {
var pidcrypt = this.pidcrypt;
var salt = '';
var p = pidcrypt.getParams(); //get parameters for operation set by init
if(!plaintext)
plaintext = p.dataIn;
if(p.UTF8)
plaintext = pidCryptUtil.encodeUTF8(plaintext);
pidcrypt.setParams({dataIn:plaintext, encryptIn: pidCryptUtil.toByteArray(plaintext)});
var ciphertext = this.encryptRaw()
salt = 'Salted__' + pidCryptUtil.convertFromHex(p.salt);
ciphertext = salt + ciphertext;
ciphertext = pidCryptUtil.encodeBase64(ciphertext); // encode in base64
pidcrypt.setParams({dataOut:ciphertext});
//remove all parameters from enviroment for more security is debug off
if(!pidcrypt.isDebug() && pidcrypt.clear) pidcrypt.clearParams();
return ciphertext || '';
}
/**
* Encrypt a text using AES encryption in CBC mode of operation
* - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
*
* Unicode multi-byte character safe
*
* @param dataIn: plain text
* @param password: String
* @param options {
* nBits: aes bit size (128, 192 or 256)
* }
*
* @param plaintext: text to encrypt
*
* @return aes-cbc encrypted text openssl compatible
*
*/
pidCrypt.AES.CBC.prototype.encryptText = function(dataIn,password,options) {
this.initEncrypt(dataIn, password, options);
return this.encrypt();
}
/**
* Decrypt a text encrypted by AES in CBC mode of operation
*
* one of the pidCrypt.AES.CBC init funtions must be called before execution
*
* @param byteArray: aes-cbc encrypted text as array of bytes
*
* @return decrypted text as String
*/
pidCrypt.AES.CBC.prototype.decryptRaw = function(byteArray) {
var aes = this.aes;
var pidcrypt = this.pidcrypt;
var p = pidcrypt.getParams(); //get parameters for operation set by init
if(!byteArray)
byteArray = p.decryptIn;
pidcrypt.setParams({decryptIn: byteArray});
if(!p.dataIn) pidcrypt.setParams({dataIn:byteArray});
if((p.iv.length/2)<p.blockSize)
return pidcrypt.appendError('pidCrypt.AES.CBC.decrypt: Sorry, can not decrypt without complete set of parameters.\n Length of key,iv:'+p.key.length+','+p.iv.length);
var iv = pidCryptUtil.convertFromHex(p.iv);
if(byteArray.length%p.blockSize != 0)
return pidcrypt.appendError('pidCrypt.AES.CBC.decrypt: Sorry, the encrypted text has the wrong length for aes-cbc mode\n Length of ciphertext:'+byteArray.length+byteArray.length%p.blockSize);
var nBytes = Math.floor(p.nBits/8); // nr of bytes in key
var keyBytes = new Array(nBytes);
var key = pidCryptUtil.convertFromHex(p.key);
for (var i=0; i<nBytes; i++) {
keyBytes[i] = isNaN(key.charCodeAt(i)) ? 0 : key.charCodeAt(i);
}
// generate key schedule
var keySchedule = aes.expandKey(keyBytes);
// separate byteArray into blocks
var nBlocks = Math.ceil((byteArray.length) / p.blockSize);
// plaintext will get generated block-by-block into array of block-length strings
var plaintxt = new Array(nBlocks.length);
var state = pidCryptUtil.toByteArray(iv);
var ciphertextBlock = [];
var dec_state = [];
for (var b=0; b<nBlocks; b++) {
ciphertextBlock = byteArray.slice(b*p.blockSize, b*p.blockSize+p.blockSize);
dec_state = aes.decrypt(ciphertextBlock, keySchedule); // decrypt ciphertext block
plaintxt[b] = pidCryptUtil.byteArray2String(aes.xOr_Array(state, dec_state));
state = ciphertextBlock.slice(); //save old ciphertext for next round
}
// join array of blocks into single plaintext string and return it
var plaintext = plaintxt.join('');
if(pidcrypt.isDebug()) pidcrypt.appendDebug('Padding after decryption:'+ pidCryptUtil.convertToHex(plaintext) + ':' + plaintext.length + '\n');
var endByte = plaintext.charCodeAt(plaintext.length-1);
//remove oppenssl A0 padding eg. 0A05050505
if(p.A0_PAD){
plaintext = plaintext.substr(0,plaintext.length-(endByte+1));
}
else {
var div = plaintext.length - (plaintext.length-endByte);
var firstPadByte = plaintext.charCodeAt(plaintext.length-endByte);
if(endByte == firstPadByte && endByte == div)
plaintext = plaintext.substr(0,plaintext.length-endByte);
}
pidcrypt.setParams({dataOut: plaintext,decryptOut: plaintext});
//remove all parameters from enviroment for more security is debug off
if(!pidcrypt.isDebug() && pidcrypt.clear) pidcrypt.clearParams();
return plaintext || '';
}
/**
* Decrypt a base64 encoded text encrypted by AES in CBC mode of operation
* and removes padding from decrypted text
*
* one of the pidCrypt.AES.CBC init funtions must be called before execution
*
* @param ciphertext: base64 encoded and aes-cbc encrypted text
*
* @return decrypted text as String
*/
pidCrypt.AES.CBC.prototype.decrypt = function(ciphertext) {
var pidcrypt = this.pidcrypt;
var p = pidcrypt.getParams(); //get parameters for operation set by init
if(ciphertext)
pidcrypt.setParams({dataIn:ciphertext});
if(!p.decryptIn) {
var decryptIn = pidCryptUtil.decodeBase64(p.dataIn);
if(decryptIn.indexOf('Salted__') == 0) decryptIn = decryptIn.substr(16);
pidcrypt.setParams({decryptIn: pidCryptUtil.toByteArray(decryptIn)});
}
var plaintext = this.decryptRaw();
if(p.UTF8)
plaintext = pidCryptUtil.decodeUTF8(plaintext); // decode from UTF8 back to Unicode multi-byte chars
if(pidcrypt.isDebug()) pidcrypt.appendDebug('Removed Padding after decryption:'+ pidCryptUtil.convertToHex(plaintext) + ':' + plaintext.length + '\n');
pidcrypt.setParams({dataOut:plaintext});
//remove all parameters from enviroment for more security is debug off
if(!pidcrypt.isDebug() && pidcrypt.clear) pidcrypt.clearParams();
return plaintext || '';
}
/**
* Decrypt a base64 encoded text encrypted by AES in CBC mode of operation
* and removes padding from decrypted text
*
* one of the pidCrypt.AES.CBC init funtions must be called before execution
*
* @param dataIn: base64 encoded aes encrypted text
* @param password: String
* @param options {
* nBits: aes bit size (128, 192 or 256),
* UTF8: boolean, set to false when decrypting certificates,
* A0_PAD: boolean, set to false when decrypting certificates
* }
*
* @return decrypted text as String
*/
pidCrypt.AES.CBC.prototype.decryptText = function(dataIn, password, options) {
this.initDecrypt(dataIn, password, options);
return this.decrypt();
}
}