Skip to content

Instantly share code, notes, and snippets.

@QingpingMeng
Created June 30, 2020 04:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save QingpingMeng/2313a08f6d86a4c1ac7eae7e52b2e54b to your computer and use it in GitHub Desktop.
Save QingpingMeng/2313a08f6d86a4c1ac7eae7e52b2e54b to your computer and use it in GitHub Desktop.
AEAD
function longToByteArray(/*long*/ long: number): Uint8Array {
  const byteArray = new Uint8Array(8)
  for (let index = 0; index < byteArray.length; index++) {
    // tslint:disable-next-line: no-bitwise
    const byte = long & 0xff
    byteArray[index] = byte
    // tslint:disable-next-line: no-bitwise
    long = (long - byte) >> 8
  }
  return byteArray
}

function arrayBufferToBase64(buffer: ArrayBuffer): string {
  let binary = ''
  const bytes = new Uint8Array(buffer)
  const len = bytes.byteLength
  for (let i = 0; i < len; i++) {
    binary += String.fromCharCode(bytes[i])
  }
  return window.btoa(binary)
}

function base64UrlEncoded(input: ArrayBuffer): string {
  const base64Encoded = arrayBufferToBase64(input)
  return base64Encoded
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/\=+$/, '')
}

async function getAESKey(): Promise<CryptoKey> {
  const aesKey = await window.crypto.subtle.generateKey(
    {
      name: 'AES-CBC',
      length: 256
    },
    true,
    ['encrypt', 'decrypt']
  )
  return aesKey
}

async function exportCryptoKey(key: CryptoKey): Promise<Uint8Array> {
  const keyBuffer = await window.crypto.subtle.exportKey('raw', key)
  return new Uint8Array(keyBuffer)
}

async function getHMACKey(): Promise<CryptoKey> {
  const rawSignKey = window.crypto.getRandomValues(new Uint8Array(32))
  const hmacKey = await window.crypto.subtle.importKey(
    'raw',
    rawSignKey,
    {
      name: 'HMAC',
      hash: { name: 'SHA-512' }
    },
    true,
    ['sign']
  )
  return hmacKey
}

async function getRsaPublicKey(jwk: JsonWebKey): Promise<CryptoKey> {
  const importedKey = await window.crypto.subtle.importKey(
    'jwk',
    jwk,
    {
      name: 'RSA-OAEP',
      hash: { name: 'sha-256' }
    },
    false,
    ['encrypt']
  )
  return importedKey
}

async function getEncryptedKeys(rsaPublicKey: CryptoKey, hmacKey: CryptoKey, aesKey: CryptoKey): Promise<ArrayBuffer> {
  const exportedAESKey = await exportCryptoKey(aesKey)
  const exportedHMACKey = await exportCryptoKey(hmacKey)
  const keysBuffer = new Uint8Array(64)
  keysBuffer.set(exportedHMACKey, 0)
  keysBuffer.set(exportedAESKey, 32)
  const encryptedKeys = await window.crypto.subtle.encrypt(
    {
      name: 'RSA-OAEP'
    },
    rsaPublicKey,
    keysBuffer
  )
  return encryptedKeys
}

async function getAuthenticationTag(
  hmacKey: CryptoKey,
  a: Uint8Array,
  iv: Uint8Array,
  e: ArrayBuffer
): Promise<Uint8Array> {
  const al = longToByteArray(a.byteLength * 8).reverse()
  const signaturePayload = new Uint8Array(a.byteLength + iv.byteLength + e.byteLength + al.length)
  signaturePayload.set(a, 0)
  signaturePayload.set(iv, a.byteLength)
  signaturePayload.set(new Uint8Array(e), iv.byteLength + a.byteLength)
  signaturePayload.set(al, 16 + a.byteLength + e.byteLength)

  const signedBuffer = await window.crypto.subtle.sign('HMAC', hmacKey, signaturePayload)
  return new Uint8Array(signedBuffer).slice(0, 32)
}

export const encryptMessage = async (data: string, jwk: JsonWebKey): Promise<string> => {
  const enc = new TextEncoder()
  const p = enc.encode(data)
  const protocalHeader = {
    alg: 'RSA-OAEP-256',
    enc: 'A256CBC-HS512',
    typ: 'JWT',
    jwk: jwk
  }
  const aesKey = await getAESKey()
  const hmacKey = await getHMACKey()
  const rsaPublicKey = await getRsaPublicKey(jwk)
  const iv = crypto.getRandomValues(new Uint8Array(16))
  const e = await window.crypto.subtle.encrypt(
    {
      name: 'AES-CBC',
      iv
    },
    aesKey,
    p
  )
  const encryptedKeys = await getEncryptedKeys(rsaPublicKey, hmacKey, aesKey)
  const encodedProtocolHeader = enc.encode(JSON.stringify(protocalHeader))
  const a = enc.encode(base64UrlEncoded(encodedProtocolHeader.buffer as ArrayBuffer))
  const authenticationTag = (await getAuthenticationTag(hmacKey, a, iv, e)).buffer
  const jwe =
    base64UrlEncoded(encodedProtocolHeader.buffer as ArrayBuffer) +
    '.' +
    base64UrlEncoded(encryptedKeys) +
    '.' +
    base64UrlEncoded(iv.buffer as ArrayBuffer) +
    '.' +
    base64UrlEncoded(e) +
    '.' +
    base64UrlEncoded(authenticationTag as ArrayBuffer)
  return jwe
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment