Exploring Android JCA Backend for package:webcrypto via JNI

72 views
Skip to first unread message

Sagar Halladakeri

unread,
Mar 20, 2026, 12:14:01 PM (10 days ago) Mar 20
to dart-gsoc

I built a small Flutter Android app to explore using Android’s Java Cryptography Architecture (JCA) directly from Dart using package:jnigen.

The goal was to understand whether a JCA-based backend could be a viable alternative to the current BoringSSL-based implementation in package:webcrypto.

 What this demonstrates
Calling Java crypto APIs from Dart via JNI (jnigen)
- SHA-256 using MessageDigest
- Safe byte conversion (Uint8List  byte[])
- Validation against package:webcrypto
- Simple performance comparison

 Architecture (data flow)

Dart (Uint8List) -> JNI bindings (jnigen) -> Java wrapper->Android JCA (MessageDigest)  -> back to Dart (Uint8List)

Screenshot 2026-03-20 at 4.41.10 PM.png

Why the outputs match

SHA-256 is a standardized algorithm (FIPS 180-4).

As long as:

- input encoding is identical (UTF-8)
- no mutation happens across JNI

different implementations (JCA vs BoringSSL) will always produce the same result.

 Benchmark (500 iterations)

JCA via JNI: ~7 ms (~0.01 ms/op)
- webcrypto: ~2 ms (~0.00 ms/op)

Observation:

- JNI overhead is visible for small inputs
- For larger inputs, the gap becomes smaller

 Key implementation

Java (JCA):Screenshot 2026-03-20 at 4.53.46 PM.png

Dart (JNI wrapper):

Screenshot 2026-03-20 at 4.54.06 PM.png

 What’s interesting
JNI adds overhead, mainly from boundary crossing and data conversion
- JCA APIs are synchronous, which doesn’t align perfectly with Dart’s async model
- Wrapper/API design becomes important for usability and performance

 Why this approach matters

Using platform crypto instead of bundling BoringSSL:

- Enables hardware-backed security (Keystore / StrongBox)
- Helps with compliance requirements (FIPS environments)
- Can reduce binary size

Questions

Would a thin mapping over JCA APIs be preferable, or should the backend introduce batching to reduce JNI crossings?
- How should the sync nature of JCA APIs be exposed in Dart (sync wrapper vs async abstraction)?
- Are there known pitfalls with JNI overhead for crypto workloads at scale?



Hamdaan Ali

unread,
Mar 22, 2026, 6:40:53 PM (8 days ago) Mar 22
to dart-gsoc
Hey Sagar,

Thanks for sharing the prototype.

I would also like to better understand the ownership model of the Java-to-Dart byte-copy path. In particular, for the getRange(... allocator: malloc) call in your Dart snippet, can you clarify how the temporary buffer is reclaimed, or point to the relevant package:jni behavior if that is handled for you? I want to make sure the benchmark and the prototype are not accidentally depending on a leaky copy path.

The benchmark is a good first datapoint, but I would be careful not to draw broader conclusions from one small-input SHA-256 case. I also wouldn't put much weight on single-device or emulator benchmark numbers, since JCA/provider performance may vary significantly across devices and Android versions, so the methodology and performance patterns matter more than any single result.

The next thing that would strengthen this substantially would be:
                                                                                                                                        
  - state the exact build mode and device/provider used,                                                                                          
  - run an input-size sweep,
  - and extend the prototype one step beyond plain digest while staying aligned with the existing backend contract.

Perhaps that could spark broader performance or architecture questions. Feel free to email your proposal draft.

Best,
Hamdaan
Reply all
Reply to author
Forward
0 new messages