Exploring Android JCA backend for package:webcrypto

44 views
Skip to first unread message

Harshita Yadav

unread,
Mar 19, 2026, 12:36:45 PM (11 days ago) Mar 19
to dart-gsoc

Hi Hamdaan and Jonas,

I came across the GSoC idea about building an Android JCA backend for package:webcrypto and I would love to contribute to this .

I’ve been looking into the idea of Android JCA backend for package:webcrypto . I tried a small experiment locally where a Flutter app calls into Java’s cryptography APIs using JNI (via jnigen).

Since the project revolves around replacing the current native backend for operations like digests, I started with a simple SHA-256 example from Dart:

1. Computing SHA-256 using package:webcrypto (existing backend)

Screenshot 2026-03-19 at 9.54.38 PM.png

which gives :

Screenshot 2026-03-19 at 9.55.38 PM.png

I then wired this through a small Dart wrapper that calls into the generated JNI bindings:

2. Dart wrapper over generated JNI bindings

Screenshot 2026-03-19 at 9.56.39 PM.png

Output :

Screenshot 2026-03-19 at 9.55.38 PM.png

3. On the Java side, I mapped this to the Android JCA implementation using MessageDigest:

Screenshot 2026-03-19 at 9.59.21 PM.png

To check consistency with the existing API, I compared the result with the current webcrypto digest output:

4. Correctness comparison between JCA backend and existing webcrypto

Screenshot 2026-03-19 at 10.00.19 PM.png

which confirms both backends agree:

Screenshot 2026-03-19 at 10.01.06 PM.png

5. I also tried a quick HMAC-SHA256 example to see how key-based operations behave:

Screenshot 2026-03-19 at 10.01.47 PM.png

which gives :

Screenshot 2026-03-19 at 10.02.33 PM.png

Running this end-to-end helped me understand how data flows across Dart → JNI → Java → back, and where the main constraints show up.

While experimenting, a few things became noticeable. The conversion between Uint8List and byte[] introduces some overhead, especially for smaller inputs. Also, since the Java APIs are synchronous, it feels slightly different from Dart’s async model, which makes the wrapper design an interesting part of the problem.


6. Micro-benchmark: JCA vs native backend

Screenshot 2026-03-19 at 10.03.17 PM.png

Screenshot 2026-03-19 at 10.03.43 PM.png

To get a rough sense of performance, I ran a small comparison:

SHA-256 (~5 bytes)

  JNI (JCA):          ~0.18 ms avg

  Native (webcrypto): ~0.05 ms avg


SHA-256 (1 KB)

  JNI (JCA):          ~0.22 ms avg

  Native (webcrypto): ~0.09 ms avg


SHA-256 (100 KB)

  JNI (JCA):          ~1.90 ms avg

  Native (webcrypto): ~1.60 ms avg



From this, it seems the JNI overhead is more visible for small inputs, while for larger inputs the difference becomes smaller.

One thing I’m currently unsure about is how the Android backend should ideally be structured. Would it make more sense to keep it as a thin mapping over the Java APIs, or to introduce a slightly higher-level layer that batches operations to reduce JNI crossings?

If it would help, I’d be happy to extend this into a slightly more complete prototype (for example adding AES-GCM or exploring key storage) and share what I find.

Best,
Harshita Yadav


Reply all
Reply to author
Forward
0 new messages