SlideShare a Scribd company logo
Securing TodoMVC 
Using the 
Web Cryptography API 
Kevin Hakanson 
12 September 2014
Kevin Hakanson 
@hakanson 
+KevinHakanson 
hakanson
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
Today's Session "todos" 
● Review some cryptography concepts 
● Look at the Web Cryptography API 
● Combine these to secure TodoMVC
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
Why?
TodoMVC Uses localStorage 
Chrome keeps localStorage in an SQLite file: 
OS X: ~/Library/Application Support/Google/Chrome/Default/Local Storage 
Windows: %HOMEPATH%AppDataLocalGoogleChromeUser DataDefaultLocal 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}]
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
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.
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
Acceptance Criteria 
● Enter password before accessing "todos" 
● Data encrypted "at rest" in localStorage
How?
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/
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
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.
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
Where? 
(Browser Support)
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
http://caniuse.com/#search=Web 
Cryptography
IE Platform Status 
Web Crypto API 
Note: IE11 implementation based on spec before 
change from CryptoOperation to Promise based API
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.
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
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
But...
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
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)
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.
My "Requirement" 
● Avoid proving "Schneier's Law" 
○ Anyone can invent a security system that he himself 
cannot break.
What? 
(concepts)
Glossary 
Glossary of Key Information Security Terms 
http://nvlpubs.nist.gov/nistpubs/ir/2013/NIST.IR.7298r2.pdf 
SOURCE: NISTIR 7298
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
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
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
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
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
OpenSSL Command Line 
$ echo -n "The quick brown fox jumps over the 
lazy dog" | openssl dgst -sha256 
(stdin)= 
d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb76 
2d02d0bf37c9e592
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 ); 
})); 
});
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(); 
}); 
} 
});
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
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
$.Uint8Util 
toHexString( buf : Uint8Array ) : string 
fromHexString ( s : string ) : Uint8Array 
(still looking for a better way)
$.WebCryptoAPI 
Wrapper around: 
● crypto (WebCrypto spec) 
● msCrypto (IE11) 
● msrCrypto (Microsoft Research) 
● webkitSubtle (Webkit Nightly) 
Implemented as jQuery Utility Plugin
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
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
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
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
OpenSSL Command Line 
$ echo -n "The quick brown fox jumps 
over the lazy dog" | openssl dgst - 
sha256 -hmac "key" 
(stdin)= 
f7bc83f430538424b13298e6aa6fb143ef4d59 
a14946175997479dbc2d1a3cd8
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 ); 
});
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 ) { 
})); 
});
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 ); 
}) 
})); 
});
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' ); 
})); 
});
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
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' ); 
}) 
})); 
});
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
Cipher, Plaintext and Ciphertext 
Cipher - Series of transformations that 
converts plaintext to ciphertext using the 
Cipher Key. 
See Also: Inverse Cipher 
SOURCE: FIPS 197
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
(CC BY 3.0) http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html
Initialization Vector (IV) 
A vector used in defining the starting point of an 
encryption process within a cryptographic 
algorithm. 
SOURCE: FIPS 140-2
OpenSSL Command Line 
$ echo -n "Message" | openssl enc - 
aes256 -K 
0CD1D07EB67E19EF56EA0F3A9A8F8A7C957A2C 
B208327E0E536608FF83256C96 -iv 
6C4C31BDAB7BAFD35B23691EC521E28D | xxd 
-p 
23e5ebe72d99cf302c99183c05cf050a
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 
});
assert.promise( crypto.subtle.importKey( 'raw', keyBuf, { name: 'AES-CBC' }, 
true, ['encrypt', 'decrypt'] ) 
.then( function ( encryptionKey ) { 
}));
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 ); 
}); 
}));
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 ); 
}); 
}); 
}));
redux 
chained vs. nested promises
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 ); 
}
function compare ( decryptResult ) { 
var plaintext = decoder.decode( new Uint8Array( decryptResult ) ); 
assert.equal( plaintext, testVector.plaintext ); 
} 
assert.promise( importKey() 
.then( encrypt ) 
.then( decrypt ) 
.then( compare ); 
);
Password-Based Key Derivation 
Functions (PBKDF) 
● The randomness of cryptographic keys is 
essential for the security of cryptographic 
applications. 
SOURCE: SP 800-132
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
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
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
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
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 
});
var passwordBuf = decoder.decode( testVector.password ); 
assert.promise( crypto.subtle.importKey('raw', passwordBuf, 'PBKDF2', 
false, ['deriveKey']) 
.then(function ( keyResult ) { 
}));
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 ); 
}); 
}));
Data Flow and Storage (expected) 
● Salt = initial random 
● IV = initial random 
● Ciphertext = AES( todos, Key, IV )
Data Flow and Storage (actual) 
● PBKDF2 only implemented in Firefox 33 
● Substitute HMAC to generate 256 bit key 
○ (do not try this at home)
Set and Confirm Password 
On first use, password must be established.
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>
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(); 
} 
}
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.
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); } 
}
Demo
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; 
}) 
}
Convert store to return a Promise 
store: function (data) { 
if (data) { 
return cryptoStorage.setItem(data); 
} else { 
return cryptoStorage.getItem(); 
} 
}
Data Flow and Storage (API)
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; 
}
Generate Key from Password 
return $.WebCryptoAPI.subtle.importKey('raw', this.salt, hmacSha256, 
true, ['sign', 'verify']) 
.then(function (keyResult) { 
});
Generate Key from Password 
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 
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 
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([]); 
} 
}); 
}); 
});
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)); 
});
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); 
});
Encrypted in localStorage 
sqlite> select * from ItemTable; 
todos-jquery-webcryptoapi|{"salt":" 
455ad2b6be02de86226c8c10fe32ebfc439b197f6b0a7918 
94dab6f6920e22ff","iv":" 
29b0b0092148e5fa849931c821627ea7","ciphertext":" 
b1770f7729edde3736eb4c26df6f4a56a236e01c525e157c 
f24ce76b35f8c622845a97f561c2c13942677db765e0638e 
f2ed90f191d5d57dcfc22fc7cd4c712716fc0e6e8748fb95 
0430cb11a7e5c66ca4c55df4d88551d5b88666cd7fa4330c 
85f20e88e30da752adf24ad71913bfb9"}
Questions? 
(and hopefully answers)

More Related Content

What's hot

Crypto failures every developer should avoid
Crypto failures every developer should avoidCrypto failures every developer should avoid
Crypto failures every developer should avoid
Filip Šebesta
 
Security Bootcamp 2013 - Mitigate DDoS attack with effective cost - Nguyễn Ch...
Security Bootcamp 2013 - Mitigate DDoS attack with effective cost - Nguyễn Ch...Security Bootcamp 2013 - Mitigate DDoS attack with effective cost - Nguyễn Ch...
Security Bootcamp 2013 - Mitigate DDoS attack with effective cost - Nguyễn Ch...
Security Bootcamp
 
The Ultimate IDS Smackdown
The Ultimate IDS SmackdownThe Ultimate IDS Smackdown
The Ultimate IDS Smackdown
Mario Heiderich
 

What's hot (20)

Outsmarting Smart Contracts - an essential walkthrough a blockchain security ...
Outsmarting Smart Contracts - an essential walkthrough a blockchain security ...Outsmarting Smart Contracts - an essential walkthrough a blockchain security ...
Outsmarting Smart Contracts - an essential walkthrough a blockchain security ...
 
[OPD 2019] Attacking JWT tokens
[OPD 2019] Attacking JWT tokens[OPD 2019] Attacking JWT tokens
[OPD 2019] Attacking JWT tokens
 
Building & Hacking Modern iOS Apps
Building & Hacking Modern iOS AppsBuilding & Hacking Modern iOS Apps
Building & Hacking Modern iOS Apps
 
Hacking intranet websites
Hacking intranet websitesHacking intranet websites
Hacking intranet websites
 
Dev and Blind - Attacking the weakest Link in IT Security
Dev and Blind - Attacking the weakest Link in IT SecurityDev and Blind - Attacking the weakest Link in IT Security
Dev and Blind - Attacking the weakest Link in IT Security
 
Total E(A)gression defcon
Total E(A)gression   defconTotal E(A)gression   defcon
Total E(A)gression defcon
 
SiestaTime - Defcon27 Red Team Village
SiestaTime - Defcon27 Red Team VillageSiestaTime - Defcon27 Red Team Village
SiestaTime - Defcon27 Red Team Village
 
JWTs and JOSE in a flash
JWTs and JOSE in a flashJWTs and JOSE in a flash
JWTs and JOSE in a flash
 
Web security: Securing untrusted web content at browsers
Web security: Securing untrusted web content at browsersWeb security: Securing untrusted web content at browsers
Web security: Securing untrusted web content at browsers
 
Crypto failures every developer should avoid
Crypto failures every developer should avoidCrypto failures every developer should avoid
Crypto failures every developer should avoid
 
ECMAScript 6 from an Attacker's Perspective - Breaking Frameworks, Sandboxes,...
ECMAScript 6 from an Attacker's Perspective - Breaking Frameworks, Sandboxes,...ECMAScript 6 from an Attacker's Perspective - Breaking Frameworks, Sandboxes,...
ECMAScript 6 from an Attacker's Perspective - Breaking Frameworks, Sandboxes,...
 
Passwords
PasswordsPasswords
Passwords
 
SSL certificates
SSL certificatesSSL certificates
SSL certificates
 
XSS Countermeasures in Grails
XSS Countermeasures in GrailsXSS Countermeasures in Grails
XSS Countermeasures in Grails
 
Root via SMS: 4G access level security assessment, Sergey Gordeychik, Alexand...
Root via SMS: 4G access level security assessment, Sergey Gordeychik, Alexand...Root via SMS: 4G access level security assessment, Sergey Gordeychik, Alexand...
Root via SMS: 4G access level security assessment, Sergey Gordeychik, Alexand...
 
Security Bootcamp 2013 - Mitigate DDoS attack with effective cost - Nguyễn Ch...
Security Bootcamp 2013 - Mitigate DDoS attack with effective cost - Nguyễn Ch...Security Bootcamp 2013 - Mitigate DDoS attack with effective cost - Nguyễn Ch...
Security Bootcamp 2013 - Mitigate DDoS attack with effective cost - Nguyễn Ch...
 
Side-Channels on the Web: Attacks and Defenses
Side-Channels on the Web: Attacks and DefensesSide-Channels on the Web: Attacks and Defenses
Side-Channels on the Web: Attacks and Defenses
 
Hashgraph vs Blockchain | Hedera Hashgraph Tutorial | Hashgraph Technology | ...
Hashgraph vs Blockchain | Hedera Hashgraph Tutorial | Hashgraph Technology | ...Hashgraph vs Blockchain | Hedera Hashgraph Tutorial | Hashgraph Technology | ...
Hashgraph vs Blockchain | Hedera Hashgraph Tutorial | Hashgraph Technology | ...
 
Bandit and Gosec - Security Linters
Bandit and Gosec - Security LintersBandit and Gosec - Security Linters
Bandit and Gosec - Security Linters
 
The Ultimate IDS Smackdown
The Ultimate IDS SmackdownThe Ultimate IDS Smackdown
The Ultimate IDS Smackdown
 

Similar to Securing TodoMVC Using the Web Cryptography API

Phu appsec13
Phu appsec13Phu appsec13
Phu appsec13
drewz lin
 
Hack any website
Hack any websiteHack any website
Hack any website
sunil kumar
 
Hacking Client Side Insecurities
Hacking Client Side InsecuritiesHacking Client Side Insecurities
Hacking Client Side Insecurities
amiable_indian
 
Developing your first application using FIWARE
Developing your first application using FIWAREDeveloping your first application using FIWARE
Developing your first application using FIWARE
FIWARE
 

Similar to Securing TodoMVC Using the Web Cryptography API (20)

OWASP ZAP Workshop for QA Testers
OWASP ZAP Workshop for QA TestersOWASP ZAP Workshop for QA Testers
OWASP ZAP Workshop for QA Testers
 
Phu appsec13
Phu appsec13Phu appsec13
Phu appsec13
 
Neoito — Secure coding practices
Neoito — Secure coding practicesNeoito — Secure coding practices
Neoito — Secure coding practices
 
Hack any website
Hack any websiteHack any website
Hack any website
 
W3 conf hill-html5-security-realities
W3 conf hill-html5-security-realitiesW3 conf hill-html5-security-realities
W3 conf hill-html5-security-realities
 
Top 10 Web Hacks 2012
Top 10 Web Hacks 2012Top 10 Web Hacks 2012
Top 10 Web Hacks 2012
 
FBI & Secret Service- Business Email Compromise Workshop
FBI & Secret Service- Business Email Compromise WorkshopFBI & Secret Service- Business Email Compromise Workshop
FBI & Secret Service- Business Email Compromise Workshop
 
FBI & Secret Service- Business Email Compromise Workshop
FBI & Secret Service- Business Email Compromise WorkshopFBI & Secret Service- Business Email Compromise Workshop
FBI & Secret Service- Business Email Compromise Workshop
 
maxbox starter72 multilanguage coding
maxbox starter72 multilanguage codingmaxbox starter72 multilanguage coding
maxbox starter72 multilanguage coding
 
Top Ten Web Hacking Techniques of 2012
Top Ten Web Hacking Techniques of 2012Top Ten Web Hacking Techniques of 2012
Top Ten Web Hacking Techniques of 2012
 
The Hacking Games - A Road to Post Exploitation Meetup - 20240222.pptx
The Hacking Games - A Road to Post Exploitation Meetup - 20240222.pptxThe Hacking Games - A Road to Post Exploitation Meetup - 20240222.pptx
The Hacking Games - A Road to Post Exploitation Meetup - 20240222.pptx
 
Web security: Securing Untrusted Web Content in Browsers
Web security: Securing Untrusted Web Content in BrowsersWeb security: Securing Untrusted Web Content in Browsers
Web security: Securing Untrusted Web Content in Browsers
 
Cloud Intrusion Detection Reloaded - 2018
Cloud Intrusion Detection Reloaded - 2018Cloud Intrusion Detection Reloaded - 2018
Cloud Intrusion Detection Reloaded - 2018
 
Modern Web Security, Lazy but Mindful Like a Fox
Modern Web Security, Lazy but Mindful Like a FoxModern Web Security, Lazy but Mindful Like a Fox
Modern Web Security, Lazy but Mindful Like a Fox
 
Hacking Client Side Insecurities
Hacking Client Side InsecuritiesHacking Client Side Insecurities
Hacking Client Side Insecurities
 
Content Security Policy - Lessons learned at Yahoo
Content Security Policy - Lessons learned at YahooContent Security Policy - Lessons learned at Yahoo
Content Security Policy - Lessons learned at Yahoo
 
OpenTelemetry For Developers
OpenTelemetry For DevelopersOpenTelemetry For Developers
OpenTelemetry For Developers
 
Developing your first application using FIWARE
Developing your first application using FIWAREDeveloping your first application using FIWARE
Developing your first application using FIWARE
 
OSINT Basics for Threat Hunters and Practitioners
OSINT Basics for Threat Hunters and PractitionersOSINT Basics for Threat Hunters and Practitioners
OSINT Basics for Threat Hunters and Practitioners
 
How to get along with HATEOAS without letting the bad guys steal your lunch?
How to get along with HATEOAS without letting the bad guys steal your lunch?How to get along with HATEOAS without letting the bad guys steal your lunch?
How to get along with HATEOAS without letting the bad guys steal your lunch?
 

More from Kevin Hakanson

More from Kevin Hakanson (10)

Sharpen your "Architectural Documentation" Saw
Sharpen your "Architectural Documentation" SawSharpen your "Architectural Documentation" Saw
Sharpen your "Architectural Documentation" Saw
 
Who's in your Cloud? Cloud State Monitoring
Who's in your Cloud? Cloud State MonitoringWho's in your Cloud? Cloud State Monitoring
Who's in your Cloud? Cloud State Monitoring
 
Adopting Multi-Cloud Services with Confidence
Adopting Multi-Cloud Services with ConfidenceAdopting Multi-Cloud Services with Confidence
Adopting Multi-Cloud Services with Confidence
 
Introduction to Speech Interfaces for Web Applications
Introduction to Speech Interfaces for Web ApplicationsIntroduction to Speech Interfaces for Web Applications
Introduction to Speech Interfaces for Web Applications
 
Learning to Mod Minecraft: A Father/Daughter Retrospective
Learning to Mod Minecraft: A Father/Daughter RetrospectiveLearning to Mod Minecraft: A Father/Daughter Retrospective
Learning to Mod Minecraft: A Father/Daughter Retrospective
 
ng-owasp: OWASP Top 10 for AngularJS Applications
ng-owasp: OWASP Top 10 for AngularJS Applicationsng-owasp: OWASP Top 10 for AngularJS Applications
ng-owasp: OWASP Top 10 for AngularJS Applications
 
Make your own Print & Play card game using SVG and JavaScript
Make your own Print & Play card game using SVG and JavaScriptMake your own Print & Play card game using SVG and JavaScript
Make your own Print & Play card game using SVG and JavaScript
 
HTTP Potpourri
HTTP PotpourriHTTP Potpourri
HTTP Potpourri
 
Implementing Messaging Patterns in JavaScript using the OpenAjax Hub
Implementing Messaging Patterns in JavaScript using the OpenAjax HubImplementing Messaging Patterns in JavaScript using the OpenAjax Hub
Implementing Messaging Patterns in JavaScript using the OpenAjax Hub
 
Internationalize your JavaScript Application: Prepare for "the next billion" ...
Internationalize your JavaScript Application: Prepare for "the next billion" ...Internationalize your JavaScript Application: Prepare for "the next billion" ...
Internationalize your JavaScript Application: Prepare for "the next billion" ...
 

Recently uploaded

Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo DiehlFuture Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Peter Udo Diehl
 

Recently uploaded (20)

Introduction to Open Source RAG and RAG Evaluation
Introduction to Open Source RAG and RAG EvaluationIntroduction to Open Source RAG and RAG Evaluation
Introduction to Open Source RAG and RAG Evaluation
 
Intro in Product Management - Коротко про професію продакт менеджера
Intro in Product Management - Коротко про професію продакт менеджераIntro in Product Management - Коротко про професію продакт менеджера
Intro in Product Management - Коротко про професію продакт менеджера
 
SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...
SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...
SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...
 
Strategic AI Integration in Engineering Teams
Strategic AI Integration in Engineering TeamsStrategic AI Integration in Engineering Teams
Strategic AI Integration in Engineering Teams
 
ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...
ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...
ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...
 
What's New in Teams Calling, Meetings and Devices April 2024
What's New in Teams Calling, Meetings and Devices April 2024What's New in Teams Calling, Meetings and Devices April 2024
What's New in Teams Calling, Meetings and Devices April 2024
 
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo DiehlFuture Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
Future Visions: Predictions to Guide and Time Tech Innovation, Peter Udo Diehl
 
IoT Analytics Company Presentation May 2024
IoT Analytics Company Presentation May 2024IoT Analytics Company Presentation May 2024
IoT Analytics Company Presentation May 2024
 
Powerful Start- the Key to Project Success, Barbara Laskowska
Powerful Start- the Key to Project Success, Barbara LaskowskaPowerful Start- the Key to Project Success, Barbara Laskowska
Powerful Start- the Key to Project Success, Barbara Laskowska
 
Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...
Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...
Secure Zero Touch enabled Edge compute with Dell NativeEdge via FDO _ Brad at...
 
A Business-Centric Approach to Design System Strategy
A Business-Centric Approach to Design System StrategyA Business-Centric Approach to Design System Strategy
A Business-Centric Approach to Design System Strategy
 
UiPath Test Automation using UiPath Test Suite series, part 1
UiPath Test Automation using UiPath Test Suite series, part 1UiPath Test Automation using UiPath Test Suite series, part 1
UiPath Test Automation using UiPath Test Suite series, part 1
 
IOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptx
IOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptxIOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptx
IOS-PENTESTING-BEGINNERS-PRACTICAL-GUIDE-.pptx
 
AI revolution and Salesforce, Jiří Karpíšek
AI revolution and Salesforce, Jiří KarpíšekAI revolution and Salesforce, Jiří Karpíšek
AI revolution and Salesforce, Jiří Karpíšek
 
Buy Epson EcoTank L3210 Colour Printer Online.pdf
Buy Epson EcoTank L3210 Colour Printer Online.pdfBuy Epson EcoTank L3210 Colour Printer Online.pdf
Buy Epson EcoTank L3210 Colour Printer Online.pdf
 
AI presentation and introduction - Retrieval Augmented Generation RAG 101
AI presentation and introduction - Retrieval Augmented Generation RAG 101AI presentation and introduction - Retrieval Augmented Generation RAG 101
AI presentation and introduction - Retrieval Augmented Generation RAG 101
 
Salesforce Adoption – Metrics, Methods, and Motivation, Antone Kom
Salesforce Adoption – Metrics, Methods, and Motivation, Antone KomSalesforce Adoption – Metrics, Methods, and Motivation, Antone Kom
Salesforce Adoption – Metrics, Methods, and Motivation, Antone Kom
 
Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...
Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...
Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...
 
The Value of Certifying Products for FDO _ Paul at FIDO Alliance.pdf
The Value of Certifying Products for FDO _ Paul at FIDO Alliance.pdfThe Value of Certifying Products for FDO _ Paul at FIDO Alliance.pdf
The Value of Certifying Products for FDO _ Paul at FIDO Alliance.pdf
 
Custom Approval Process: A New Perspective, Pavel Hrbacek & Anindya Halder
Custom Approval Process: A New Perspective, Pavel Hrbacek & Anindya HalderCustom Approval Process: A New Perspective, Pavel Hrbacek & Anindya Halder
Custom Approval Process: A New Perspective, Pavel Hrbacek & Anindya Halder
 

Securing TodoMVC Using the Web Cryptography API

  • 1. Securing TodoMVC Using the Web Cryptography API Kevin Hakanson 12 September 2014
  • 2. Kevin Hakanson @hakanson +KevinHakanson hakanson
  • 3. 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
  • 4.
  • 5. Today's Session "todos" ● Review some cryptography concepts ● Look at the Web Cryptography API ● Combine these to secure TodoMVC
  • 6. 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
  • 8. TodoMVC Uses localStorage Chrome keeps localStorage in an SQLite file: OS X: ~/Library/Application Support/Google/Chrome/Default/Local Storage Windows: %HOMEPATH%AppDataLocalGoogleChromeUser DataDefaultLocal 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}]
  • 9. 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
  • 10. 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.
  • 11. 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
  • 12. Acceptance Criteria ● Enter password before accessing "todos" ● Data encrypted "at rest" in localStorage
  • 13. How?
  • 14. 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/
  • 15. 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
  • 16. 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.
  • 17. 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
  • 19. 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
  • 21. IE Platform Status Web Crypto API Note: IE11 implementation based on spec before change from CryptoOperation to Promise based API
  • 22. 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.
  • 23. 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
  • 24. 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
  • 26. 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
  • 27. 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)
  • 28. 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.
  • 29. My "Requirement" ● Avoid proving "Schneier's Law" ○ Anyone can invent a security system that he himself cannot break.
  • 31. Glossary Glossary of Key Information Security Terms http://nvlpubs.nist.gov/nistpubs/ir/2013/NIST.IR.7298r2.pdf SOURCE: NISTIR 7298
  • 32. 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
  • 33. 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
  • 34. 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
  • 35. 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
  • 36. 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
  • 37. OpenSSL Command Line $ echo -n "The quick brown fox jumps over the lazy dog" | openssl dgst -sha256 (stdin)= d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb76 2d02d0bf37c9e592
  • 38. 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 ); })); });
  • 39. 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(); }); } });
  • 40. 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
  • 41. 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
  • 42. $.Uint8Util toHexString( buf : Uint8Array ) : string fromHexString ( s : string ) : Uint8Array (still looking for a better way)
  • 43. $.WebCryptoAPI Wrapper around: ● crypto (WebCrypto spec) ● msCrypto (IE11) ● msrCrypto (Microsoft Research) ● webkitSubtle (Webkit Nightly) Implemented as jQuery Utility Plugin
  • 44. 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
  • 45. 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
  • 46. 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
  • 47. 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
  • 48. OpenSSL Command Line $ echo -n "The quick brown fox jumps over the lazy dog" | openssl dgst - sha256 -hmac "key" (stdin)= f7bc83f430538424b13298e6aa6fb143ef4d59 a14946175997479dbc2d1a3cd8
  • 49. 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 ); });
  • 50. 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 ) { })); });
  • 51. 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 ); }) })); });
  • 52. 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' ); })); });
  • 53. 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
  • 54. 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' ); }) })); });
  • 55. 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
  • 56.
  • 57. Cipher, Plaintext and Ciphertext Cipher - Series of transformations that converts plaintext to ciphertext using the Cipher Key. See Also: Inverse Cipher SOURCE: FIPS 197
  • 58. 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
  • 59. (CC BY 3.0) http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html
  • 60. Initialization Vector (IV) A vector used in defining the starting point of an encryption process within a cryptographic algorithm. SOURCE: FIPS 140-2
  • 61. OpenSSL Command Line $ echo -n "Message" | openssl enc - aes256 -K 0CD1D07EB67E19EF56EA0F3A9A8F8A7C957A2C B208327E0E536608FF83256C96 -iv 6C4C31BDAB7BAFD35B23691EC521E28D | xxd -p 23e5ebe72d99cf302c99183c05cf050a
  • 62. 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 });
  • 63. assert.promise( crypto.subtle.importKey( 'raw', keyBuf, { name: 'AES-CBC' }, true, ['encrypt', 'decrypt'] ) .then( function ( encryptionKey ) { }));
  • 64. 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 ); }); }));
  • 65. 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 ); }); }); }));
  • 66. redux chained vs. nested promises
  • 67. 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 ); }
  • 68. function compare ( decryptResult ) { var plaintext = decoder.decode( new Uint8Array( decryptResult ) ); assert.equal( plaintext, testVector.plaintext ); } assert.promise( importKey() .then( encrypt ) .then( decrypt ) .then( compare ); );
  • 69.
  • 70. Password-Based Key Derivation Functions (PBKDF) ● The randomness of cryptographic keys is essential for the security of cryptographic applications. SOURCE: SP 800-132
  • 71. 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
  • 72. 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
  • 73. 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
  • 74. 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
  • 75. 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 });
  • 76. var passwordBuf = decoder.decode( testVector.password ); assert.promise( crypto.subtle.importKey('raw', passwordBuf, 'PBKDF2', false, ['deriveKey']) .then(function ( keyResult ) { }));
  • 77. 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 ); }); }));
  • 78.
  • 79. Data Flow and Storage (expected) ● Salt = initial random ● IV = initial random ● Ciphertext = AES( todos, Key, IV )
  • 80. Data Flow and Storage (actual) ● PBKDF2 only implemented in Firefox 33 ● Substitute HMAC to generate 256 bit key ○ (do not try this at home)
  • 81. Set and Confirm Password On first use, password must be established.
  • 82. 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>
  • 83. 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(); } }
  • 84. 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.
  • 85. 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); } }
  • 86. Demo
  • 87. 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; }) }
  • 88. Convert store to return a Promise store: function (data) { if (data) { return cryptoStorage.setItem(data); } else { return cryptoStorage.getItem(); } }
  • 89. Data Flow and Storage (API)
  • 90. 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; }
  • 91. Generate Key from Password return $.WebCryptoAPI.subtle.importKey('raw', this.salt, hmacSha256, true, ['sign', 'verify']) .then(function (keyResult) { });
  • 92. Generate Key from Password return $.WebCryptoAPI.subtle.importKey('raw', this.salt, hmacSha256, true, ['sign', 'verify']) .then(function (keyResult) { return $.WebCryptoAPI.subtle.sign(hmacSha256, keyResult, buf) .then(function (signResult) { }); });
  • 93. Generate Key from Password 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) { }); }); });
  • 94. Generate Key from Password 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([]); } }); }); });
  • 95. 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)); });
  • 96. 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); });
  • 97. Encrypted in localStorage sqlite> select * from ItemTable; todos-jquery-webcryptoapi|{"salt":" 455ad2b6be02de86226c8c10fe32ebfc439b197f6b0a7918 94dab6f6920e22ff","iv":" 29b0b0092148e5fa849931c821627ea7","ciphertext":" b1770f7729edde3736eb4c26df6f4a56a236e01c525e157c f24ce76b35f8c622845a97f561c2c13942677db765e0638e f2ed90f191d5d57dcfc22fc7cd4c712716fc0e6e8748fb95 0430cb11a7e5c66ca4c55df4d88551d5b88666cd7fa4330c 85f20e88e30da752adf24ad71913bfb9"}
  • 98.