You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在JNI中将Android PrivateKey转换为OpenSSL EVP_PKEY的方法

解决Android Keystore私钥转OpenSSL 3 EVP_PKEY的问题

核心限制说明

Android Keystore管理的私钥是不可导出原始编码的,所以PrivateKey.getEncoded()返回null是设计使然,无法通过依赖密钥原始编码的d2i_AutoPrivateKey(需要DER/PEM格式的密钥数据)直接构造EVP_PKEY。必须通过OpenSSL的引擎(ENGINE)机制或者自定义密钥操作桥接来实现,让OpenSSL间接调用Android Keystore的密钥能力,而非直接获取密钥内容。

方案一:JNI桥接+自定义EVP_PKEY_METHOD

通过JNI层对接Android Keystore的私钥操作,为OpenSSL实现自定义的密钥方法回调,让OpenSSL在需要使用私钥时,间接调用Android系统的密钥操作API。

1. JNI层核心代码示例

#include <jni.h>
#include <openssl/evp.h>

// 保存Java层私钥引用的上下文key
#define ANDROID_KEYSTORE_PKEY_REF 0

// 自定义签名实现:桥接Android Keystore的签名操作
static int android_keystore_sign(EVP_PKEY_CTX *ctx, unsigned char *sig, size_t *siglen, const unsigned char *tbs, size_t tbslen) {
    JNIEnv *env;
    // 从EVP_PKEY中取出之前保存的Java层PrivateKey引用
    jobject privateKey = EVP_PKEY_get_ex_data(EVP_PKEY_CTX_get0_pkey(ctx), ANDROID_KEYSTORE_PKEY_REF);
    if (!privateKey) return 0;

    // 调用Java层Signature API完成签名
    jclass sigClass = (*env)->FindClass(env, "java/security/Signature");
    jmethodID initSignId = (*env)->GetMethodID(env, sigClass, "initSign", "(Ljava/security/PrivateKey;)V");
    jmethodID updateId = (*env)->GetMethodID(env, sigClass, "update", "([BII)V");
    jmethodID signId = (*env)->GetMethodID(env, sigClass, "sign", "()[B");
    jobject sigObj = (*env)->NewObject(env, sigClass, 
        (*env)->GetMethodID(env, sigClass, "<init>", "(Ljava/lang/String;)V"), 
        (*env)->NewStringUTF(env, "SHA256withRSA")); // 根据密钥类型调整算法

    (*env)->CallVoidMethod(env, sigObj, initSignId, privateKey);
    jbyteArray tbsArray = (*env)->NewByteArray(env, tbslen);
    (*env)->SetByteArrayRegion(env, tbsArray, 0, tbslen, tbs);
    (*env)->CallVoidMethod(env, sigObj, updateId, tbsArray, 0, tbslen);
    jbyteArray sigBytes = (*env)->CallObjectMethod(env, sigObj, signId);

    *siglen = (*env)->GetArrayLength(env, sigBytes);
    (*env)->GetByteArrayRegion(env, sigBytes, 0, *siglen, sig);

    // 清理局部引用
    (*env)->DeleteLocalRef(env, sigClass);
    (*env)->DeleteLocalRef(env, sigObj);
    (*env)->DeleteLocalRef(env, tbsArray);
    (*env)->DeleteLocalRef(env, sigBytes);
    return 1;
}

// 创建绑定Android Keystore私钥的EVP_PKEY
EVP_PKEY *create_android_keystore_pkey(JNIEnv *env, jobject privateKey) {
    EVP_PKEY *pkey = EVP_PKEY_new();
    if (!pkey) return NULL;

    // 根据密钥类型选择对应的EVP_PKEY_METHOD(示例为RSA,EC密钥需调整)
    EVP_PKEY_METHOD *method = EVP_PKEY_METHOD_new(EVP_PKEY_RSA, 0);
    if (!method) {
        EVP_PKEY_free(pkey);
        return NULL;
    }

    // 绑定签名方法,如需解密等操作可添加对应回调
    EVP_PKEY_METHOD_set_sign(method, NULL, android_keystore_sign);
    EVP_PKEY_set_method(pkey, method);

    // 保存Java层私钥引用到EVP_PKEY的扩展数据中
    jobject globalKeyRef = (*env)->NewGlobalRef(env, privateKey);
    EVP_PKEY_set_ex_data(pkey, ANDROID_KEYSTORE_PKEY_REF, globalKeyRef);

    return pkey;
}

// JNI暴露给Java的方法
JNIEXPORT jlong JNICALL
Java_com_your_package_YourClass_createOpenSSLPrivateKey(JNIEnv *env, jobject thiz, jobject privateKey) {
    EVP_PKEY *pkey = create_android_keystore_pkey(env, privateKey);
    return (jlong)pkey;
}

2. Java层调用示例

public class YourClass {
    static {
        System.loadLibrary("your-openssl-lib");
    }

    private native long createOpenSSLPrivateKey(PrivateKey privateKey);

    public void useOpenSSLPrivateKey() {
        try {
            PrivateKey key = KeyChain.getPrivateKey(context, "your-key-alias");
            long pkeyPtr = createOpenSSLPrivateKey(key);
            // 将pkeyPtr传递给其他OpenSSL方法使用,使用完记得调用JNI方法释放资源
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

方案二:使用OpenSSL 3 Provider架构(推荐)

OpenSSL 3的Provider机制是替代ENGINE的新标准,你可以实现自定义Provider对接Android Keystore:

  • 实现OSSL_KEYMGMT接口,让Provider能够识别Android Keystore的私钥元数据
  • 实现对应密钥类型的操作方法(签名、解密等)
  • 在JNI中调用OSSL_PROVIDER_load()加载自定义Provider,通过EVP_PKEY_fromdata()创建EVP_PKEY

这种方式更符合OpenSSL 3的设计规范,扩展性更强。

关键注意事项

  • 禁止尝试绕过Android Keystore的导出限制,这是系统安全设计要求,违规操作可能导致应用被Google Play拒绝
  • 针对不同密钥类型(RSA/EC等),需调整JNI中的算法参数和EVP_PKEY_METHOD类型
  • JNI层需正确管理Java对象的全局引用,避免内存泄漏

内容的提问来源于stack exchange,提问作者Vamsi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 13:03:31