• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Kotlin Multiplatform (KMP) vs. C++: Building Cross-Platform Cryptographic Core Engines for Mobile

Kotlin Multiplatform (KMP) vs. C++: Building Cross-Platform Cryptographic Core Engines for Mobile

Architectural Considerations: KMP vs. C++ for Cryptographic Cores

When architecting cross-platform cryptographic libraries, particularly for mobile environments (Android and iOS), the choice between Kotlin Multiplatform (KMP) and native C++ presents distinct trade-offs. C++ offers unparalleled control and performance but comes with significant development overhead, manual memory management complexities, and a steeper learning curve for teams not already proficient. KMP, on the other hand, leverages Kotlin’s modern syntax and tooling to share business logic and, crucially for this use case, native code across platforms. For cryptographic cores, KMP allows us to write the core algorithms in Kotlin, which then compiles to JVM bytecode for Android and Objective-C/Swift interoperable code for iOS, while still enabling direct integration with platform-specific native libraries (like OpenSSL or Apple’s CommonCrypto) for performance-critical or hardware-accelerated operations.

The primary driver for considering KMP in this context is the potential for significant code reuse for the cryptographic logic itself, while still retaining the ability to drop down to highly optimized native implementations when necessary. This hybrid approach is often more pragmatic than a pure C++ solution, which would require separate build systems, testing frameworks, and potentially different API designs for each platform, even if the core logic is shared.

KMP Approach: Shared Logic with Native Interop

The KMP strategy involves defining the cryptographic interfaces and core algorithms in shared Kotlin modules. For performance-sensitive operations like AES encryption/decryption or SHA-256 hashing, we can either implement them directly in Kotlin (which can be surprisingly performant due to compiler optimizations) or, more commonly for production-grade security, delegate to native libraries. KMP’s `expect`/`actual` mechanism is key here. We declare an `expect` function in the common module, and then provide `actual` implementations for each target platform (JVM and Native).

Defining the Common Interface (Kotlin)

Let’s define a simple interface for an AES encryptor in our common module.

// commonMain/kotlin/com/example/crypto/AesEncryptor.kt
package com.example.crypto

interface AesEncryptor {
    @Throws(CryptoException::class)
    fun encrypt(plaintext: ByteArray, key: ByteArray, iv: ByteArray): ByteArray

    @Throws(CryptoException::class)
    fun decrypt(ciphertext: ByteArray, key: ByteArray, iv: ByteArray): ByteArray
}

expect class AesEncryptorFactory {
    fun create(): AesEncryptor
}

class CryptoException(message: String, cause: Throwable? = null) : Exception(message, cause)

Platform-Specific Implementations (JVM/Android)

On the JVM (Android), we can use the built-in Java Cryptography Architecture (JCA). The `actual` implementation will wrap these APIs.

// androidMain/kotlin/com/example/crypto/AesEncryptorImpl.kt
package com.example.crypto

import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

actual typealias AesEncryptorFactory = AndroidAesEncryptorFactory

class AndroidAesEncryptorFactory : AesEncryptorFactory {
    override fun create(): AesEncryptor = AndroidAesEncryptor()
}

class AndroidAesEncryptor : AesEncryptor {
    override fun encrypt(plaintext: ByteArray, key: ByteArray, iv: ByteArray): ByteArray {
        return try {
            val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
            val secretKeySpec = SecretKeySpec(key, "AES")
            val ivParameterSpec = IvParameterSpec(iv)
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
            cipher.doFinal(plaintext)
        } catch (e: Exception) {
            throw CryptoException("AES encryption failed", e)
        }
    }

    override fun decrypt(ciphertext: ByteArray, key: ByteArray, iv: ByteArray): ByteArray {
        return try {
            val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
            val secretKeySpec = SecretKeySpec(key, "AES")
            val ivParameterSpec = IvParameterSpec(iv)
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
            cipher.doFinal(ciphertext)
        } catch (e: Exception) {
            throw CryptoException("AES decryption failed", e)
        }
    }
}

Platform-Specific Implementations (iOS/Native)

For iOS, we’ll use KMP’s ability to bridge to Objective-C/Swift. We can either write the logic directly in Swift/Objective-C using CommonCrypto or leverage a C/C++ library. For this example, we’ll assume a C++ library is available and demonstrate how KMP can call into it. First, we need to expose C functions from our C++ library that KMP can call.

C++ Cryptographic Library (Example)

Assume we have a C++ library (e.g., `libcrypto_cpp.a` or `.so`) that provides the following C-style API for interoperability.

// crypto_cpp/aes_engine.h
#ifndef AES_ENGINE_H
#define AES_ENGINE_H

#ifdef __cplusplus
extern "C" {
#endif

// Returns 0 on success, -1 on error
int aes_encrypt(const unsigned char* plaintext, int plaintext_len,
                const unsigned char* key, const unsigned char* iv,
                unsigned char* ciphertext);

// Returns 0 on success, -1 on error
int aes_decrypt(const unsigned char* ciphertext, int ciphertext_len,
                const unsigned char* key, const unsigned char* iv,
                unsigned char* plaintext);

#ifdef __cplusplus
}
#endif

#endif // AES_ENGINE_H
// crypto_cpp/aes_engine.cpp
#include "aes_engine.h"
#include <vector>
#include <cstring> // For memcpy

// Placeholder for actual OpenSSL or similar implementation
// In a real scenario, this would involve OpenSSL, Crypto++, etc.
// For demonstration, we'll simulate success/failure.

int aes_encrypt(const unsigned char* plaintext, int plaintext_len,
                const unsigned char* key, const unsigned char* iv,
                unsigned char* ciphertext) {
    if (!plaintext || !key || !iv || !ciphertext) return -1;
    // Simulate encryption: copy plaintext to ciphertext and add a marker
    memcpy(ciphertext, plaintext, plaintext_len);
    // In a real implementation, you'd use OpenSSL's EVP_EncryptUpdate/Final
    // or similar.
    // For simplicity, let's assume output size is same as input for this mock.
    return 0; // Success
}

int aes_decrypt(const unsigned char* ciphertext, int ciphertext_len,
                const unsigned char* key, const unsigned char* iv,
                unsigned char* plaintext) {
    if (!ciphertext || !key || !iv || !plaintext) return -1;
    // Simulate decryption: copy ciphertext to plaintext
    memcpy(plaintext, ciphertext, ciphertext_len);
    // In a real implementation, you'd use OpenSSL's EVP_DecryptUpdate/Final
    // or similar.
    return 0; // Success
}

KMP Native Wrapper (Objective-C++)

We create an Objective-C++ file that bridges Kotlin/Native to our C++ library. This file will be compiled into the iOS framework.

// iosMain/kotlin/com/example/crypto/AesEncryptorNative.mm
#import <Foundation/Foundation.h>
#import "AesEncryptor.h" // Kotlin generated header
#import "aes_engine.h" // Our C++ header

// Define the actual implementation for the native target
@interface NativeAesEncryptor : NSObject <com_example_crypto_AesEncryptor>
@end

@implementation NativeAesEncryptor

- (NSData *_Nullable)encrypt:(NSData *_Nonnull)plaintext key:(NSData *_Nonnull)key iv:(NSData *_Nonnull)iv error:(NSError **_Nonnull)error {
    const unsigned char* plaintextBytes = (const unsigned char*)plaintext.bytes;
    const unsigned char* keyBytes = (const unsigned char*)key.bytes;
    const unsigned char* ivBytes = (const unsigned char*)iv.bytes;

    // Allocate buffer for ciphertext. In a real scenario, you'd need to calculate
    // the exact size required by the crypto library (e.g., padding).
    // For this mock, we assume output size is same as input.
    size_t bufferSize = plaintext.length;
    NSMutableData* ciphertextData = [NSMutableData dataWithLength:bufferSize];
    unsigned char* ciphertextBytes = (unsigned char*)ciphertextData.mutableBytes;

    int result = aes_encrypt(plaintextBytes, (int)plaintext.length, keyBytes, ivBytes, ciphertextBytes);

    if (result != 0) {
        // Handle error: set NSError
        NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"C++ AES encryption failed." };
        *error = [NSError errorWithDomain:@"com.example.crypto.NativeError" code:result userInfo:userInfo];
        return nil;
    }

    return ciphertextData;
}

- (NSData *_Nullable)decrypt:(NSData *_Nonnull)ciphertext key:(NSData *_Nonnull)key iv:(NSData *_Nonnull)iv error:(NSError **_Nonnull)error {
    const unsigned char* ciphertextBytes = (const unsigned char*)ciphertext.bytes;
    const unsigned char* keyBytes = (const unsigned char*)key.bytes;
    const unsigned char* ivBytes = (const unsigned char*)iv.bytes;

    // Allocate buffer for plaintext. Similar size considerations as encryption.
    size_t bufferSize = ciphertext.length;
    NSMutableData* plaintextData = [NSMutableData dataWithLength:bufferSize];
    unsigned char* plaintextBytes = (unsigned char*)plaintextData.mutableBytes;

    int result = aes_decrypt(ciphertextBytes, (int)ciphertext.length, keyBytes, ivBytes, plaintextBytes);

    if (result != 0) {
        // Handle error: set NSError
        NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"C++ AES decryption failed." };
        *error = [NSError errorWithDomain:@"com.example.crypto.NativeError" code:result userInfo:userInfo];
        return nil;
    }

    return plaintextData;
}

@end

// Factory implementation
@interface NativeAesEncryptorFactory : NSObject <com_example_crypto_AesEncryptorFactory>
@end

@implementation NativeAesEncryptorFactory
- (id<com_example_crypto_AesEncryptor>)create {
    return [[NativeAesEncryptor alloc] init];
}
@end

// Expose the factory to Kotlin/Native
extern "C" {
    // This function will be called by Kotlin/Native to get the factory instance.
    // The name must match the 'actual' declaration in Kotlin.
    id<com_example_crypto_AesEncryptorFactory> getNativeAesEncryptorFactory() {
        return [[NativeAesEncryptorFactory alloc] init];
    }
}

In your `iosMain/kotlin/com/example/crypto/AesEncryptorImpl.kt` file, you would then have:

// iosMain/kotlin/com/example/crypto/AesEncryptorImpl.kt
package com.example.crypto

import platform.Foundation.NSData
import platform.Foundation.NSError
import platform.darwin.NSInteger

// Import the C++ exposed function
@CName("getNativeAesEncryptorFactory")
external fun getNativeAesEncryptorFactory(): com_example_crypto_AesEncryptorFactory

actual typealias AesEncryptorFactory = NativeAesEncryptorFactory

class NativeAesEncryptorFactory : com_example_crypto_AesEncryptorFactory {
    override fun create(): com_example_crypto_AesEncryptor {
        // Call the C++ exposed function to get the factory
        val nativeFactory = getNativeAesEncryptorFactory()
        return nativeFactory.create()
    }
}

// Need to define the interfaces that Kotlin/Native generates for Objective-C classes
// These are typically generated by the KMP plugin, but can be defined manually if needed.
// For simplicity, assuming they are available or generated.
// Example:
// interface com_example_crypto_AesEncryptor {
//     fun encrypt(plaintext: NSData, key: NSData, iv: NSData): NSData?
//     fun decrypt(ciphertext: NSData, key: NSData, iv: NSData): NSData?
// }
// interface com_example_crypto_AesEncryptorFactory {
//     fun create(): com_example_crypto_AesEncryptor
// }

// Helper to convert Kotlin ByteArray to NSData and vice-versa
fun ByteArray.toNSData(): NSData = NSData.create(bytes = this.toUByteArray().toTypedArray(), length = this.size.toULong())
fun NSData.toByteArray(): ByteArray = ByteArray(this.length.toInt()).apply {
    usePinned { pinned ->
        memcpy(pinned.addressOf(0), [email protected], [email protected])
    }
}

// Wrapper class to conform to the common interface
class NativeAesEncryptor(private val nativeImpl: com_example_crypto_AesEncryptor) : AesEncryptor {
    override fun encrypt(plaintext: ByteArray, key: ByteArray, iv: ByteArray): ByteArray {
        val plaintextNS = plaintext.toNSData()
        val keyNS = key.toNSData()
        val ivNS = iv.toNSData()

        var error: NSError? = null
        val ciphertextNS = nativeImpl.encrypt(plaintextNS, keyNS, ivNS)

        if (ciphertextNS == null) {
            val errorCode = error?.code ?: -1
            val errorMessage = error?.localizedDescription ?: "Unknown native encryption error"
            throw CryptoException(errorMessage, cause = Exception("Native error code: $errorCode"))
        }
        return ciphertextNS.toByteArray()
    }

    override fun decrypt(ciphertext: ByteArray, key: ByteArray, iv: ByteArray): ByteArray {
        val ciphertextNS = ciphertext.toNSData()
        val keyNS = key.toNSData()
        val ivNS = iv.toNSData()

        var error: NSError? = null
        val plaintextNS = nativeImpl.decrypt(ciphertextNS, keyNS, ivNS)

        if (plaintextNS == null) {
            val errorCode = error?.code ?: -1
            val errorMessage = error?.localizedDescription ?: "Unknown native decryption error"
            throw CryptoException(errorMessage, cause = Exception("Native error code: $errorCode"))
        }
        return plaintextNS.toByteArray()
    }
}

// Update the factory to return our wrapper
class NativeAesEncryptorFactory : com_example_crypto_AesEncryptorFactory {
    private val nativeFactory = getNativeAesEncryptorFactory() // Get the actual native factory
    override fun create(): com_example_crypto_AesEncryptor {
        return NativeAesEncryptor(nativeFactory.create()) // Return our wrapper
    }
}

C++ Approach: Pure Native Core

A pure C++ approach involves writing the entire cryptographic core in C++. This core would then be compiled into static or dynamic libraries for each target platform (e.g., `libcrypto_android.a`, `libcrypto_ios.a`, `libcrypto_windows.lib`). Each platform’s application would link against its respective library.

C++ Library Structure

The structure would be similar to the C++ library presented earlier, but potentially more extensive, including classes and methods for various cryptographic operations.

// cpp_crypto/aes_engine.h
#pragma once
#include <vector>
#include <string>
#include <stdexcept>

namespace CryptoCore {

class CryptoException : public std::runtime_error {
public:
    CryptoException(const std::string& msg) : std::runtime_error(msg) {}
};

class AesEngine {
public:
    // Key and IV sizes are typically fixed (e.g., 16, 24, 32 bytes for key; 16 bytes for IV)
    static const size_t KEY_SIZE_128 = 16;
    static const size_t KEY_SIZE_192 = 24;
    static const size_t KEY_SIZE_256 = 32;
    static const size_t IV_SIZE = 16;

    virtual ~AesEngine() = default;

    virtual std::vector<unsigned char> encrypt(
        const std::vector<unsigned char>& plaintext,
        const std::vector<unsigned char>& key,
        const std::vector<unsigned char>& iv) = 0;

    virtual std::vector<unsigned char> decrypt(
        const std::vector<unsigned char>& ciphertext,
        const std::vector<unsigned char>& key,
        const std::vector<unsigned char>& iv) = 0;
};

// Factory function to create an engine instance
// This allows for platform-specific implementations (e.g., using hardware acceleration)
std::unique_ptr<AesEngine> createAesEngine();

} // namespace CryptoCore
// cpp_crypto/aes_engine_openssl.cpp (Example using OpenSSL)
#include "aes_engine.h"
#include <openssl/evp.h>
#include <openssl/err.h>
#include <memory>
#include <cstring>

namespace CryptoCore {

class OpenSslAesEngine : public AesEngine {
public:
    std::vector<unsigned char> encrypt(
        const std::vector<unsigned char>& plaintext,
        const std::vector<unsigned char>& key,
        const std::vector<unsigned char>& iv) override {

        if (key.size() != KEY_SIZE_256 || iv.size() != IV_SIZE) {
            throw CryptoException("Invalid key or IV size for AES-256-CBC");
        }

        EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
        if (!ctx) throw CryptoException("Failed to create EVP_CIPHER_CTX");

        std::vector<unsigned char> ciphertext(plaintext.size() + EVP_MAX_BLOCK_LENGTH); // Reserve space for padding
        int len = 0;
        int ciphertext_len = 0;

        if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key.data(), iv.data())) {
            ERR_print_errors_fp(stderr);
            EVP_CIPHER_CTX_free(ctx);
            throw CryptoException("EVP_EncryptInit_ex failed");
        }

        if (1 != EVP_EncryptUpdate(ctx, ciphertext.data(), &len, plaintext.data(), plaintext.size())) {
            ERR_print_errors_fp(stderr);
            EVP_CIPHER_CTX_free(ctx);
            throw CryptoException("EVP_EncryptUpdate failed");
        }
        ciphertext_len = len;

        if (1 != EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len)) {
            ERR_print_errors_fp(stderr);
            EVP_CIPHER_CTX_free(ctx);
            throw CryptoException("EVP_EncryptFinal_ex failed");
        }
        ciphertext_len += len;

        EVP_CIPHER_CTX_free(ctx);
        ciphertext.resize(ciphertext_len); // Trim to actual size
        return ciphertext;
    }

    std::vector<unsigned char> decrypt(
        const std::vector<unsigned char>& ciphertext,
        const std::vector<unsigned char>& key,
        const std::vector<unsigned char>& iv) override {

        if (key.size() != KEY_SIZE_256 || iv.size() != IV_SIZE) {
            throw CryptoException("Invalid key or IV size for AES-256-CBC");
        }

        EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
        if (!ctx) throw CryptoException("Failed to create EVP_CIPHER_CTX");

        std::vector<unsigned char> plaintext(ciphertext.size()); // Max possible size
        int len = 0;
        int plaintext_len = 0;

        if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key.data(), iv.data())) {
            ERR_print_errors_fp(stderr);
            EVP_CIPHER_CTX_free(ctx);
            throw CryptoException("EVP_DecryptInit_ex failed");
        }

        if (1 != EVP_DecryptUpdate(ctx, plaintext.data(), &len, ciphertext.data(), ciphertext.size())) {
            ERR_print_errors_fp(stderr);
            EVP_CIPHER_CTX_free(ctx);
            throw CryptoException("EVP_DecryptUpdate failed");
        }
        plaintext_len = len;

        if (1 != EVP_DecryptFinal_ex(ctx, plaintext.data() + len, &len)) {
            ERR_print_errors_fp(stderr);
            EVP_CIPHER_CTX_free(ctx);
            throw CryptoException("EVP_DecryptFinal_ex failed");
        }
        plaintext_len += len;

        EVP_CIPHER_CTX_free(ctx);
        plaintext.resize(plaintext_len); // Trim to actual size
        return plaintext;
    }
};

std::unique_ptr<AesEngine> createAesEngine() {
    // In a real scenario, you might check for hardware support here
    // and return a different engine implementation if available.
    return std::make_unique<OpenSslAesEngine>();
}

} // namespace CryptoCore

Platform-Specific Build & Integration

Android: The C++ library would be compiled into a shared object (`.so`) using the Android NDK. A JNI (Java Native Interface) layer would be created in C++ to expose functions callable from Kotlin/Java.

// android/jni/native_lib.cpp
#include <jni.h>
#include <vector>
#include <memory>
#include "cpp_crypto/aes_engine.h" // Include your C++ core header

// Helper to convert jbyteArray to std::vector<unsigned char>
std::vector<unsigned char> jbyteArrayToVector(JNIEnv* env, jbyteArray array) {
    if (!array) return {};
    jbyte* elements = env->GetByteArrayElements(array, NULL);
    jsize len = env->GetArrayLength(array);
    std::vector<unsigned char> vec(elements, elements + len);
    env->ReleaseByteArrayElements(array, elements, JNI_ABORT);
    return vec;
}

// Helper to convert std::vector<unsigned char> to jbyteArray
jbyteArray vectorToJbyteArray(JNIEnv* env, const std::vector<unsigned char>& vec) {
    jbyteArray array = env->NewByteArray(vec.size());
    if (!array) return nullptr;
    env->SetByteArrayRegion(array, 0, vec.size(), reinterpret_cast<const jbyte*>(vec.data()));
    return array;
}

extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_example_myapp_CryptoWrapper_nativeEncryptAes(
        JNIEnv* env,
        jobject /* this */,
        jbyteArray plaintext,
        jbyteArray key,
        jbyteArray iv) {

    std::unique_ptr<CryptoCore::AesEngine> engine = CryptoCore::createAesEngine();
    try {
        std::vector<unsigned char> plaintextVec = jbyteArrayToVector(env, plaintext);
        std::vector<unsigned char> keyVec = jbyteArrayToVector(env, key);
        std::vector<unsigned char> ivVec = jbyteArrayToVector(env, iv);

        std::vector<unsigned char> ciphertextVec = engine->encrypt(plaintextVec, keyVec, ivVec);
        return vectorToJbyteArray(env, ciphertextVec);
    } catch (const CryptoCore::CryptoException& e) {
        // Throw Java exception
        jclass exceptionClass = env->FindClass("java/lang/RuntimeException");
        env->ThrowNew(exceptionClass, e.what());
        return nullptr;
    } catch (...) {
        jclass exceptionClass = env->FindClass("java/lang/RuntimeException");
        env->ThrowNew(exceptionClass, "Unknown C++ error during encryption");
        return nullptr;
    }
}

extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_example_myapp_CryptoWrapper_nativeDecryptAes(
        JNIEnv* env,
        jobject /* this */,
        jbyteArray ciphertext,
        jbyteArray key,
        jbyteArray iv) {

    std::unique_ptr<CryptoCore::AesEngine> engine = CryptoCore::createAesEngine();
    try {
        std::vector<unsigned char> ciphertextVec = jbyteArrayToVector(env, ciphertext);
        std::vector<unsigned char> keyVec = jbyteArrayToVector(env, key);
        std::vector<unsigned char> ivVec = jbyteArrayToVector(env, iv);

        std::vector<unsigned char> plaintextVec = engine->decrypt(ciphertextVec, keyVec, ivVec);
        return vectorToJbyteArray(env, plaintextVec);
    } catch (const CryptoCore::CryptoException& e) {
        // Throw Java exception
        jclass exceptionClass = env->FindClass("java/lang/RuntimeException");
        env->ThrowNew(exceptionClass, e.what());
        return nullptr;
    } catch (...) {
        jclass exceptionClass = env->FindClass("java/lang/RuntimeException");
        env->ThrowNew(exceptionClass, "Unknown C++ error during decryption");
        return nullptr;
    }
}

The Kotlin/Java code would then call these JNI functions.

// androidMain/kotlin/com/example/myapp/CryptoWrapper.kt
package com.example.myapp

class CryptoWrapper {
    init {
        System.loadLibrary("native-lib") // Load the compiled .so file
    }

    external fun nativeEncryptAes(plaintext: ByteArray, key: ByteArray, iv: ByteArray): ByteArray?
    external fun nativeDecryptAes(ciphertext: ByteArray, key: ByteArray, iv: ByteArray): ByteArray?

    // Add methods to wrap these native calls, similar to the KMP example
    // ...
}

iOS: The C++ library would be compiled into a static library (`.a`) or framework. Objective-C++ wrappers would be created to bridge to the C++ code, similar to the KMP native interop example, but without the KMP layer in between.

// ios/CryptoWrapper.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface CryptoWrapper : NSObject
- (NSData *_Nullable)encryptAes:(NSData *_Nonnull)plaintext key:(NSData *_Nonnull)key iv:(NSData *_Nonnull)iv error:(NSError **_Nonnull)error;
- (NSData *_Nullable)decryptAes:(NSData *_Nonnull)ciphertext key:(NSData *_Nonnull)key iv:(NSData *_Nonnull)iv error:(NSError **_Nonnull)error;
@end

NS_ASSUME_NONNULL_END
// ios/CryptoWrapper.mm
#import "CryptoWrapper.h"
#import "cpp_crypto/aes_engine.h" // Include your C++ core header
#import <memory>

@implementation CryptoWrapper {
    std::unique_ptr<CryptoCore::AesEngine> _aesEngine;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        // Create the C++ engine instance
        _aesEngine = CryptoCore::createAesEngine();
        if (!_aesEngine) {
            // Handle error: engine creation failed
            NSLog(@"Error: Failed to create C++ AesEngine.");
            return nil;
        }
    }
    return self;
}

- (NSData *_Nullable)encryptAes:(NSData *_Nonnull)plaintext key:(NSData *_Nonnull)key iv:(NSData *_Nonnull)iv error:(NSError **_Nonnull)error {
    try {
        // Convert NSData to std::vector
        std::vector<unsigned char> plaintextVec(plaintext.bytes, (unsigned char*)plaintext.bytes + plaintext.length);
        std::vector<unsigned char> keyVec(key.bytes, (unsigned char*)key.bytes + key.length);
        std::vector<unsigned char> ivVec(iv.bytes, (unsigned char*)iv.bytes + iv.length);

        // Call C++ engine
        std::vector<unsigned char> ciphertextVec = _aesEngine->encrypt(plaintextVec, keyVec, ivVec);

        // Convert std::vector to NSData
        return [NSData dataWithBytes:ciphertextVec.data() length:ciphertextVec.size()];

    } catch (const CryptoCore::CryptoException& e) {
        // Create and set NSError
        NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String:e.what()] };
        *error = [NSError errorWithDomain:@"com.example.crypto.CppError" code:-1 userInfo:userInfo];
        return nil;
    } catch (...) {
        NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Unknown C++ error during encryption." };
        *error = [NSError errorWithDomain:@"com.example.crypto.CppError" code:-2 userInfo:userInfo];
        return nil;
    }
}

- (NSData *_Nullable)decryptAes:(NSData *_Nonnull)ciphertext key:(NSData *_Nonnull)key iv:(NSData *_Nonnull)iv error:(NSError **_Nonnull)error {
    try {
        // Convert NSData to std::vector
        std::vector<unsigned char> ciphertextVec(ciphertext.bytes, (unsigned char*)ciphertext.bytes + ciphertext.length);
        std::vector<unsigned char> keyVec(key.bytes, (unsigned char*)key.bytes + key.length);
        std::vector<unsigned char> ivVec(iv.bytes, (unsigned char*)iv.bytes + iv.length);

        // Call C++ engine
        std::vector<unsigned char> plaintextVec = _aesEngine->decrypt(ciphertextVec, keyVec, ivVec);

        // Convert std::vector to NSData
        return [NSData dataWithBytes:plaintextVec.data() length:plaintextVec.size()];

    } catch (const CryptoCore::CryptoException& e) {
        // Create and set NSError
        NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String:e.what()] };
        *error = [NSError errorWithDomain:@"com.example.crypto.CppError" code:-1 userInfo:userInfo];
        return nil;
    } catch (...) {
        NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Unknown C++ error during decryption." };
        *error = [NSError errorWithDomain:@"com.example.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability
  • Scala Pekko vs. Go Goroutines: Actor Model vs. CSP for Event-Driven Reactive Systems
  • Java Loom Virtual Threads vs. Go Goroutines: Under-the-Hood Scheduler and Thread Overhead Comparison

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (584)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (806)
  • PHP (5)
  • PHP Development (21)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (19)
  • Ruby on Rails (1)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (7)
  • WordPress Theme Development (357)

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (806)
  • Debugging & Troubleshooting (584)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala