Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Securing TodoMVC Using the Web Cryptography API

Kevin Hakanson
September 12, 2014

Securing TodoMVC Using the Web Cryptography API

The open source TodoMVC project implements a Todo application using popular JavaScript MV* frameworks. Some of the implementations add support for compile to JavaScript languages, module loaders and real time backends. This presentation will demonstrate a TodoMVC implementation which adds support for the forthcoming W3C Web Cryptography API, as well as review some key cryptographic concepts and definitions.

Instead of storing the Todo list as plaintext in localStorage, this "secure" TodoMVC implementation encrypts Todos using a password derived key. The PBKDF2 algorithm is used for the deriveKey operation, with getRandomValues generating a cryptographically random salt. The importKey method sets up usage of AES-CBC for both encrypt and decrypt operations. The final solution helps address item "A6-Sensitive Data Exposure" from the OWASP Top 10.

With the Web Cryptography API being a recommendation in 2014, any Q&A time will likely include browser implementations and limitations, and whether JavaScript cryptography adds any value.

Kevin Hakanson

September 12, 2014
Tweet

More Decks by Kevin Hakanson

Other Decks in Technology

Transcript

  1. Project which offers the same Todo application implemented using MV*

    concepts in most of the popular JavaScript MV* frameworks of today. http://todomvc.com/ https://github.com/tastejs/todomvc
  2. Today's Session "todos" • Review some cryptography concepts • Look

    at the Web Cryptography API • Combine these to secure TodoMVC
  3. Supporting Materials (if you want to follow along) Presentation and

    Source Code https://github.com/hakanson/todomvc-jquery-webcryptoapi Demo https://hakanson.github.io/todomvc-jquery-webcryptoapi
  4. TodoMVC Uses localStorage Chrome keeps localStorage in an SQLite file:

    OS X: ~/Library/Application Support/Google/Chrome/Default/Local Storage Windows: %HOMEPATH%\AppData\Local\Google\Chrome\User Data\Default\Local Storage $ sqlite3 http_todomvc.com_0.localstorage sqlite> select * from ItemTable; todos-jquery|[{"id":"c8f7b7e1-88bf-451b-8aff- 04c9ac2544aa","title":"secure using Web Cryptography API","completed":false}]
  5. OWASP Top 10 2013 • A1-Injection • A2-Broken Authentication and

    Session Management • A3-Cross-Site Scripting (XSS) • A4-Insecure Direct Object References • A5-Security Misconfiguration • A6-Sensitive Data Exposure • A7-Missing Function Level Action Control • A8-Cross-Site Request Forgery (CSRF) • A9-Using Components with Known Vulnerabilities • A10-Unvalidated Redirects and Forwards https://www.owasp.org/index.php/Top_10_2013
  6. A6-Sensitive Data Exposure Covers sensitive data protection from the moment

    sensitive data is provided by the user, sent to and stored within the application, and then sent back to the browser again.
  7. A6-Sensitive Data Exposure Am I Vulnerable To 'Sensitive Data Exposure'?

    1. Is any of this data stored in clear text long term, including backups of this data? 2. … 3. … 4. … 5. … https://www.owasp.org/index.php/Top_10_2013-A6
  8. W3C Web Cryptography API This specification describes a JavaScript API

    for performing basic cryptographic operations in web applications, such as hashing, signature generation and verification, and encryption and decryption. http://www.w3.org/TR/WebCryptoAPI/
  9. WebCryptoAPI Use Cases • Multi-factor Authentication • Protected Document Exchange

    • Cloud Storage • Document Signing • Data Integrity Protection • Secure Messaging • Javascript Object Signing and Encryption (JOSE) http://www.w3.org/TR/WebCryptoAPI/#use-cases
  10. SubtleCrypto Interface • Provides a set of methods for dealing

    with low-level cryptographic primitives and algorithms. • Named SubtleCrypto to reflect the fact that many of these algorithms have subtle usage requirements in order to provide the required algorithmic security guarantees.
  11. Web Cryptography Working Group Key Dates ◦ April 2012: Group

    Formation ◦ March 2014: Last Call Working Draft ◦ ????: Expected Candidate Recommendation ◦ ????: Expected Proposed Recommendation ◦ 2015: Expected Recommendation http://www.w3.org/2012/webcrypto/Overview.html
  12. Chromium Dashboard Web Crypto API Implementation Status Enabled by default

    in desktop Chrome 37 (launch bug) Available in Chrome for Android release 37. Consensus & Standardization • Firefox: In development • Internet Explorer: In development • Opera: Shipped in release 24 • Opera for Android: Shipped in release 24 • Safari: In development • Web Developers: Mixed signals
  13. IE Platform Status Web Crypto API Note: IE11 implementation based

    on spec before change from CryptoOperation to Promise based API
  14. Promises/A+ A promise represents the eventual result of an asynchronous

    operation. The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise's eventual value or the reason why the promise cannot be fulfilled.
  15. Chromium Dashboard Promises (ES6) Implementation Status Enabled by default in

    desktop Chrome 32 (launch bug) Available in Chrome for Android release 32. Consensus & Standardization • Firefox: Shipped • Internet Explorer: Public support • Opera: Shipped in release 19 • Opera for Android: Shipped in release 19 • Safari: In development • Web Developers: Mixed signals
  16. Microsoft Research JavaScript Cryptography Library • The algorithms are exposed

    via the W3C WebCrypto interface, and are tested against the Internet Explorer 11 implementation of that interface. • This library is under active development. Future updates to this library may change the programming interfaces. Date Published:17 June 2014
  17. Javascript Cryptography Considered Harmful (circa 2010) • Opinion on browser

    Javascript cryptography ◦ "no reliable way for any piece of Javascript code to verify its execution environment" ◦ "can't outsource random number generation in a cryptosystem" ◦ "practically no value to doing crypto in Javascript once you add SSL to the mix" ◦ "store the key on that server [and] documents there" http://www.matasano.com/articles/javascript-cryptography/ • Didn't consider the "offline" user experience
  18. Host-Proof Hosting • In A Blink ◦ Sketch: Locked inside

    data cloud, key at browser. • Solution ◦ Host sensitive data in encrypted form, so that clients can only access and manipulate it by providing a pass-phrase which is never transmitted to the server. ◦ All encryption and decryption takes place inside the browser itself. http://ajaxpatterns.org/Host-Proof_Hosting (July 2005)
  19. Host-Proof Hosting "Requirements" • Secure transport mechanism (HTTPS). • Trust

    provider that hosts web application and serves HTML and JavaScript resources. • Defend against and accept risk of script injection (XSS) threat. ◦ However, unauthorized access by hackers only attacks users who access the application while infected, and not the entire persisted data store.
  20. My "Requirement" • Avoid proving "Schneier's Law" ◦ Anyone can

    invent a security system that he himself cannot break.
  21. Cryptography The discipline that embodies principles, means, and methods for

    providing information security, including confidentiality, data integrity, non- repudiation, and authenticity. SOURCE: SP 800-21
  22. Pseudorandom number generator (PRNG) An algorithm that produces a sequence

    of bits that are uniquely determined from an initial value called a seed. The output of the PRNG “appears” to be random. A cryptographic PRNG has the additional property that the output is unpredictable, given that the seed is not known. SOURCE: CNSSI-4009
  23. crypto.getRandomValues() If you provide an integer-based TypedArray, the function is

    going fill the array with cryptographically random numbers. var buf = new Uint8Array(32); window.crypto.getRandomValues(buf); https://developer.mozilla.org/en-US/docs/DOM/window.crypto.getRandomValues http://msdn.microsoft.com/en-us/library/ie/dn302324(v=vs.85).aspx
  24. Cryptographic Hash Function A function that maps a bit string

    of arbitrary length to a fixed length bit string. Approved hash functions satisfy the following properties: • One-way • Collision resistant SOURCE: SP 800-21
  25. Message Digest The result of applying a hash function to

    a message. Also known as a “hash value” or “hash output”. SOURCE: SP 800-107
  26. OpenSSL Command Line $ echo -n "The quick brown fox

    jumps over the lazy dog" | openssl dgst -sha256 (stdin)= d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb76 2d02d0bf37c9e592
  27. QUnit Test QUnit.test( 'SHA-256', function ( assert ) { var

    testVector = { data: 'The quick brown fox jumps over the lazy dog', sha256Hash : 'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592' }; var encoder = new TextEncoder(); var dataBuf = encoder.encode( testVector.data ); assert.promise( crypto.subtle.digest( { name: 'sha-256' }, dataBuf ) .then( function (result) { var hash = $.Uint8Util.toHexString( new Uint8Array( result ) ); assert.equal( hash, testVector.sha256Hash ); })); });
  28. QUnit Extension QUnit.extend( QUnit.assert, { promise: function ( promise )

    { var assert = this; QUnit.stop(); promise.catch( function ( err ) { assert.ok( false, err.message ); }).then( function ( ) { QUnit.start(); }); } });
  29. ex: DOMException code: 9 message: "WebCrypto is only supported over

    secure origins. See http://crbug.com/373032" name: "NotSupportedError" __proto__: DOMException For example these are considered secure origins: • chrome-extension://xxx • https://xxx • wss://xxx • file://xxx • http://localhost/ • http://127.0.0.1/ Whereas these are considered insecure: • http://foobar • ws://foobar
  30. TextEncoder Encoding API Script API to allow encoding/decoding of strings

    from binary data. Firefox 19 https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder Intent to Ship in Chrome 38 Issue 243354: Implement Text Encoding API Polyfill https://github.com/inexorabletash/text-encoding
  31. $.Uint8Util toHexString( buf : Uint8Array ) : string fromHexString (

    s : string ) : Uint8Array (still looking for a better way)
  32. $.WebCryptoAPI Wrapper around: • crypto (WebCrypto spec) • msCrypto (IE11)

    • msrCrypto (Microsoft Research) • webkitSubtle (Webkit Nightly) Implemented as jQuery Utility Plugin
  33. Hash-based Message Authentication Code (HMAC) A message authentication code that

    uses a cryptographic key in conjunction with a hash function. SOURCE: FIPS 201; CNSSI-4009
  34. Cryptographic Key A parameter used in conjunction with a cryptographic

    algorithm that determines • the transformation of plaintext data into ciphertext data, • the transformation of ciphertext data into plaintext data, • a digital signature computed from data, • ... SOURCE: FIPS 140-2
  35. Symmetric Key A cryptographic key that is used to perform

    both the cryptographic operation and its inverse, for example to encrypt and decrypt, or create a message authentication code and to verify the code. SOURCE: SP 800-63; CNSSI-4009
  36. Digital Signature The result of a cryptographic transformation of data

    which, when properly implemented, provides the services of: 1. origin authentication, 2. data integrity, and 3. signer non-repudiation. SOURCE: FIPS 140-2
  37. OpenSSL Command Line $ echo -n "The quick brown fox

    jumps over the lazy dog" | openssl dgst - sha256 -hmac "key" (stdin)= f7bc83f430538424b13298e6aa6fb143ef4d59 a14946175997479dbc2d1a3cd8
  38. QUnit.test( 'HMAC using SHA-256', function ( assert ) { var

    testVector = { data: 'The quick brown fox jumps over the lazy dog', key: 'key', hash : 'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8' }; var hmacSha256 = { name: 'hmac', hash: { name: 'sha-256' } }; var encoder = new TextEncoder(); var dataBuf = encoder.encode( testVector.data ); var keyBuf = encoder.encode( testVector.key ); });
  39. QUnit.test( 'HMAC using SHA-256', function ( assert ) { var

    testVector = { data: 'The quick brown fox jumps over the lazy dog', key: 'key', hash : 'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8' }; var hmacSha256 = { name: 'hmac', hash: { name: 'sha-256' } }; var encoder = new TextEncoder(); var dataBuf = encoder.encode( testVector.data ); var keyBuf = encoder.encode( testVector.key ); assert.promise( crypto.subtle.importKey( 'raw', keyBuf, hmacSha256, true, ['sign', 'verify'] ) .then( function ( keyResult ) { })); });
  40. QUnit.test( 'HMAC using SHA-256', function ( assert ) { var

    testVector = { data: 'The quick brown fox jumps over the lazy dog', key: 'key', hash : 'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8' }; var hmacSha256 = { name: 'hmac', hash: { name: 'sha-256' } }; var encoder = new TextEncoder(); var dataBuf = encoder.encode( testVector.data ); var keyBuf = encoder.encode( testVector.key ); assert.promise( crypto.subtle.importKey( 'raw', keyBuf, hmacSha256, true, ['sign', 'verify'] ) .then( function ( keyResult ) { return crypto.subtle.sign( hmacSha256, keyResult, dataBuf ) .then( function ( signResult ) { var hash = $.Uint8Util.toHexString( new Uint8Array( signResult ) ); assert.equal( hash, testVector.hash ); }) })); });
  41. KJH-256 Hash QUnit.test( 'KJH-256', function ( assert ) { var

    testVector = { data: 'The quick brown fox jumps over the lazy dog', }; var encoder = new TextEncoder(); var dataBuf = encoder.encode( testVector.data ); assert.promise( crypto.subtle.digest( { name: 'kjh-256' }, dataBuf ) .then( function ( result ) { var hash = $.Uint8Util.toHexString( new Uint8Array( result ) ); assert.equal( hash, 'kjh' ); })); });
  42. Error Results IE 11 NotSupportedError Chrome 37 Algorithm: Unrecognized name

    Firefox 35 An invalid or illegal string was specified Opera 24 Algorithm: Unrecognized name MSR Crypto 1.2 unsupported algorithm Webkit Nightly NotSupportedError: DOM Exception 9
  43. QUnit.test( 'HMAC using KJH-256', function ( assert ) { var

    testVector = { data: 'The quick brown fox jumps over the lazy dog', key: 'key' }; var encoder = new TextEncoder(); var dataBuf = encoder.encode( testVector.data ); var keyBuf = encoder.encode( testVector.key ); var hmacKjh256 = { name: 'hmac', hash: { name: 'kjh-256' } }; assert.promise( crypto.subtle.importKey( 'raw', keyBuf, hmacKjh256, true, ['sign', 'verify'] ) .then( function ( keyResult ) { return crypto.subtle.sign( hmacKjh256, keyResult, dataBuf ) .then( function ( signResult ) { var hash = $.Uint8Util.toHexString( new Uint8Array( signResult ) ); assert.equal( hash, 'kjh' ); }) })); });
  44. Error Results IE 11 failed, expected argument to be truthy,

    was: false Chrome 37 HmacImportParams: hash: Algorithm: Unrecognized name Firefox 35 An invalid or illegal string was specified Opera 24 HmacImportParams: hash: Algorithm: Unrecognized name MSR Crypto 1.2 Error: unsupported hash alorithm (sha-224, sha-256, sha-384, sha-512) Webkit Nightly NotSupportedError: DOM Exception 9
  45. Cipher, Plaintext and Ciphertext Cipher - Series of transformations that

    converts plaintext to ciphertext using the Cipher Key. See Also: Inverse Cipher SOURCE: FIPS 197
  46. AES The Advanced Encryption Standard specifies a U.S. government approved

    cryptographic algorithm that can be used to protect electronic data. The AES algorithm is a symmetric block cipher that can encrypt (encipher) and decrypt (decipher) information. SOURCE: FIPS 197
  47. Initialization Vector (IV) A vector used in defining the starting

    point of an encryption process within a cryptographic algorithm. SOURCE: FIPS 140-2
  48. OpenSSL Command Line $ echo -n "Message" | openssl enc

    - aes256 -K 0CD1D07EB67E19EF56EA0F3A9A8F8A7C957A2C B208327E0E536608FF83256C96 -iv 6C4C31BDAB7BAFD35B23691EC521E28D | xxd -p 23e5ebe72d99cf302c99183c05cf050a
  49. QUnit.test( 'AES-CBC', function ( assert ) { var testVector =

    { plaintext: 'Message', iv: '6C4C31BDAB7BAFD35B23691EC521E28D', key: '0CD1D07EB67E19EF56EA0F3A9A8F8A7C957A2CB208327E0E536608FF83256C96', ciphertext: '23e5ebe72d99cf302c99183c05cf050a' }; var encoder = new TextEncoder(); var decoder = new TextDecoder(); var buf = encoder.encode( testVector.plaintext ); var keyBuf = $.Uint8Util.fromHexString( testVector.key ); var ivBuf = $.Uint8Util.fromHexString( testVector.iv ); var aesCbc = { name: 'AES-CBC', iv: ivBuf }; // import key, encrypt, decrypt and compare });
  50. assert.promise( crypto.subtle.importKey( 'raw', keyBuf, { name: 'AES-CBC' }, true, ['encrypt',

    'decrypt'] ) .then( function ( encryptionKey ) { return crypto.subtle.encrypt( aesCbc, encryptionKey, buf ) .then( function ( encryptResult ) { var encryptBuf = new Uint8Array( encryptResult ); var ciphertext = $.Uint8Util.toHexString( encryptBuf ); assert.equal( ciphertext, testVector.ciphertext ); }); }));
  51. assert.promise( crypto.subtle.importKey( 'raw', keyBuf, { name: 'AES-CBC' }, true, ['encrypt',

    'decrypt'] ) .then( function ( encryptionKey ) { return crypto.subtle.encrypt( aesCbc, encryptionKey, buf ) .then( function ( encryptResult ) { var encryptBuf = new Uint8Array( encryptResult ); var ciphertext = $.Uint8Util.toHexString( encryptBuf ); assert.equal( ciphertext, testVector.ciphertext ); return crypto.subtle.decrypt( aesCbc, encryptionKey, encryptBuf ) .then( function ( decryptResult ) { var plaintext = decoder.decode( new Uint8Array( decryptResult ) ); assert.equal( plaintext, testVector.plaintext ); }); }); }));
  52. function importKey () { return crypto.subtle.importKey( 'raw', keyBuf, { name:

    'AES-CBC' }, true, ['encrypt', 'decrypt'] ) } function encrypt ( importedKey ) { encryptionKey = importedKey; return crypto.subtle.encrypt( aesCbc, encryptionKey, buf ); } function decrypt ( encryptResult ) { var encryptBuf = new Uint8Array( encryptResult ); var ciphertext = $.Uint8Util.toHexString( encryptBuf ); assert.equal( ciphertext, testVector.ciphertext ); return crypto.subtle.decrypt( aesCbc, encryptionKey, encryptBuf ); }
  53. function compare ( decryptResult ) { var plaintext = decoder.decode(

    new Uint8Array( decryptResult ) ); assert.equal( plaintext, testVector.plaintext ); } assert.promise( importKey() .then( encrypt ) .then( decrypt ) .then( compare ); );
  54. Password-Based Key Derivation Functions (PBKDF) • The randomness of cryptographic

    keys is essential for the security of cryptographic applications. SOURCE: SP 800-132
  55. Password-Based Key Derivation Functions (PBKDF) • The randomness of cryptographic

    keys is essential for the security of cryptographic applications. • Most user-chosen passwords have low entropy and weak randomness properties. ◦ shall not be used directly as cryptographic keys SOURCE: SP 800-132
  56. Password-Based Key Derivation Functions (PBKDF) • The randomness of cryptographic

    keys is essential for the security of cryptographic applications. • Most user-chosen passwords have low entropy and weak randomness properties. ◦ shall not be used directly as cryptographic keys • KDFs are deterministic algorithms that are used to derive cryptographic keying material from a secret value, such as a password. SOURCE: SP 800-132
  57. Salt A non-secret value that is used in a cryptographic

    process, usually to ensure that the results of computations for one instance cannot be reused by an Attacker. SOURCE: SP 800-63; CNSSI-4009
  58. PBKDF Specification Input: P Password S Salt C Iteration count

    kLen Length of MK in bits; at most (232-1) x hLen Parameter: PRF HMAC with an approved hash function hlen Digest size of the hash function Output: mk Master key http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
  59. QUnit.test( 'PBKDF2', function ( assert ) { var testVector =

    { password : 'password', salt: 'cf7488cd1e48e84990f51b3f121e161318ba2098aa6c993ded1012c955d5a3e8', iterations: 100, key: 'c12b2e03a08f3f0d23f3c4429c248c275a728814053a093835e803bc8e695b4e' }; var alg = { name: 'PBKDF2', hash: 'SHA-1', salt: $.Uint8Util.fromHexString( testVector.salt ), iterations: testVector.iterations }; // import key, derive bits and compare });
  60. var passwordBuf = decoder.decode( testVector.password ); assert.promise( crypto.subtle.importKey('raw', passwordBuf, 'PBKDF2',

    false, ['deriveKey']) .then(function ( keyResult ) { return crypto.subtle.deriveBits( alg, keyResult, 256 ) .then(function ( deriveResult ) { var deriveBuf = new Uint8Array( deriveResult ); var key = $.Uint8Util.toHexString( deriveBuf ); assert.equal( key, testVector.key ); }); }));
  61. Data Flow and Storage (expected) • Salt = initial random

    • IV = initial random • Ciphertext = AES( todos, Key, IV )
  62. Data Flow and Storage (actual) • PBKDF2 only implemented in

    Firefox 33 • Substitute HMAC to generate 256 bit key ◦ (do not try this at home)
  63. Password Entry Field <header id="header"> <h1><span>secure</span> todos</h1> <input id="new-todo" placeholder="What

    needs to be done?" class="hidden"> <input id="todo-password" type="password" placeholder="Enter Password" autofocus class="edit"> </header>
  64. Render Password Entry Field render: function () { var todos,

    placeholder; if (this.todos) { this.$password.addClass('hidden'); // todos list rendering... } else { if (!cryptoStorage.initialized) { placeholder = (this.$password.data('confirm') ? 'Confirm Password' : 'Set Password'); this.$password.attr('placeholder', placeholder); } this.$password.removeClass('hidden').focus(); } }
  65. Enter and Validate Password On subsequent uses, password must be

    entered to unlock todos. An invalid password will shake the password input field and outline in red.
  66. CSS3 Shake Animation #todo-password.invalid { margin-left: 50px; padding: 3px 3px

    3px 7px; border: 3px solid; width: 493px; border-radius: 6px; border-color: red; outline-color: red; animation: shake .5s linear; } @keyframes shake { 8%, 41% { transform: translateX(-10px); } 25%, 58% { transform: translateX(10px); } 75% { transform: translateX(-5px); } 92% { transform: translateX(5px); } 0%, 100% { transform: translateX(0); } }
  67. validatePassword: function (password, confirmPassword) { var that = this; if

    (confirmPassword && confirmPassword != password) { return new Promise(function (resolve, reject) { reject(new Error('passwords do not match')); }); } return cryptoStorage.authenticate(password).then(function () { return util.store(); }).then(function (result) { that.todos = result; }) }
  68. Convert store to return a Promise store: function (data) {

    if (data) { return cryptoStorage.setItem(data); } else { return cryptoStorage.getItem(); } }
  69. Random IV and Salt if ( !this.initialized ) { this.salt

    = new Uint8Array( 32 ); $.WebCryptoAPI.getRandomValues( this.salt ); this.hexSalt = $.Uint8Util.toHexString( this.salt ); this.iv = new Uint8Array( 16 ); $.WebCryptoAPI.getRandomValues( this.iv ); this.hexIV = $.Uint8Util.toHexString( this.iv ); this.initialized = true; }
  70. return $.WebCryptoAPI.subtle.importKey('raw', this.salt, hmacSha256, true, ['sign', 'verify']) .then(function (keyResult) {

    return $.WebCryptoAPI.subtle.sign(hmacSha256, keyResult, buf) .then(function (signResult) { }); }); Generate Key from Password
  71. return $.WebCryptoAPI.subtle.importKey('raw', this.salt, hmacSha256, true, ['sign', 'verify']) .then(function (keyResult) {

    return $.WebCryptoAPI.subtle.sign(hmacSha256, keyResult, buf) .then(function (signResult) { var keyBuf = new Uint8Array(signResult); // importKey for later AES encryption return $.WebCryptoAPI.subtle.importKey('raw', keyBuf, {name: 'AES-CBC'}, true, ['encrypt', 'decrypt']) .then(function (result) { }); }); }); Generate Key from Password
  72. return $.WebCryptoAPI.subtle.importKey('raw', this.salt, hmacSha256, true, ['sign', 'verify']) .then(function (keyResult) {

    return $.WebCryptoAPI.subtle.sign(hmacSha256, keyResult, buf) .then(function (signResult) { var keyBuf = new Uint8Array(signResult); // importKey for later AES encryption return $.WebCryptoAPI.subtle.importKey('raw', keyBuf, {name: 'AES-CBC'}, true, ['encrypt', 'decrypt']) .then(function (result) { that.encryptionKey = result; if (!that.hasTodos) { return that.setItem([]); } }); }); }); Generate Key from Password
  73. Encrypt and Store var encoder = new TextEncoder(); var todosBuf

    = encoder.encode(JSON.stringify(value)); var aesCbc = {name: 'AES-CBC', iv: this.iv }; var data = { salt: this.hexSalt, iv: this.hexIV, ciphertext: null }; return $.WebCryptoAPI.subtle.encrypt(aesCbc, this.encryptionKey, todosBuf) .then(function (result) { data.ciphertext = $.Uint8Util.toHexString(new Uint8Array(result)); localStorage.setItem(NAMESPACE, JSON.stringify(data)); });
  74. Decrypt var todosBuf = $.Uint8Util.fromHexString(data.ciphertext); var aesCbc = {name: 'AES-CBC',

    iv: this.iv }; $.WebCryptoAPI.subtle.decrypt(aesCbc, this.encryptionKey, todosBuf) .then(function (result) { var decoder = new TextDecoder(); var plaintext = decoder.decode(new Uint8Array(result)); try { data.todos = JSON.parse(plaintext); resolve(data.todos); } catch (err) { reject(err); } }, function (err) { reject(err); });
  75. Encrypted in localStorage sqlite> select * from ItemTable; todos-jquery-webcryptoapi|{"salt":" 455ad2b6be02de86226c8c10fe32ebfc439b197f6b0a7918

    94dab6f6920e22ff","iv":" 29b0b0092148e5fa849931c821627ea7","ciphertext":" b1770f7729edde3736eb4c26df6f4a56a236e01c525e157c f24ce76b35f8c622845a97f561c2c13942677db765e0638e f2ed90f191d5d57dcfc22fc7cd4c712716fc0e6e8748fb95 0430cb11a7e5c66ca4c55df4d88551d5b88666cd7fa4330c 85f20e88e30da752adf24ad71913bfb9"}