diff --git a/native/expo-modules/aes-crypto/ios/AESCryptoModule.swift b/native/expo-modules/aes-crypto/ios/AESCryptoModule.swift
--- a/native/expo-modules/aes-crypto/ios/AESCryptoModule.swift
+++ b/native/expo-modules/aes-crypto/ios/AESCryptoModule.swift
@@ -11,6 +11,7 @@
 
     Function("generateKey", generateKey)
     Function("encrypt", encrypt)
+    Function("decrypt", decrypt)
   }
 }
 
@@ -51,6 +52,20 @@
   sealedData.copyBytes(to: destination.rawBufferPtr())
 }
 
+private func decrypt(rawKey: Uint8Array,
+                     sealedData: Uint8Array,
+                     destination: Uint8Array) throws {
+  guard destination.byteLength == sealedData.byteLength - IV_LENGTH - TAG_LENGTH
+  else {
+    throw InvalidDataLengthException()
+  }
+  
+  let key = SymmetricKey(data: rawKey.data())
+  let sealedBox = try AES.GCM.SealedBox(combined: sealedData.data())
+  let plaintext = try AES.GCM.open(sealedBox, using: key)
+  plaintext.copyBytes(to: destination.rawBufferPtr())
+}
+
 // MARK: - Utilities
 
 extension TypedArray {
diff --git a/native/utils/aes-crypto-module.js b/native/utils/aes-crypto-module.js
--- a/native/utils/aes-crypto-module.js
+++ b/native/utils/aes-crypto-module.js
@@ -14,6 +14,11 @@
     data: Uint8Array,
     destination: Uint8Array,
   ) => void,
+  +decrypt: (
+    key: Uint8Array,
+    data: Uint8Array,
+    destination: Uint8Array,
+  ) => void,
 } = requireNativeModule('AESCrypto');
 
 export function generateKey(): Uint8Array {
@@ -28,3 +33,11 @@
   AESCryptoModule.encrypt(key, data, sealedDataBuffer);
   return sealedDataBuffer;
 }
+
+export function decrypt(key: Uint8Array, data: Uint8Array): Uint8Array {
+  invariant(AESCryptoModule.decrypt, 'AESCrypto.decrypt is not implemented');
+  invariant(data.length >= IV_LENGTH + TAG_LENGTH, 'Invalid data length');
+  const plaintextBuffer = new Uint8Array(data.length - IV_LENGTH - TAG_LENGTH);
+  AESCryptoModule.decrypt(key, data, plaintextBuffer);
+  return plaintextBuffer;
+}