Android Activity

From Crypto++ Wiki
Jump to: navigation, search
Cryptopp-android-prng.png
Android (Command Line) shows you how to build Crypto++ on the command line. This page discusses how to use the Crypto++ shared object in an Android Activity. The sample activity provides a shared object that calls into the Crypto++ shared object, builds the activity's shared object using the NDK, and packages the APK using Ant.

You can find an example of using prebuilt Crypto++ shared objects at Noloader | Android-PRNG. The sample code demonstrates two things. First, it shows you how to integrate a prebuilt Crypto++ library shared object into a Ant-based or NDK-based project. It may work for Android Studio, too. It may even work for Eclipse, depending on how well it imports an Android java project with JNI.

Second, it shows you how to accumulate seed data from sensors that can be used to seed your software based random number generators. A generator should have a fresh seed applied with IncorporateEntropy before each call to GenerateBlock to ensure the fitness of the generator.

Android.mk

Android provides a modified makefile based build system. If you are interested in an Android.mk to use with ndk-build, Eclipse, or Android Sudio, then see Pull Request 3, Add Android.mk to build using android NDK. Thanks to Alex Afanasyev for providing it.

An Android.mk has not been added to the Crypto++ sources because there are some open questions about using it to safely build static archives and shared objects. See, for example, No archive symbol table (run ranlib) while building libcryptopp.a through ndk-build and How to run ranlib on an archive built through Android.mk?

Android Studio

If you are using Android Studio, then you may need to add the following. Also see JNI and Gradle in Android Studio on Stack Overflow.

sourceSets.main {
    jni.srcDirs = []
    jniLibs.srcDir 'src/main/libs'
}

PRNG.java

Android's Java interface into Crypto++ is PRNG.java. Note that libprng.so provides CryptoPP_Reseed, CryptoPP_GetBytes and friends.

Crypto++ is thread safe at the class level, meaning their is no shared data. Crypto++ objects must be guarded using an external lock in case multiple threads use the same object. PRNG.java provides the lock.

public class PRNG {
    
    static {
        System.loadLibrary("stlport_shared");
        System.loadLibrary("cryptopp");
        System.loadLibrary("prng");
    }
    
    private static native int CryptoPP_Reseed(byte[] bytes);
    
    private static native int CryptoPP_GetBytes(byte[] bytes);
    
    private static Object lock = new Object();
    
    // Class method. Returns the number of bytes consumed from the seed.
    public static int Reseed(byte[] seed) {
        
        synchronized (lock) {
            return CryptoPP_Reseed(seed);
        }
    }
    
    // Class method. Returns the number of bytes generated.
    public static int GetBytes(byte[] bytes) {
        synchronized (lock) {
            return CryptoPP_GetBytes(bytes);
        }
    }
    
    // Instance method. Returns the number of bytes consumed from the seed.
    public int reseed(byte[] seed) {
        synchronized (lock) {
            return CryptoPP_Reseed(seed);
        }
    }
    
    // Instance method. Returns the number of bytes generated.
    public int getBytes(byte[] bytes) {
        synchronized (lock) {
            return CryptoPP_GetBytes(bytes);
        }
    }
}

libprng.cpp

The source file libprng.cpp is kind of big because it has to handle reseeds, generate random data and read the sensors. The three interesting functions from the JNI point of view are JNI_OnLoad, Java_com_cryptopp_prng_PRNG_CryptoPP_1Reseed and Java_com_cryptopp_prng_PRNG_CryptoPP_1GetBytes. Classes like ReadByteBuffer and WriteByteBuffer are helper classes.

Java uses unusual names for functions. I seem to recall you can use javah to generate them.

JNI_OnLoad

jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
    if (vm == NULL) {
        LOG_ERROR("JNI_OnLoad: virtual machine is not valid");
        return -1;
    }

    JNIEnv* env = NULL;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), EXPECTED_JNI_VERSION)) {
        LOG_ERROR("JNI_OnLoad: GetEnv failed");
        return -1;
    }

    if (env == NULL) {
        LOG_ERROR("JNI_OnLoad: JNI environment is not valid");
        return -1;
    }

    JNINativeMethod methods[2];

    methods[0].name = "CryptoPP_Reseed";
    methods[0].signature = "([B)I";
    methods[0].fnPtr =
            reinterpret_cast<void*>(Java_com_cryptopp_prng_PRNG_CryptoPP_1Reseed);

    methods[1].name = "CryptoPP_GetBytes";
    methods[1].signature = "([B)I";
    methods[1].fnPtr =
            reinterpret_cast<void*>(Java_com_cryptopp_prng_PRNG_CryptoPP_1GetBytes);

    jclass cls = env->FindClass("com/cryptopp/prng/PRNG");
    if (cls == NULL) {
        LOG_ERROR("JNI_OnLoad: FindClass com/cryptopp/prng/PRNG failed");
    } else {
        env->RegisterNatives(cls, methods, COUNTOF(methods));
    }

    return EXPECTED_JNI_VERSION;
}

Java_com_cryptopp_prng_PRNG_CryptoPP_1Reseed

jint JNICALL Java_com_cryptopp_prng_PRNG_CryptoPP_1Reseed(
        JNIEnv* env, jclass, jbyteArray seed)
{
    int consumed = 0;

    try {
        if (!env) {
            LOG_ERROR("Reseed: environment is NULL");
        }

        if (!seed) {
            // OK if the caller passed NULL for the array
            LOG_WARN("GetBytes: byte array is NULL");
        }

        if (env && seed) {
            ReadByteBuffer buffer(env, seed);

            const byte* _arr = buffer.GetByteArray();
            size_t _len = buffer.GetArrayLen();

            if ((_arr == NULL)) {
                LOG_ERROR("Reseed: array pointer is not valid");
            } else if ((_len == 0)) {
                LOG_ERROR("Reseed: array size is not valid");
            } else {
                AutoSeededRandomPool& prng = GetPRNG();
                prng.IncorporateEntropy(_arr, _len);

                LOG_INFO("Reseed: seeded with %d bytes", (int )_len);

                consumed += (int) _len;
            }
        }
    } catch (const Exception& ex) {
        LOG_ERROR("Reseed: Crypto++ exception: \"%s\"", ex.what());
        return 0;
    }

    return consumed;
}

Java_com_cryptopp_prng_PRNG_CryptoPP_1GetBytes

JNIEXPORT jint JNICALL Java_com_cryptopp_prng_PRNG_CryptoPP_1GetBytes(
        JNIEnv* env, jclass, jbyteArray bytes)
{
    int retrieved = 0;

    // Always mix in data during a call to GetBytes, even if the array is null.
    // Any entropy gathered will help future calls with a non-null array.
    int rc1, rc2;

    rc1 = AddProcessInfo();
    assert(rc1 > 0);

    rc2 = AddSensorData();
    assert(rc2 > 0);

    try {

        if (!env) {
            LOG_ERROR("GetBytes: environment is NULL");
        }

        if (!bytes) {
            // OK if the caller passed NULL for the array
            LOG_WARN("GetBytes: byte array is NULL");
        }

        if (env && bytes) {

            WriteByteBuffer buffer(env, bytes);

            byte* _arr = buffer.GetByteArray();
            size_t _len = buffer.GetArrayLen();

            if ((_arr == NULL)) {
                LOG_ERROR("GetBytes: array pointer is not valid");
            } else if ((_len == 0)) {
                LOG_ERROR("GetBytes: array size is not valid");
            } else {
                AutoSeededRandomPool& prng = GetPRNG();
                prng.GenerateBlock(_arr, _len);

                LOG_INFO("GetBytes: generated %d bytes", (int )_len);

                retrieved += (int) _len;
            }
        }
    } catch (const Exception& ex) {
        LOG_ERROR("GetBytes: Crypto++ exception: \"%s\"", ex.what());
        return 0;
    }

    return retrieved;
}

Downloads

The source code for Android-PRNG is located on GitHub at Noloader | Android-PRNG.