Android RSA加密解密

作者: StephenLau | 来源:发表于2019-04-18 14:33 被阅读4次

    需求

    在Android上实现RSA 2048 PKCS#1加密,秘钥不能放在Java层(其实公钥被别人看了也没啥吧?)。

    几个思路:

    1. 所有加密的程序都在native层。
    2. 秘钥在Native层,加密在Java实现。
    3. 所有都在Java实现。(不满足公钥不能放在Java层的要求)

    RSA加密

    RSA是一种非对称加密算法。

    通常的使用流程是A生成一对秘钥,公钥私钥,而这对秘钥有这样的特性:

    使用公钥加密的数据,利用私钥进行解密,使用私钥加密的数据,利用公钥进行解密

    A把公钥交给B,B使用公钥加密后把数据给回A,A使用私钥进行解密。

    2048是RSA密钥长度bit数,数字越大安全性越大。

    PKCS#1是公钥加密标准(Public Key Cryptography Standards, PKCS),是秘钥的一种格式。

    这里可以体验一下。

    Android在native中使用OpenSSL

    项目地址:https://github.com/etet2007/RSAencrypt

    在Android中添加native代码的步骤在官网中说得很清楚了。

    总的来说有几个Point。

    1. cpp文件夹下用于解密的cpp文件。Java层调用的native方法与其关联。
    //Java
    
    public static native byte[] encodeByRSAPubKey(byte[] src);
    
    //CPP
    
    extern "C" JNIEXPORT jbyteArray JNICALL
    Java_lau_stephen_rsaencrypt_EncryptUtils_encodeByRSAPubKey(JNIEnv *env, jclass type,
                                                               jbyteArray src_) {
        std::string keys = "-----BEGIN PUBLIC KEY-----\n"
                           "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwm/vZlgdvA/s3o+Epq2B\n"
                           "TF9Pk1goY4+wji1fjkmePasZkt12Rx0A0qXuzBfe8K4Y1uf/sKD1XHeXtvPol5TF\n"
                           "ZKq6dEQpd3PsweFMFGYfZbA5IdwEQWXFJqJSpru/jXENCanUARVV5Au0fjaMw71x\n"
                           "dGbHQ7gNdxln9xeoPkyCLBuWor5B3U47NFGEz8ZMELCib0+9bPtzIVuBYA5BsT9A\n"
                           "WgHZpuRZRgQ2r3a0ehe7gO1H+SKrLStVzUZ7EUW4PBM4IhIrR+BfORHi4PgD+4rZ\n"
                           "IuMzg99Y20ytaHIm6tw6+dvt3gSY2q2VWITCVE2dtH167R/AR+mJDFhp89Ss1sGE\n"
                           "wQIDAQAB\n"
                           "-----END PUBLIC KEY-----";
    
        jbyte *src = env->GetByteArrayElements(src_, NULL);
    
        jsize src_Len = env->GetArrayLength(src_);
    
        int encryptedValueSize = 0, src_flen = 0, cipherText_offset = 0, desText_len = 0, src_offset = 0;
    
        //BIO_new_mem_buf() creates a memory BIO using len bytes of data at buf,
        // if len is -1 then the buf is assumed to be nul terminated and its length is determined by strlen.
        BIO *keyBio = BIO_new_mem_buf((void *) keys.c_str(), -1);
        //The RSA structure consists of several BIGNUM components.
        // It can contain public as well as private RSA keys:
        RSA *publicKey = PEM_read_bio_RSA_PUBKEY(keyBio, NULL, NULL, NULL);
        //释放BIO
        BIO_free_all(keyBio);
    
        //RSA_size returns the RSA modulus size in bytes.
        // It can be used to determine how much memory must be allocated for an RSA encrypted value.
        int flen = RSA_size(publicKey);
    
        //复制src到srcOrigin
        unsigned char *srcOrigin = (unsigned char *) malloc(src_Len);
        memset(srcOrigin, 0, src_Len);
        memcpy(srcOrigin, src, src_Len);
        //每次加密后的长度
        unsigned char *encryptedValue = (unsigned char *) malloc(flen);
    
        desText_len = flen * (src_Len / (flen - 11) + 1);
        unsigned char *desText = (unsigned char *) malloc(desText_len);
        memset(desText, 0, desText_len);
    
        //对数据进行公钥加密运算
        //对于1024bit,2048应该为256
        //RSA_PKCS1_PADDING 最大加密长度 为 128 -11
        //RSA_NO_PADDING 最大加密长度为  128
        //rsa_size = rsa_size - RSA_PKCS1_PADDING_SIZE;
        for (int i = 0; i <= src_Len / (flen - 11); i++) {
            src_flen = (i == src_Len / (flen - 11)) ? src_Len % (flen - 11) : flen - 11;
            if (src_flen == 0) {
                break;
            }
            //重置encryptedValue
            memset(encryptedValue, 0, flen);
            //encrypt srcOrigin + src_offset到encryptedValue
            //returns the size of the encrypted data
            encryptedValueSize = RSA_public_encrypt(src_flen, srcOrigin + src_offset, encryptedValue,
                                                    publicKey, RSA_PKCS1_PADDING);
            if (encryptedValueSize == -1) {
                RSA_free(publicKey);
                CRYPTO_cleanup_all_ex_data();
                env->ReleaseByteArrayElements(src_, src, 0);
                free(srcOrigin);
                free(encryptedValue);
                free(desText);
    
                return NULL;
            }
            //复制encryptedValue到desText + cipherText_offset
            memcpy(desText + cipherText_offset, encryptedValue, encryptedValueSize);
    
            cipherText_offset += encryptedValueSize;
            src_offset += src_flen;
        }
    
        RSA_free(publicKey);
        //清除管理CRYPTO_EX_DATA的全局hash表中的数据,避免内存泄漏
        CRYPTO_cleanup_all_ex_data();
    
        //从jni释放数据指针
        env->ReleaseByteArrayElements(src_, src, 0);
    
        jbyteArray cipher = env->NewByteArray(cipherText_offset);
    
        //在堆中分配ByteArray数组对象成功,将拷贝数据到数组中
        env->SetByteArrayRegion(cipher, 0, cipherText_offset, (jbyte *) desText);
        //释放内存
        free(srcOrigin);
        free(encryptedValue);
        free(desText);
    
        return cipher;
    }
    
    1. 放置OpenSSL的库于项目中。这里的库是.a的静态库或者.so的动态库,可以自行编译或下载别人的来用。
      动态库静态库编译

    在Gradle中设置jniLibs的地址,把带有so库目录地址告诉Gradle,在打包的时候才会把so库打进APK。

        sourceSets.main {
            jniLibs.srcDirs = ['libs/lib']
        }
    
    1. 提供CMake文件

    Gradle提供CMakeList.txt文件的路径,我这里是放在app下。记住路径都是相对路径来的。

        externalNativeBuild {
            cmake {
                path file('CMakeLists.txt')
            }
        }
    

    CMakeList.txt文件,告诉系统用encrypt.cpp编译动态库,其中引用到OpenSSL的crypto、ssl动态库。

    # Sets the minimum version of CMake required to build the native library.
    
    cmake_minimum_required(VERSION 3.4.1)
    
    include_directories(libs/include)
    
    # Creates and names a library, sets it as either STATIC
    # or SHARED, and provides the relative paths to its source code.
    # You can define multiple libraries, and CMake builds them for you.
    # Gradle automatically packages shared libraries with your APK.
    
    add_library( # Sets the name of the library.
            encrypt
            # Sets the library as a shared library.
            SHARED
            # Provides a relative path to your source file(s).
            src/main/cpp/encrypt.cpp)
    
    add_library(crypto SHARED IMPORTED)
    add_library(ssl SHARED IMPORTED)
    
    set_target_properties(crypto PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/lib/${ANDROID_ABI}/libcrypto.so)
    set_target_properties(ssl PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/lib/${ANDROID_ABI}/libssl.so)
    
    find_library( # Sets the name of the path variable.
            log-lib
    
            # Specifies the name of the NDK library that
            # you want CMake to locate.
            log)
    
    target_link_libraries( # Specifies the target library.
            encrypt
            crypto
            ssl
            # Links the target library to the log library
            # included in the NDK.
            ${log-lib} )
    

    加密在Java实现

    Android 密钥库系统

    相关文章

      网友评论

        本文标题:Android RSA加密解密

        本文链接:https://www.haomeiwen.com/subject/dcxtgqtx.html