Skip to content

Instantly share code, notes, and snippets.

@piroor
Last active February 8, 2019 09:34
Show Gist options
  • Select an option

  • Save piroor/dee3b71bedddf22c4bc264eea6fd97ce to your computer and use it in GitHub Desktop.

Select an option

Save piroor/dee3b71bedddf22c4bc264eea6fd97ce to your computer and use it in GitHub Desktop.
Simple String Encryptor (example)
/*
Simple String Encryptor with common key cryptosystem (example)
Usage:
// The first argument of the constructor is the algorithm.
// If you don't specify any algorithm, AES-CTR 256bit is used.
const encryptor = new Encryptor({ name: 'AES-CTR', length: 256 });
const counter = crypto.getRandomValues(new Uint8Array(16));
const encrypted = await encryptor.encryptString('Hello world!', { counter });
console.log(encrypted); // => BinaryString
console.log(btoa(encrypted)); // => Base64 String, safe to save
const decrypted = await encryptor.decryptString(encrypted, { counter });
console.log(decrypted); // => "Hello world!"
// The key is exportable as a JSON Web Key.
// You can store it to local storage or somewhere for furthur sessions.
localStorage.setItem('key', await encryptor.exportKey());
localStorage.setItem('secret message', btoa(encrypted));
// encrypt/decrypt over sessions
const key = localStorage.getItem('key');
const encryptedMessage = atob(localStorage.getItem('secret message'));
const newEncryptor = new Encryptor({ name: 'AES-CTR', length: 256 }, key);
console.log(await newEncryptor.decryptString(encryptedMessage)); // => "Hello world!"
References:
https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API
https://github.com/diafygi/webcrypto-examples
http://var.blog.jp/archives/62330155.html
Creator:
YUKI "Piro" Hiroshi <[email protected]>
License:
MIT
*/
class Encryptor {
constructor(algorithm, JSONWebKey = null) {
this.$algorithm = algorithm || { name: 'AES-CTR', length: 256 };
this.$initialized = this.init(JSONWebKey);
}
async init(JSONWebKey) {
if (JSONWebKey) {
try {
this.key = await crypto.subtle.importKey(
'jwk',
JSONWebKey,
{ name: this.$algorithm.name },
false,
['encrypt', 'decrypt']
);
}
catch(e) {
}
}
if (!this.key) {
this.key = await this.generateKey();
}
}
async exportKey() {
await this.$initialized;
return crypto.subtle.exportKey('jwk', this.key);
}
async generateKey() {
const algorithm = {
name: this.$algorithm.name,
length: this.$algorithm.length
};
return crypto.subtle.generateKey(
algorithm,
true,
['encrypt', 'decrypt']
);
}
async encrypt(input, options = null) {
options = options || {};
await this.$initialized;
const algorithm = {
name: this.$algorithm.name
};
switch (algorithm.name) {
case 'AES-CBC':
algorithm.iv = options.iv;
break;
case 'AES-CTR':
algorithm.length = options.length || (options.counter ? 64 : 128);
algorithm.counter = options.counter || new Uint8Array(16);
break;
case 'AES-GCM':
algorithm.iv = options.iv;
if (options.additionalData)
algorithm.additionalData = options.additionalData;
if (typeof options.additionalData == 'string')
algorithm.additionalData = (new TextEncoder()).encode(algorithm.additionalData).buffer;
algorithm.tagLength = options.tagLength || 128;
break;
}
return crypto.subtle.encrypt(
algorithm,
this.key,
input
);
}
async decrypt(encrypted, options = null) {
options = options || {};
const algorithm = {
name: this.$algorithm.name
};
switch (algorithm.name) {
case 'AES-CBC':
algorithm.iv = options.iv;
break;
case 'AES-CTR':
algorithm.length = options.length || (options.counter ? 64 : 128);
algorithm.counter = options.counter || new Uint8Array(16);
break;
case 'AES-GCM':
algorithm.iv = options.iv;
if (options.additionalData)
algorithm.additionalData = options.additionalData;
if (typeof options.additionalData == 'string')
algorithm.additionalData = (new TextEncoder()).encode(algorithm.additionalData).buffer;
algorithm.tagLength = options.tagLength || 128;
break;
}
await this.$initialized;
return crypto.subtle.decrypt(
algorithm,
this.key,
encrypted
);
}
async encryptString(input, options = null) {
const data = (new TextEncoder()).encode(input);
const encryptedData = await this.encrypt(data, options);
return Array.from(new Uint8Array(encryptedData), char => String.fromCharCode(char)).join('');
}
async decryptString(encrypted, options = null) {
const data = Uint8Array.from(encrypted.split(''), char => char.charCodeAt(0));
const decryptedData = await this.decrypt(data, options);
return (new TextDecoder()).decode(new Uint8Array(decryptedData));
}
};
@piroor
Copy link
Author

piroor commented Feb 8, 2019

Benchmark

(async (times) => {
  const input = 'Hellow World.';
  const cbc = new Encryptor({ name: 'AES-CBC', length: 256 });
  cbc.options = { iv: window.crypto.getRandomValues(new Uint8Array(16)) };
  const ctr = new Encryptor({ name: 'AES-CTR', length: 256 });
  const gcm = new Encryptor({ name: 'AES-GCM', length: 256 });
  gcm.options = { iv: window.crypto.getRandomValues(new Uint8Array(12)), additionalData: (new Date()).toString() };
  for (const algorithm of [cbc, ctr, gcm]) {
    try {
      const start = Date.now();
      for (let i = 0; i < times; i++) {
        const encrypted = await algorithm.encryptString(`${input} ${(new Date()).toString()} ${parseInt(Math.random * 65000)}`, algorithm.options);
        //console.log(algorithm.$algorithm.name, 'encrypted', encrypted);
        const decrypted = await algorithm.decryptString(encrypted, algorithm.options);
        //console.log(algorithm.$algorithm.name, 'decrypted', decrypted);
      }
      console.log(algorithm.$algorithm.name, Date.now() - start);
    }
    catch(e) {
      console.log(algorithm.$algorithm.name, e);
    }
  }
})(10000);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment