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
@@ -2,12 +2,15 @@
 import CryptoKit
 
 private let KEY_SIZE = 32 // bytes
+private let IV_LENGTH = 12 // bytes, IV - unique Initialization Vector (nonce)
+private let TAG_LENGTH = 16 // bytes - GCM auth tag
 
 public class AESCryptoModule: Module {
   public func definition() -> ModuleDefinition {
     Name("AESCrypto")
 
     Function("generateKey", generateKey)
+    Function("encrypt", encrypt)
   }
 }
 
@@ -23,9 +26,38 @@
   }
 }
 
+private func encrypt(rawKey: Uint8Array,
+                     plaintext: Uint8Array,
+                     destination: Uint8Array) throws {
+  guard destination.byteLength == plaintext.byteLength + IV_LENGTH + TAG_LENGTH
+  else {
+    throw InvalidDataLengthException()
+  }
+  
+  let key = SymmetricKey(data: rawKey.data())
+  let iv = AES.GCM.Nonce()
+  let encryptionResult = try AES.GCM.seal(plaintext.data(),
+                                          using: key,
+                                          nonce: iv)
+
+  // 'combined' returns concatenated: iv || ciphertext || tag
+  guard let sealedData = encryptionResult.combined else {
+    // this happens only if Nonce/IV != 12 bytes long
+    throw EncryptionFailedException("Incorrect AES configuration")
+  }
+  guard sealedData.count == destination.byteLength else {
+    throw EncryptionFailedException("Encrypted data has unexpected length")
+  }
+  sealedData.copyBytes(to: destination.rawBufferPtr())
+}
+
 // MARK: - Utilities
 
 extension TypedArray {
+  func data() -> Data {
+    Data(bytes: self.rawPointer, count: self.byteLength)
+  }
+  
   func rawBufferPtr() -> UnsafeMutableRawBufferPointer {
     UnsafeMutableRawBufferPointer(start: self.rawPointer,
                                   count: self.byteLength)
@@ -39,3 +71,15 @@
     "The AES key has invalid length"
   }
 }
+
+private class InvalidDataLengthException: Exception {
+  override var reason: String {
+    "Source or destination array has invalid length"
+  }
+}
+
+private class EncryptionFailedException: GenericException<String> {
+  override var reason: String {
+    "Failed to encrypt data: \(param)"
+  }
+}
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
@@ -1,11 +1,19 @@
 // @flow
 
 import { requireNativeModule } from 'expo-modules-core';
+import invariant from 'invariant';
 
 const KEY_SIZE = 32; // bytes
+const IV_LENGTH = 12; // bytes, IV - unique Initialization Vector (nonce)
+const TAG_LENGTH = 16; // bytes - GCM auth tag
 
 const AESCryptoModule: {
   +generateKey: (destination: Uint8Array) => void,
+  +encrypt: (
+    key: Uint8Array,
+    data: Uint8Array,
+    destination: Uint8Array,
+  ) => void,
 } = requireNativeModule('AESCrypto');
 
 export function generateKey(): Uint8Array {
@@ -13,3 +21,10 @@
   AESCryptoModule.generateKey(keyBuffer);
   return keyBuffer;
 }
+
+export function encrypt(key: Uint8Array, data: Uint8Array): Uint8Array {
+  invariant(AESCryptoModule.encrypt, 'AESCrypto.encrypt is not implemented');
+  const sealedDataBuffer = new Uint8Array(data.length + IV_LENGTH + TAG_LENGTH);
+  AESCryptoModule.encrypt(key, data, sealedDataBuffer);
+  return sealedDataBuffer;
+}