Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
2 tasks done
marc-wittwer opened this issue Dec 29, 2024 · 0 comments

Comments

@marc-wittwer
Copy link

marc-wittwer commented Dec 29, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant