UNPKG

cryptico

Version:
355 lines (329 loc) 10.2 kB
var cryptico = module.exports = (function() { var my = {}; aes.Init(); var base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; my.b256to64 = function(t) { var a, c, n; var r = '', l = 0, s = 0; var tl = t.length; for (n = 0; n < tl; n++) { c = t.charCodeAt(n); if (s == 0) { r += base64Chars.charAt((c >> 2) & 63); a = (c & 3) << 4; } else if (s == 1) { r += base64Chars.charAt((a | (c >> 4) & 15)); a = (c & 15) << 2; } else if (s == 2) { r += base64Chars.charAt(a | ((c >> 6) & 3)); l += 1; r += base64Chars.charAt(c & 63); } l += 1; s += 1; if (s == 3) s = 0; } if (s > 0) { r += base64Chars.charAt(a); l += 1; r += '='; l += 1; } if (s == 1) { r += '='; } return r; } my.b64to256 = function(t) { var c, n; var r = '', s = 0, a = 0; var tl = t.length; for (n = 0; n < tl; n++) { c = base64Chars.indexOf(t.charAt(n)); if (c >= 0) { if (s) r += String.fromCharCode(a | (c >> (6 - s)) & 255); s = (s + 2) & 7; a = (c << s) & 255; } } return r; } my.b16to64 = function(h) { var i; var c; var ret = ""; if(h.length % 2 == 1) { h = "0" + h; } for (i = 0; i + 3 <= h.length; i += 3) { c = parseInt(h.substring(i, i + 3), 16); ret += base64Chars.charAt(c >> 6) + base64Chars.charAt(c & 63); } if (i + 1 == h.length) { c = parseInt(h.substring(i, i + 1), 16); ret += base64Chars.charAt(c << 2); } else if (i + 2 == h.length) { c = parseInt(h.substring(i, i + 2), 16); ret += base64Chars.charAt(c >> 2) + base64Chars.charAt((c & 3) << 4); } while ((ret.length & 3) > 0) ret += "="; return ret; } my.b64to16 = function(s) { var ret = ""; var i; var k = 0; var slop; for (i = 0; i < s.length; ++i) { if (s.charAt(i) == "=") break; v = base64Chars.indexOf(s.charAt(i)); if (v < 0) continue; if (k == 0) { ret += int2char(v >> 2); slop = v & 3; k = 1; } else if (k == 1) { ret += int2char((slop << 2) | (v >> 4)); slop = v & 0xf; k = 2; } else if (k == 2) { ret += int2char(slop); ret += int2char(v >> 2); slop = v & 3; k = 3; } else { ret += int2char((slop << 2) | (v >> 4)); ret += int2char(v & 0xf); k = 0; } } if (k == 1) ret += int2char(slop << 2); return ret; } // Converts a string to a byte array. my.string2bytes = function(string) { var bytes = new Array(); for(var i = 0; i < string.length; i++) { bytes.push(string.charCodeAt(i)); } return bytes; } // Converts a byte array to a string. my.bytes2string = function(bytes) { var string = ""; for(var i = 0; i < bytes.length; i++) { string += String.fromCharCode(bytes[i]); } return string; } // Returns a XOR b, where a and b are 16-byte byte arrays. my.blockXOR = function(a, b) { var xor = new Array(16); for(var i = 0; i < 16; i++) { xor[i] = a[i] ^ b[i]; } return xor; } // Returns a 16-byte initialization vector. my.blockIV = function() { var r = new SecureRandom(); var IV = new Array(16); r.nextBytes(IV); return IV; } // Returns a copy of bytes with zeros appended to the end // so that the (length of bytes) % 16 == 0. my.pad16 = function(bytes) { var newBytes = bytes.slice(0); var padding = (16 - (bytes.length % 16)) % 16; for(i = bytes.length; i < bytes.length + padding; i++) { newBytes.push(0); } return newBytes; } // Removes trailing zeros from a byte array. my.depad = function(bytes) { var newBytes = bytes.slice(0); while(newBytes[newBytes.length - 1] == 0) { newBytes = newBytes.slice(0, newBytes.length - 1); } return newBytes; } // AES CBC Encryption. my.encryptAESCBC = function(plaintext, key) { var exkey = key.slice(0); aes.ExpandKey(exkey); var blocks = my.string2bytes(plaintext); blocks = my.pad16(blocks); var encryptedBlocks = my.blockIV(); for(var i = 0; i < blocks.length/16; i++) { var tempBlock = blocks.slice(i * 16, i * 16 + 16); var prevBlock = encryptedBlocks.slice((i) * 16, (i) * 16 + 16); tempBlock = my.blockXOR(prevBlock, tempBlock); aes.Encrypt(tempBlock, exkey); encryptedBlocks = encryptedBlocks.concat(tempBlock); } var ciphertext = my.bytes2string(encryptedBlocks); return my.b256to64(ciphertext) } // AES CBC Decryption. my.decryptAESCBC = function(encryptedText, key) { var exkey = key.slice(0); aes.ExpandKey(exkey); var encryptedText = my.b64to256(encryptedText); var encryptedBlocks = my.string2bytes(encryptedText); var decryptedBlocks = new Array(); for(var i = 1; i < encryptedBlocks.length/16; i++) { var tempBlock = encryptedBlocks.slice(i * 16, i * 16 + 16); var prevBlock = encryptedBlocks.slice((i-1) * 16, (i-1) * 16 + 16); aes.Decrypt(tempBlock, exkey); tempBlock = my.blockXOR(prevBlock, tempBlock); decryptedBlocks = decryptedBlocks.concat(tempBlock); } decryptedBlocks = my.depad(decryptedBlocks); return my.bytes2string(decryptedBlocks); } // Wraps a string to 60 characters. my.wrap60 = function(string) { var outstr = ""; for(var i = 0; i < string.length; i++) { if(i % 60 == 0 && i != 0) outstr += "\n"; outstr += string[i]; } return outstr; } // Generate a random key for the AES-encrypted message. my.generateAESKey = function() { var key = new Array(32); var r = new SecureRandom(); r.nextBytes(key); return key; } // Generates an RSA key from a passphrase. my.generateRSAKey = function(passphrase, bitlength) { Math.seedrandom(sha256.hex(passphrase)); var rsa = new RSAKey(); rsa.generate(bitlength, "03"); return rsa; } // Returns the ascii-armored version of the public key. my.publicKeyString = function(rsakey) { pubkey = my.b16to64(rsakey.n.toString(16)); return pubkey; } // Returns an MD5 sum of a publicKeyString for easier identification. my.publicKeyID = function(publicKeyString) { return MD5(publicKeyString); } my.publicKeyFromString = function(string) { var N = my.b64to16(string.split("|")[0]); var E = "03"; var rsa = new RSAKey(); rsa.setPublic(N, E); return rsa } my.encrypt = function(plaintext, publickeystring, signingkey) { var cipherblock = ""; var aeskey = my.generateAESKey(); try { var publickey = my.publicKeyFromString(publickeystring); cipherblock += my.b16to64(publickey.encrypt(my.bytes2string(aeskey))) + "?"; } catch(err) { return {status: "Invalid public key"}; } if(signingkey) { signString = cryptico.b16to64(signingkey.signString(plaintext, "sha256")); plaintext += "::52cee64bb3a38f6403386519a39ac91c::"; plaintext += cryptico.publicKeyString(signingkey); plaintext += "::52cee64bb3a38f6403386519a39ac91c::"; plaintext += signString; } cipherblock += my.encryptAESCBC(plaintext, aeskey); return {status: "success", cipher: cipherblock}; } my.decrypt = function(ciphertext, key) { var cipherblock = ciphertext.split("?"); var aeskey = key.decrypt(my.b64to16(cipherblock[0])); if(aeskey == null) { return {status: "failure"}; } aeskey = my.string2bytes(aeskey); var plaintext = my.decryptAESCBC(cipherblock[1], aeskey).split("::52cee64bb3a38f6403386519a39ac91c::"); if(plaintext.length == 3) { var publickey = my.publicKeyFromString(plaintext[1]); var signature = my.b64to16(plaintext[2]); if(publickey.verifyString(plaintext[0], signature)) { return {status: "success", plaintext: plaintext[0], signature: "verified", publicKeyString: my.publicKeyString(publickey)}; } else { return {status: "success", plaintext: plaintext[0], signature: "forged", publicKeyString: my.publicKeyString(publickey)}; } } else { return {status: "success", plaintext: plaintext[0], signature: "unsigned"}; } } return my; }()); module.exports.RSAKey = RSAKey;