Web Crypto API

Sung
Monomax Software
https://monomax.sh

Web Crypto API

  1. Introduction
  2. Real life example

1. Introduction

Web Crypto API

A standardized JavaScript interface for accessing cryptographic primitives

W3C published a recommendation in January 2017

All major browsers implement it

https://caniuse.com/#search=crypto

Browser crypto implementations

  • Firefox - NSS (Network Security Services)
  • Chrome - BoringSSL

An interface for the underlying implementations

window.crypto.subtle

  • low-level cryptography APIs.
  • very easy to misuse.
window.crypto.subtle.generateKey(
    {
        name: "HMAC",
        hash: {name: "SHA-256"},
    },
    false,
    ["sign", "verify"]
)
.then(function(key){
    console.log(key);
})
.catch(function(err){
    console.error(err);
});
window.crypto.subtle.sign(
    {
        name: "HMAC",
    },
    key,
    data // ArrayBuffer of data you want to sign
)
.then(function(signature){
    //an ArrayBuffer containing the signature
    console.log(new Uint8Array(signature));
})
.catch(function(err){
    console.error(err);
});
window.crypto.subtle.encrypt(
    {
      name: 'AES-GCM',
      iv: someRandomBytes
    },
    cipherKey,
    dataBuf
).then(function(ciphertext){
    console.log(ciphertext);
})
.catch(function(err){
    console.error(err);
});

window.crypto.subtle

  • decrypt()
  • deriveBits()
  • deriveKey()
  • digest()
  • encrypt()
  • exportKey()
  • generateKey()
  • importKey()
  • sign()
  • unwrapKey()
  • verify()
  • wrapKey()

2. Real life examples

NAD

An open source, encrypted notebook that respects your privacy

https://www.nadproject.com

NAD

Design goal

  • Clients can write encrypted notes on the server
  • Server cannot decrypt the notes

My solution (registration)

  • User enters email and password
  • Derive k <- pbkdf2(password: password, salt: email, alg: 'sha256', iteration: 10000)
  • Derive k0 <- hkdf(alg: 'sha256', secret: k, salt: email, info: 'enc') (used as encryption key)
  • Derive k1 <- hkdf(alg: 'sha256', secret: k, salt: email, info: 'auth') (sent to server for authentication)
  • POST to '/register': (email, k1, kdf_iteration)
  • Generate salt <- 256bit random string
  • Derive k2 <- pbkdf2(password: k1, salt: salt, alg: 'sha256', iteration: 20000)
  • Create a user with (hashed_password, salt) = (k2, salt)

Crash course in cryptography

Key-derivation function

A function to derive multiple keys from a single source key

Pseudo-random function

Defined over space (K, X, Y)

K x X -> Y such that there exists "efficient" algorithm to evaluate F(k,x)

Pseudo-random?

Nothing is truly random. Approximates the randomness.

Problem with PRF

  • Source key is not uniform (e.g. passwords are often dictionaries)
  • PRF is not psuedo-random if key is not uniform

Extract-then-expand

  1. Extract: extract a psuedo-random key, k, from the source key
  2. Expand: Expand k using the pseudo-random function

HKDF (KDF from HMAC)

  • Extract: k <- HMAC(salt, SK)
  • Expand using HMAC as PRF using k

PBKDF (Password-based KDF)

  • Defends against dictionary attack
  • Salt, slow hash function
  • bcrypt, scrypt, argon, ...

Registering on www.nadproject.com

  • User enters email and password
  • Derive k <- pbkdf2(password: password, salt: email, alg: 'sha256', iteration: 10000)
  • Derive k0 <- hkdf(alg: 'sha256', secret: k, salt: email, info: 'enc') (used as encryption key)
  • Derive k1 <- hkdf(alg: 'sha256', secret: k, salt: email, info: 'auth') (sent to server for authentication)
  • POST to '/register': (email, k1, kdf_iteration)

  • Generate salt <- 256bit random string
  • Derive k2 <- pbkdf2(password: k1, salt: salt, alg: 'sha256', iteration: 20000)
  • Create a user with (hashed_password, salt) = (k2, salt)
function pbkdf2(secret, salt, iterations) {
  const secretBuf = strToBuffer(secret);
  const saltBuf = strToBuffer(salt);

  const key = await window.crypto.subtle.importKey(
    'raw',
    secretBuf,
    { name: 'PBKDF2' },
    false,
    ['deriveBits']
  );

  return window.crypto.subtle.deriveBits(
    {
      name: 'PBKDF2',
      salt: saltBuf,
      iterations,
      hash: { name: SHA256 }
    },
    key,
    256
  );
}
function strToBuffer(str) {
  // convert to utf8 encoding
  const strUtf8 = unescape(encodeURIComponent(str));

  const buf = new ArrayBuffer(strUtf8.length);
  const bufView = new Uint8Array(buf);
  for (let i = 0; i < strUtf8.length; i++) {
    bufView[i] = strUtf8.charCodeAt(i);
  }

  return buf;
}

What is ArrayBuffer?

Fixed-size raw binary data buffer

// create an ArrayBuffer with a size in bytes
var buffer = new ArrayBuffer(8);

console.log(buffer.byteLength);
// expected output: 8

Viewed and manipulated using a mask

function bufToB64(buf) {
  let binary = '';
  const bytes = new Uint8Array(buf);
  const len = bytes.byteLength;

  for (let i = 0; i < len; i++) {
    binary += String.fromCharCode(bytes[i]);
  }

  return window.btoa(binary);
}
function pbkdf2(secret, salt, iterations) {
  const secretBuf = strToBuffer(secret);
  const saltBuf = strToBuffer(salt);

  const key = await window.crypto.subtle.importKey(
    'raw',
    secretBuf,
    { name: 'PBKDF2' },
    false,
    ['deriveBits']
  );

  return window.crypto.subtle.deriveBits(
    {
      name: 'PBKDF2',
      salt: saltBuf,
      iterations,
      hash: { name: SHA256 }
    },
    key,
    256
  );
}
async function hkdf(secret, salt, info, algorithm, dkLen) {
  let secretBuf;
  if (typeof secret === 'string') {
    secretBuf = strToBuffer(secret);
  } else {
    secretBuf = secret;
  }

  const saltBuf = strToBuffer(salt);
  const infoBuf = strToBuffer(info);

  const key = await window.crypto.subtle.importKey(
    'raw',
    secretBuf,
    {
      name: 'HKDF'
    },
    false,
    ['deriveBits']
  );

  return window.crypto.subtle.deriveBits(
    {
      name: HKDF,
      hash: algorithm,
      salt: saltBuf,
      info: infoBuf
    },
    key,
    dkLen
  );
}
async function makeKeys(email, password, iteration) {
  const masterKey = await pbkdf2(password, email, iteration);
  const encKeyBits = await hkdf(
    masterKey, email, 'enc', SHA256, 256
  );
  const authKeyBits = await hkdf(
    masterKey, email, 'auth', SHA256, 256
  );

  return {
    encKey: bufToB64(encKeyBits),
    authKey: bufToB64(authKeyBits)
  };
}

...And more

symmetric block cipher using AES256 in GCM mode

Open source

https://www.nadproject.com

Wrap up - Web Crypto API

  1. Introduction
  2. Real life examples

Conclusion

  • A unified JavaScript interface for accessing cryptographic primitives
  • All major browser vendors implement it

NAD Project

  • Learn cryptography in JS together.
  • It's open source.
  • The future is private.

Links

Thanks

Sung
Monomax Software
sung@monomax.sh
https://monomax.sh