diff --git a/native/android/app/src/cpp/AESCrypto.cpp b/native/android/app/src/cpp/AESCrypto.cpp
--- a/native/android/app/src/cpp/AESCrypto.cpp
+++ b/native/android/app/src/cpp/AESCrypto.cpp
@@ -1,20 +1,84 @@
+#include "jniHelpers.h"
 #include <Tools/AESCrypto.h>
+#include <fbjni/ByteBuffer.h>
+#include <fbjni/fbjni.h>
+
+using namespace facebook::jni;
+
+class AESCryptoJavaClass : public JavaClass<AESCryptoJavaClass> {
+public:
+  // app.comm.android.aescrypto.AESCryptoModuleCompat
+  static auto constexpr kJavaDescriptor =
+      "Lapp/comm/android/aescrypto/AESCryptoModuleCompat;";
+
+  static void generateKey(rust::Slice<uint8_t> buffer) {
+    local_ref<JByteBuffer> byteBuffer =
+        JByteBuffer::wrapBytes(buffer.data(), buffer.size());
+
+    static const auto cls = javaClassStatic();
+    static auto method =
+        cls->getStaticMethod<void(local_ref<JByteBuffer>)>("generateKey");
+    method(cls, byteBuffer);
+  }
+
+  static void encrypt(
+      rust::Slice<uint8_t> key,
+      rust::Slice<uint8_t> plaintext,
+      rust::Slice<uint8_t> sealedData) {
+    local_ref<JByteBuffer> keyBuffer =
+        JByteBuffer::wrapBytes(key.data(), key.size());
+    local_ref<JByteBuffer> plaintextBuffer =
+        JByteBuffer::wrapBytes(plaintext.data(), plaintext.size());
+    local_ref<JByteBuffer> sealedDataBuffer =
+        JByteBuffer::wrapBytes(sealedData.data(), sealedData.size());
+    static const auto cls = javaClassStatic();
+    static auto method = cls->getStaticMethod<void(
+        local_ref<JByteBuffer>,
+        local_ref<JByteBuffer>,
+        local_ref<JByteBuffer>)>("encrypt");
+    method(cls, keyBuffer, plaintextBuffer, sealedDataBuffer);
+  }
+
+  static void decrypt(
+      rust::Slice<uint8_t> key,
+      rust::Slice<uint8_t> sealedData,
+      rust::Slice<uint8_t> plaintext) {
+    local_ref<JByteBuffer> keyBuffer =
+        JByteBuffer::wrapBytes(key.data(), key.size());
+    local_ref<JByteBuffer> sealedDataBuffer =
+        JByteBuffer::wrapBytes(sealedData.data(), sealedData.size());
+    local_ref<JByteBuffer> plaintextBuffer =
+        JByteBuffer::wrapBytes(plaintext.data(), plaintext.size());
+    static const auto cls = javaClassStatic();
+    static auto method = cls->getStaticMethod<void(
+        local_ref<JByteBuffer>,
+        local_ref<JByteBuffer>,
+        local_ref<JByteBuffer>)>("decrypt");
+    method(cls, keyBuffer, sealedDataBuffer, plaintextBuffer);
+  }
+};
 
 namespace comm {
 
 void AESCrypto::generateKey(rust::Slice<uint8_t> buffer) {
+  NativeAndroidAccessProvider::runTask(
+      [&]() { AESCryptoJavaClass::generateKey(buffer); });
 }
 
 void AESCrypto::encrypt(
     rust::Slice<uint8_t> key,
     rust::Slice<uint8_t> plaintext,
     rust::Slice<uint8_t> sealedData) {
+  NativeAndroidAccessProvider::runTask(
+      [&]() { AESCryptoJavaClass::encrypt(key, plaintext, sealedData); });
 }
 
 void AESCrypto::decrypt(
     rust::Slice<uint8_t> key,
     rust::Slice<uint8_t> sealedData,
     rust::Slice<uint8_t> plaintext) {
+  NativeAndroidAccessProvider::runTask(
+      [&]() { AESCryptoJavaClass::decrypt(key, sealedData, plaintext); });
 }
 
 } // namespace comm
diff --git a/native/expo-modules/comm-expo-package/android/src/main/java/app/comm/android/aescrypto/AESCryptoModule.kt b/native/expo-modules/comm-expo-package/android/src/main/java/app/comm/android/aescrypto/AESCryptoModule.kt
--- a/native/expo-modules/comm-expo-package/android/src/main/java/app/comm/android/aescrypto/AESCryptoModule.kt
+++ b/native/expo-modules/comm-expo-package/android/src/main/java/app/comm/android/aescrypto/AESCryptoModule.kt
@@ -98,10 +98,8 @@
 
 // region RN-agnostic implementations
 
-// Compatibility module to be called from native Java
+// Compatibility module to be called from native Java and Rust
 class AESCryptoModuleCompat {
-  private val secureRandom by lazy { SecureRandom() }
-
   public fun generateKey(): ByteArray {
    return KeyGenerator.getInstance(ALGORITHM_AES).apply {
       init(KEY_SIZE * 8, secureRandom)
@@ -130,6 +128,40 @@
     val plaintext = decryptAES(sealedDataBuffer, secretKey)
     return ByteArray(plaintext.remaining()).also(plaintext::get)
   }
+
+  companion object {
+    private val secureRandom by lazy { SecureRandom() }
+    @JvmStatic
+    fun generateKey(
+      buffer: ByteBuffer,
+    ) {
+      val key = KeyGenerator.getInstance(ALGORITHM_AES).apply {
+        init(KEY_SIZE * 8, secureRandom)
+      }.generateKey().encoded
+      
+      buffer.put(key)
+    }
+
+    @JvmStatic
+    fun encrypt(
+      rawKey: ByteBuffer,
+      plaintext: ByteBuffer,
+      sealedData: ByteBuffer,
+    ) {
+      val secretKey = rawKey.toAESSecretKey()
+      encryptAES(plaintext, secretKey, sealedData)
+    }
+
+    @JvmStatic
+    fun decrypt(
+      rawKey: ByteBuffer,
+      sealedData: ByteBuffer,
+      plaintext: ByteBuffer,
+    ) {
+      val secretKey = rawKey.toAESSecretKey()
+      decryptAES(sealedData, secretKey, plaintext)
+    }
+  }
 }
 
 /**
@@ -209,6 +241,13 @@
     .toSecretKey()
 }
 
+fun ByteBuffer.toAESSecretKey(): SecretKey{
+  if(this.remaining() != KEY_SIZE) {
+    throw InvalidKeyLengthException()
+  }
+  return ByteArray(this.remaining()).also(this::get).toSecretKey();
+}
+
 // endregion
 
 // region Exception definitions