Android端使用國(guó)密SM2進(jìn)行加密

前言

最近研究SM2加密在app中的應(yīng)用冈止,在掉了1024根頭發(fā)的時(shí)候酌媒,終于給找到解決方案了。

背景

項(xiàng)目中使用的是簽發(fā)公私鑰證書進(jìn)行的加解密的嗤军,證書的格式為.cer文件格式注盈。也就是app端在應(yīng)用中載入公鑰,然后調(diào)用SM2的加密方法對(duì)數(shù)據(jù)進(jìn)行加密叙赚,將加密的數(shù)據(jù)傳給后端老客,后端通過對(duì)應(yīng)的私鑰解密。
long long ago震叮,在iOS系統(tǒng)進(jìn)行過一次大版本的升級(jí)過后胧砰,iPhone端的SM2加密就失效了,官方的原生方法無法讀取.cer文件苇瓣,這樣就無法獲取到證書中的公鑰尉间,也就無法完成整個(gè)加密過程了。
而今在Android端也出現(xiàn)了這樣的問題击罪,大概看了一下項(xiàng)目中使用的一些第三方框架哲嘲,大體就是對(duì)官方這樣的方法做了幾層封裝,然后將國(guó)密的一些加密方式整合在一起媳禁。所以這樣的第三方框架已經(jīng)無法繼續(xù)使用眠副。
沒辦法,項(xiàng)目還堅(jiān)持使用SM2進(jìn)行加密竣稽,那么只能繼續(xù)尋找解決辦法囱怕。作為一個(gè)非專業(yè)Android開發(fā)者來說,具有鉆研的精神可能是我最后的倔強(qiáng)了毫别。首先是全網(wǎng)各種查找方案娃弓,無一例外,都是老舊的實(shí)現(xiàn)方法岛宦,中間確實(shí)想過放棄台丛,也討論過切換加密方案,但是又需要考慮切換加密方案對(duì)用戶的影響已經(jīng)各端的工作量砾肺,也就暫時(shí)擱淺了齐佳。
大概是思維進(jìn)入死胡同了,漫無目的的看著各種各樣的處理方法债沮。最后還是回歸到原始方法。

解決方案

方法一
dependencies {
    // 強(qiáng)強(qiáng)聯(lián)手
    implementation 'cn.hutool:hutool-all:5.8.21'
    implementation 'org.bouncycastle:bcprov-jdk15to18:1.76'
}
  • 實(shí)現(xiàn)
// 重要的導(dǎo)包
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import java.security.PublicKey;

import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.util.encoders.Hex;

import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.ECKeyUtil;

try {
            // 將.cer文件放置在Android應(yīng)用的res/raw目錄下本鸣。如果該目錄不存在疫衩,可以手動(dòng)創(chuàng)建它。
            // 請(qǐng)確保將your_certificate替換為你實(shí)際的.cer文件的名稱荣德,而且要處理異常以確保代碼的健壯性闷煤。
            InputStream is = getResources().openRawResource(R.raw. your_certificate);  // 讀取.cer文件童芹,不限于這種讀取方式
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate cert = (X509Certificate)cf.generateCertificate(is);
            is.close();

            PublicKey pk = cert.getPublicKey();
            byte[] pubkey = pk.getEncoded();
            byte[] publicKey_XY = readPublicKey(pubkey);
            // 獲取公鑰值,在公鑰的前面加上04前綴
            String pubkeyStr = "04" + bytesToHex(publicKey_XY);
            // 待加密內(nèi)容
            String jsonStr = "{\"name\":\"zhangsan\",\"userid\":\"1234567890\"}";
            final SM2 sm2 = new SM2(null, ECKeyUtil.toSm2PublicParams(pubkeyStr));
            sm2.usePlainEncoding();
            // jsonStr通過公鑰加密后的加密內(nèi)容鲤拿,轉(zhuǎn)成十六進(jìn)制
            String encryptStr = sm2.encryptHex(jsonStr, KeyType.PublicKey).toUpperCase();

            System.out.println(encryptStr);
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

public static byte[] readPublicKey(byte[] pubkey) throws Exception {
        SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(pubkey);
        DERBitString publicKeyData = (DERBitString) subjectPublicKeyInfo.getPublicKeyData();
        byte[] publicKey = publicKeyData.getEncoded();
        byte[] encodedPublicKey = publicKey;
        byte[] ecP = new byte[64];
        System.arraycopy(encodedPublicKey, 4, ecP, 0, ecP.length);

        // 公鑰的X值
        byte[] certPKX = new byte[32];
        // 公鑰的Y值
        byte[] certPKY = new byte[32];
        System.arraycopy(ecP, 0, certPKX, 0, 32);
        System.arraycopy(ecP, 32, certPKY, 0, 32);

        return ecP;
}

    /**
     * 字節(jié)轉(zhuǎn)16進(jìn)制
     * @param b 字節(jié)
     * @return 返回轉(zhuǎn)換后的十六進(jìn)制值
     */
    public static String byteToHex(byte b) {
        String hexString = Integer.toHexString(b & 0xFF);
        if (hexString.length() < 2) {
            hexString = new StringBuilder(String.valueOf(0)).append(hexString).toString();
        }
        return hexString.toUpperCase(Locale.ROOT);
    }

    /**
     * 字節(jié)數(shù)組轉(zhuǎn)16進(jìn)制
     * @param bytes 字節(jié)數(shù)組
     * @return 返回十六進(jìn)制字符串
     */
    public static String bytesToHex(byte[] bytes) {
        StringBuffer buffer = new StringBuffer();
        if (bytes != null && bytes.length > 0) {
            for (int i = 0; i < bytes.length; i++) {
                String hex = byteToHex(bytes[i]);
                buffer.append(hex);
            }
        }
        return buffer.toString();
    }
方法二
  • 使用OpenSSL庫或者使用gmssl
    兩大利器假褪,都是從c++底層去處理證書文件結(jié)構(gòu),這個(gè)就不會(huì)受系統(tǒng)大版本升級(jí)影響了近顷。我的iOS端app內(nèi)就是使用OpenSSL的方式去實(shí)現(xiàn)的生音。
    首先需要在Android端配置支持C++混合開發(fā)的環(huán)境,見官方介紹
  • native-lib.cpp
#include <jni.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fstream>
#include <iostream>
#include "openssl/crypto.h"
#include "openssl/x509.h"

using namespace std;

extern "C" JNIEXPORT jstring JNICALL
Java_com_zsplat_opensslsupportedapp_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    unsigned char usrCertificate[4096];
    const unsigned char *certDataBytes = NULL;
    unsigned long usrCertificateLen;
    X509 *x509Cert = NULL;
    FILE *fp = NULL;
    fp = fopen("./app/src/main/cpp/test.cer", "rb");
    if (fp == NULL) {
        cout << "讀取文件錯(cuò)誤窒升。缀遍。。" << endl;
    } else {

        usrCertificateLen = fread(usrCertificate, 1, 4096, fp);
        fclose(fp);
        certDataBytes = usrCertificate;
        x509Cert = d2i_X509(NULL, &certDataBytes, usrCertificateLen);
        if (x509Cert == NULL) {
            cout << "x509錯(cuò)誤" << endl;
            return "";
        }
        ASN1_BIT_STRING *pubkey = X509_get0_pubkey_bitstr(x509Cert);
        for (int i = 0; i < pubkey->length; i++) {
            // 打印公鑰
            printf("%02x", pubkey->data[i]);
            // 將(char *)data數(shù)據(jù)轉(zhuǎn)為string
        }
        // 返回公鑰即可
    }

    return env->NewStringUTF(OpenSSL_version(OPENSSL_VERSION));
}
  • CMakeLists.txt
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Declares and names the project.

project("native-lib")

#將openssl的頭文件目錄包含進(jìn)來
include_directories(src/main/cpp)

#添加兩個(gè)靜態(tài)庫文件
add_library(
        openssl-crypto
        STATIC
        IMPORTED)
set_target_properties(
        openssl-crypto
        PROPERTIES
        IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/src/main/cpp/libs/libcrypto.a)

add_library(
        openssl-ssl
        STATIC
        IMPORTED)
set_target_properties(
        openssl-ssl
        PROPERTIES
        IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/src/main/cpp/libs/libssl.a)

# 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.
#        opensslsupportedapp
        native-lib
        # Sets the library as a shared library.
        # SHARED不注釋程序會(huì)報(bào)錯(cuò)饱须,項(xiàng)目啟動(dòng)閃退域醇,不知道是不是這邊造成的,還沒找到解決辦法
#        SHARED
        # Provides a relative path to your source file(s).
        native-lib.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

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)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
#        opensslsupportedapp
        native-lib
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib}
        openssl-ssl
        openssl-crypto)

項(xiàng)目中的.a靜態(tài)文件庫是使用NDK和OpenSSL源碼編譯的蓉媳,根據(jù)Android Studio中使用的NDK版本去做編譯譬挚。具體編譯方法可以去搜索。后續(xù)抽空寫一個(gè)關(guān)于編譯OpenSSL靜態(tài)庫的操作吧酪呻。

可能安卓端配置C++環(huán)境對(duì)我來說稍微麻煩點(diǎn)减宣,不過只要配置好,那是真的方便号杠,對(duì)于大部分加密的問題蚪腋,OpenSSL都能解決的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末姨蟋,一起剝皮案震驚了整個(gè)濱河市屉凯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌眼溶,老刑警劉巖悠砚,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異堂飞,居然都是意外死亡灌旧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門绰筛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枢泰,“玉大人,你說我怎么就攤上這事铝噩『饴欤” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)毛甲。 經(jīng)常有香客問我年叮,道長(zhǎng),這世上最難降的妖魔是什么玻募? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任只损,我火速辦了婚禮,結(jié)果婚禮上七咧,老公的妹妹穿的比我還像新娘跃惫。我一直安慰自己,他們只是感情好坑雅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布辈挂。 她就那樣靜靜地躺著,像睡著了一般裹粤。 火紅的嫁衣襯著肌膚如雪终蒂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天遥诉,我揣著相機(jī)與錄音拇泣,去河邊找鬼。 笑死矮锈,一個(gè)胖子當(dāng)著我的面吹牛霉翔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播苞笨,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼债朵,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了瀑凝?” 一聲冷哼從身側(cè)響起序芦,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎粤咪,沒想到半個(gè)月后谚中,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寥枝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年宪塔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片囊拜。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡某筐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出冠跷,到底是詐尸還是另有隱情来吩,我是刑警寧澤敢辩,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站弟疆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏盗冷。R本人自食惡果不足惜怠苔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一露氮、第九天 我趴在偏房一處隱蔽的房頂上張望胎许。 院中可真熱鬧,春花似錦拷呆、人聲如沸锅劝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽故爵。三九已至玻粪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诬垂,已是汗流浹背劲室。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留结窘,地道東北人很洋。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像隧枫,于是被迫代替她去往敵國(guó)和親喉磁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容