Skip to content

✨Support for crypto.subtle.generateKey("AES-GCM") & crypto.subtle.importKey("pkcs8"+RSA-OAEP) #569

@marc-wittwer

Description

@marc-wittwer

What feature or enhancement are you suggesting?

This repo looks great. Thanks for all the work!

I'm currently on a mission to implement our encryption functions that work in the react webapp and NodeJS in our react native app. These functions use the node:crypto and Web Crypto API.

We use the following functions from the crypto.subtle.

Operation Algorithm Supported
crypto.subtle.importKey("raw", "AES-GCM") AES-GCM
crypto.subtle.generateKey("AES-GCM") AES-GCM
crypto.subtle.exportKey("raw") AES-GCM
crypto.subtle.encrypt("AES-GCM") AES-GCM
crypto.subtle.decrypt("AES-GCM") AES-GCM
crypto.subtle.generateKey("RSA-OAEP") RSA-OAEP
crypto.subtle.importKey("spki", "RSA-OAEP") RSA-OAEP
crypto.subtle.importKey("pkcs8", "RSA-OAEP") RSA-OAEP
crypto.subtle.encrypt("RSA-OAEP") RSA-OAEP
crypto.subtle.decrypt("RSA-OAEP") RSA-OAEP
crypto.subtle.exportKey("spki", publicKey) RSA-OAEP
crypto.subtle.exportKey("pkcs8", privateKey) RSA-OAEP
crypto.subtle.importKey("raw", "PBKDF2") PBKDF2
crypto.subtle.deriveBits("PBKDF2") PBKDF2

The only two function that we are missing are:

cryptoKey = await crypto.subtle.generateKey(
        {
          name: "AES-GCM",
          length: 256,
        },
        true,
        ["encrypt", "decrypt"],
      )
await crypto.subtle.importKey(
        "pkcs8",
        arrayBuffer,
        { name: "RSA-OAEP", hash: "SHA-256" },
        true,
        ["decrypt"],
      )

What Platforms whould this feature/enhancement affect?

iOS, Android

Alternatives/Workarounds

The current workaround is using the react-native-webview-crypto which works. This brings window.crypto.subtle to your React Native application. It does this by communicating with a hidden WebView, which performs the actual computation.

However, this does not seem great.

Additional information


I saw this comment. I can try to setup a unit test that uses the two missing functions.

Unit test: `importKey("RSA-OAEP")`
import { decode, encode } from "js-base64"

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

const base64ToUint8Array = (base64: string) => {
  const binaryString = atob(base64)
  const bytes = new Uint8Array(binaryString.length)
  for (let i = 0; i < binaryString.length; i++) {
    bytes[i] = binaryString.charCodeAt(i)
  }
  return bytes
}

export const testImportKeyRSAOAEP = async () => {
  const publicKeyBase64 = `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Yxs1i4Z5HzsewxTAAhQ8hMrmlzMvQ4EL1+grkjWvhGJAFjxK9OapVQJ7yOhovt4HB8x6EY0na2JF9X/z82HcNVaO5twL1y50783WIHHoO5Z5VgK6LLF/HdhlqXqSO9LuqpSmlobv+YSLM09phpOmZ2y4IBiD08AROIW0qAmOjXZjfyqxc9I2ZQRB89Ek5VBnaimnFNto06FMei2rPLplD0Ez05Xrib44LlS4ofmbAkQsjplnNqMZHj1kaErzHqxBAWZyna9J8V3evUOwlUvSJUsyFfR869UQtgSqDhW6m/IDBQIT1PLov9nLExVkF5CJzuty4gIbW9eqxHeO7fGiwIDAQAB`
  const publicKeyUint8array = base64ToUint8Array(publicKeyBase64)
  const publicKeyArrayBuffer = publicKeyUint8array.buffer
  const encryptorKey = await crypto.subtle.importKey(
    "spki",
    publicKeyArrayBuffer,
    { name: "RSA-OAEP", hash: "SHA-256" },
    true,
    ["encrypt"],
  )

  const data = "Hello World!"
  const base64Data = encode(data) // utf8ToBase64(data)
  const dataUint8array = base64ToUint8Array(base64Data)
  const dataArrayBuffer = dataUint8array.buffer

  const encryptedDataRSA = await crypto.subtle.encrypt(
    { name: "RSA-OAEP" },
    encryptorKey,
    dataArrayBuffer,
  )

  const privateKeyBase64 = `MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDRjGzWLhnkfOx7DFMACFDyEyuaXMy9DgQvX6CuSNa+EYkAWPEr05qlVAnvI6Gi+3gcHzHoRjSdrYkX1f/PzYdw1Vo7m3AvXLnTvzdYgceg7lnlWArossX8d2GWpepI70u6qlKaWhu/5hIszT2mGk6ZnbLggGIPTwBE4hbSoCY6NdmN/KrFz0jZlBEHz0STlUGdqKacU22jToUx6Las8umUPQTPTleuJvjguVLih+ZsCRCyOmWc2oxkePWRoSvMerEEBZnKdr0nxXd69Q7CVS9IlSzIV9Hzr1RC2BKoOFbqb8gMFAhPU8ui/2csTFWQXkInO63LiAhtb16rEd47t8aLAgMBAAECggEAITsMs3aCIqrw8Z6Ftxaah5kkrAkVatHDNiQLHjhs3Z14RXbVYCbhemB2ZtcWtfr9FDCaQISJqYuwlvgX5kNovCsJcTR4OPqSeZL0WvPRzaKe3PD2Yeqf3Satci+DlOdl8gc6rEGn7um0bihqI2I+nrvUdyfE5TqZB1N3XRWKmmZQS72ZJzpK1iZgzqyFBdp4UuhFaN/V+LZXCnIaglir4Td0VIOl71vDZkRPdtmy8jupYxbCk/B3DEKWDSadZgJvWBtTNJp38K8g6C2FbEmXwYqMP6fUp2C3Dec9+rSL/eFxV24lmnHjVyZtbr+JvjgZJWkq1GbVOszbms3Kl8uyEQKBgQDtLDS5R312BCVo7Sha2NN3vvsF89ErtdjDpASb9psKyVBkU32MBPOCZRXYa+WHvATNORHuYDU0V/nVrfZ/UsGlEf2c7osRGBTB32//XcszB6OqDBJlMV2zstDL6XatjiMJ8mUNR7LlzxfC/BpyvQ2Zotnm7c0ahVCiKrMbVvKsiQKBgQDiLthAl0kT4Dc8XNR9VvX0ZmK9FHb0i4pOIlgligzvLPnXPcSqYgv1iK0RG8mdFP4jupOyaQgYAzHYr06vOAiTFnIFSPMfSS35F27A3ukDsHHffpDwVn7j3dVf+DW8HpxSrvc//Baxb0OtOU816kBXyR89bi4qrsygxjEoYzLdcwKBgBysMnePyAAjgi5MNYu+GNqqMQjIMCp7oogMZS5Bwv6r1dc7LLtnwdSqydhPOwGM3nu9AYjzApugYyjNDjbYV2bQZPu67v8TDTde/tg9i5pQux2MthCbxjs6S/nK8LkMrPm/3y2a1Grp/XJqLfxfFKzVPkinyRsCsPvZ86tDeLUZAoGBAJoJ3zs2DQ3dQKD6c7ic9cqpxAsTmeP3+Iw39aIzP5XQIqMFLSAAwDZLC9q/+vHg7ye0FIyH3XxFCLiSw9qvJZ/OxH527STceNPQspvl8/mQPC1CjEEyFx7m4D+I0ke47Suef0LzUx0qMoQRqLGGRKXEkmMK26Q0AaZo8+eWj3ijAoGAcUm27e9/TzyUmegV/4L/oMxnbLKoaedvOVEFm2fa0VuwfzNPNXtol2vZoTejJqnpEDEaEmRxN3mabapmmltVrfZes4KfGYDvjK9phSPnT/0LJNpbgu6su+SX/AB42o5pC6ckoh4fpFr8RsnU0qWvFcHtzko2l3rcfs5j7il3djU=`
  const uint8array = base64ToUint8Array(privateKeyBase64)
  const arrayBuffer = uint8array.buffer

  const key = await crypto.subtle.importKey(
    "pkcs8",
    arrayBuffer,
    { name: "RSA-OAEP", hash: "SHA-256" },
    true,
    ["decrypt"],
  )
  // expect(key).toBeDefined()
  // expect(key.type).toBe("private")
  // expect(key.algorithm.name).toBe("RSA-OAEP")
  // expect(key.usages).toContain("decrypt")

  const decryptedDataRSA = await crypto.subtle.decrypt({ name: "RSA-OAEP" }, key, encryptedDataRSA)

  // decode() is base64ToUtf8()
  const decryptedDataUTF8 = decode(arrayBufferToBase64(decryptedDataRSA))
  console.log(decryptedDataUTF8)

  // expect(decryptedDataUTF8).toBe(data)
}
Unit test: `generateKey("AES-GCM")`
import { decode, encode } from "js-base64"

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

export const testGenerateKeyAESGCM = async () => {
  const cryptoKey = await crypto.subtle.generateKey(
    {
      name: "AES-GCM",
      length: 256,
    },
    true,
    ["encrypt", "decrypt"],
  )
  expect(cryptoKey).toBeDefined()
  expect(cryptoKey.type).toBe("secret")
  expect(cryptoKey.algorithm.name).toBe("AES-GCM")

  const aesKeyBuffer = await crypto.subtle.exportKey("raw", cryptoKey)
  expect(aesKeyBuffer).toBeInstanceOf(ArrayBuffer)
  expect(aesKeyBuffer.byteLength).toBe(32) // 256 bits = 32 bytes

  const aesKeyBase64 = arrayBufferToBase64(aesKeyBuffer)
  expect(typeof aesKeyBase64).toBe("string")
  expect(aesKeyBase64.length).toBeGreaterThan(0)
}

However, regarding the actual implementation of the missing functions generateKey("AES-GCM") and importKey("pkcs8"+RSA-OAEP) I wouldn't know where to start.

Let me know how I could help getting these functions supported by react-native-quick-crypto.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions