Hi Hamdaan and Jonas,
I’m Tushar, and I’m interested in the “Android JCA backend for package:webcrypto” GSoC project.
To better understand the feasibility, I built a prototype using package:jnigen that integrates Java’s JCA APIs with Dart via JNI. I implemented SHA-256 and HMAC-SHA256 and compared them against the existing webcrypto backend.
From my initial evaluation:
Outputs match exactly (correctness verified)
Benchmarks were averaged over multiple runs
For small inputs, the existing backend performs better (due to JNI overhead)
For larger inputs (~1MB), the JCA backend shows significantly better performance (~7x faster)
Summary (1MB input):
JCA → ~1.8 ms
WebCrypto → ~14.2 ms
Speedup → ~7–7.5x
This helped me better understand the trade-offs between JNI overhead and native execution across different workloads.
On the Java side, I used standard JCA primitives exposed via JNI. For example:
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
byte[] result = mac.doFinal(data);
I’d appreciate your guidance on the next step — would you prefer integrating this as an alternative backend first, or directly working toward replacing the current Android implementation?
Additionally, from a GSoC perspective, I’d love to understand what would be a reasonable and impactful target for a ~3-month timeline (e.g., covering a subset like hashing + HMAC first vs. aiming for a broader API surface including AES/RSA and key management).
Looking forward to your feedback.
Best regards,
Tushar

Hi Hamdaan and Jonas,
Thank you again for your guidance — I’ve been iterating on the prototype to better understand the performance characteristics of the JNI-based JCA backend. Based on your suggestions, I structured the evaluation into two phases and refined the benchmarking methodology.
Phase 1: Validation and Rigorous Benchmarking
(Initial validation – low iteration / warmup)
In the initial phase, I used a lighter configuration to validate correctness and get a directional understanding:
Outputs matched exactly with the WebCrypto backend (correctness verified)
JNI overhead dominates for small inputs
JCA begins to outperform WebCrypto as input size increases
As you pointed out, this setup was not sufficiently rigorous.
(Improved methodology – release mode + higher iterations)
I refined the benchmarking setup as follows:
Run in release mode
Device: Moto G85 5G (Android 15)
10 warmup iterations + 100 measured iterations
Timing via Dart Stopwatch (µs precision)
Measurements include end-to-end cost (JNI calls + Dart ↔ Java data conversion)
How benchmarks were computed:
Warmup runs are discarded to stabilize runtime (reduce cold-start effects)
100 measured iterations per operation and input size
Each iteration performs the full operation including JNI boundary crossing
Reported values are average latency (µs)
Min/max and variance are tracked internally for consistency
Identical input data and keys are reused to avoid randomness bias
Benchmark scope:
Input sizes: 1 KB → 1 MB
Operations:
SHA-256, SHA-512
HMAC-SHA256
AES-GCM (encrypt/decrypt)
AES-256 key generation
Summary of observations (based on measured results):
Small inputs (~1 KB):
SHA-256: comparable (~41 µs vs 42 µs)
HMAC / SHA-512: WebCrypto slightly faster
AES: JNI overhead dominates heavily (JCA ~400–500 µs vs Web ~5–6 µs)
Medium inputs (~10 KB):
SHA-256: ~4.2× faster
SHA-512: ~3× faster
HMAC-SHA256: ~3.1× faster
AES still dominated by initialization + JNI overhead
Large inputs (~100 KB – 1 MB):
SHA-256: up to ~12–14× faster
HMAC-SHA256: up to ~14× faster
SHA-512: ~6–8× faster
These results clearly show that JNI overhead is amortized with larger inputs, allowing JCA’s native execution to significantly outperform the current backend for throughput-heavy workloads.
Observation on AES behavior:
AES-GCM performance is currently significantly worse than WebCrypto across all input sizes:
Example (1 MB):
JCA: ~371 ms
WebCrypto: ~2.5 ms
This appears to be dominated by:
Repeated Cipher.getInstance and init calls
JNI data transfer overhead
Lack of object reuse
Observation on scaling limits:
When running the full benchmark suite continuously (all operations × sizes × 100 iterations), I observed instability/crashes at larger sizes (≥1 MB).
This is likely due to:
Frequent JNI crossings with large buffers
Repeated allocation and copying (JByteArray ↔ Uint8List)
High cumulative workload leading to memory pressure and GC activity
Interpretation:
Hashing and HMAC scale well and benefit significantly from native execution
AES performance is currently limited by initialization and data transfer costs
For large workloads, execution strategy (batching, reuse, reduced allocations) becomes critical
Next steps (ongoing work):
I am continuing to explore improvements, including:
Scaling iteration counts based on input size to improve stability
Investigating AES optimizations (object reuse vs per-call initialization)
Running additional analysis in Flutter profile mode (--profile)
Evaluating approaches to reduce JNI overhead (buffer reuse / batching)
I am also reviewing the existing backend contract (impl_interface.dart) to ensure that this approach can align with the current structure rather than introducing a separate abstraction.
Additionally, I will be extending evaluation to more representative operations (e.g., asymmetric crypto and key management) and will share a follow-up update soon with those results.
Please let me know if you would prefer:
continuing with this broader benchmarking approach while stabilizing execution, or
focusing more deeply on specific primitives (e.g., AES behavior, key management, or asymmetric crypto)
Happy to iterate further based on your feedback.
Best regards,
Tushar