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
@@ -17,6 +17,7 @@
 private const val IV_LENGTH = 12 // bytes - unique Initialization Vector (nonce)
 private const val TAG_LENGTH = 16 // bytes - GCM auth tag
 
+// Expo module called from the RN app JS code
 class AESCryptoModule : Module() {
   private val secureRandom by lazy { SecureRandom() }
 
@@ -109,6 +110,39 @@
 
 // region RN-agnostic implementations
 
+// Compatibility module to be called from native Java
+class AESCryptoModuleCompat {
+  private val secureRandom by lazy { SecureRandom() }
+
+  public fun generateKey(): ByteArray {
+   return KeyGenerator.getInstance(ALGORITHM_AES).apply {
+      init(KEY_SIZE * 8, secureRandom)
+    }.generateKey().encoded
+  }
+
+  public fun encrypt(
+    rawKey: ByteArray,
+    plaintext: ByteArray,
+  ): ByteArray {
+    val secretKey = rawKey.toSecretKey()
+    val (iv, ciphertextWithTag) = encryptAES(plaintext, secretKey)
+    return iv + ciphertextWithTag
+  }
+
+  public fun decrypt(
+    rawKey: ByteArray,
+    sealedData: ByteArray
+  ): ByteArray {
+    if(sealedData.size <= IV_LENGTH + TAG_LENGTH) {
+      throw InvalidDataLengthException()
+    }
+    val secretKey = rawKey.toSecretKey()
+    val iv = sealedData.copyOfRange(0, IV_LENGTH)
+    val ciphertextWithTag = sealedData.copyOfRange(IV_LENGTH, sealedData.size)
+    return decryptAES(ciphertextWithTag, secretKey, iv)
+  }
+}
+
 /**
  * Encrypts given [plaintext] with given [key] using AES-256 GCM algorithm
  *