1. 程式人生 > >小王的尷尬日常(一)--使用RSA公鑰證書解密

小王的尷尬日常(一)--使用RSA公鑰證書解密

最近接了一個活,要用rsa的公鑰解密,這個公鑰儲存在公鑰證書裡面,這個公鑰證書呢… 安裝在了windows作業系統裡。以下是講解部分,如果不想看的直接跳躍到最末尾的程式碼部分。
公鑰–>公鑰證書–>公鑰證書庫(Windows)
我要是使用它呢,就要反向過來:
解密<–提取公鑰<–找到公鑰證書<–開啟公鑰證書庫(Windows)

當時我想這活沒難度so easy, 可第一步就有點尷尬啊,先說這個公鑰證書庫的問題:
第一個證書庫相關的函式,是CertOpenStore, 天下套路千千萬,開啟控制代碼佔一半。無論是Windows還是linux這些個系統函式設計都是一個套路,遵循的樹狀結構。

HCERTSTORE WINAPI CertOpenStore(
__in LPCSTR lpszStoreProvider,
__in DWORD dwMsgAndCertEncodingType,
__in HCRYPTPROV_LEGACY hCryptProv,
__in DWORD dwFlags,
__in const void* pvPara
);
Instruction :The CertOpenStore function opens a certificate store by using a specified store provider type. While this function can open a certificate store for most purposes, CertOpenSystemStore is recommended to open the most common certificate stores. CertOpenStore is required for more complex options and special cases.

英文不好的兄弟自行腦補,反正就是開啟一個證書庫的操作控制代碼。
然後咱們仔細推敲一下這個函式,咱們先說第一個引數 lpszStoreProvider ,這個引數很重要,它決定了你以後的命運。

lpszStoreProvider
A pointer to a null-terminated ANSI string that specifies the store provider type.

就是選擇一下你開啟的證書庫型別,它有很多種選擇,常用的有以下幾種:

CERT_STORE_PROV_MEMORY 記憶體裡的證書庫,程式裡面建立用來在記憶體裡儲存證書
CERT_STORE_PROV_SYSTEM 系統裡的證書庫,儲存在系統裡面的證書庫
CERT_STORE_PROV_FILENAME 檔案裡面的證書庫,儲存在檔案裡面的證書庫
CERT_STORE_PROV_COLLECTION 證書庫的集合,一般是程式裡面建立用來新增其他證書庫的

上面這四種是比較常用的,而我面臨的情況是證書被安裝在了系統裡面,所以自然要用到 CERT_STORE_PROV_SYSTEM

咱們再說第二個引數 dwMsgAndCertEncodingType,這個就不仔細說了,證書編碼格式,主要有以下兩種:

PKCS_7_ASN_ENCODING
Specifies PKCS #7 message encoding.

X509_ASN_ENCODING
Specifies X.509 certificate encoding

第三個引數 hCryptProv
這個引數是讓你選擇csp,這個直接NULL就好了,預設就可以了。

第四個引數 dwFlags
這個引數,結合第一個引數的CERT_STORE_PROV_SYSTEM能打出很多種套路,我就說兩個常用的:

CERT_SYSTEM_STORE_LOCAL_MACHINE 本地系統證書庫
CERT_SYSTEM_STORE_CURRENT_USER 當前使用者證書庫

這個選擇完了還不算完,我們還有第五個引數 pvPara ,這個引數我們要指定證書的型別,我們知道在Windows下面證書劃分為:個人、中級證書頒發機構、受信認的根證書頒發機構、受信任的釋出者、其他人。但是這第五個引數怎麼填?跟以上這幾個型別都無關。
這裡寫圖片描述
這是系統證書庫的登錄檔儲存結構,路徑為:
HKEY_CURRENT_USER(或HKEY_LOCAL_MACHINE)\Software\Microsoft\SystemCertificates,比如說我的是中級證書頒發機構,儲存的子鍵名為 CA,我就把‘CA’作為第五個引數。

HCERTSTORE hSysStore = CertOpenStore(
        CERT_STORE_PROV_SYSTEM_A,        // The store provider type
        0,                               // The encoding type is
        NULL,                            // Use the default HCRYPTPROV
        CERT_SYSTEM_STORE_CURRENT_USER,  // Set the store location CERT_SYSTEM_STORE_CURRENT_USER | CERT_SYSTEM_STORE_LOCAL_MACHINE
        "CA"                             // The store name as a Unicode "Root"|"TrustedPeople"|"TrustedPublisher" |"MY"|"TrustedPublisher"   
        ))

接下來,我們要找到自己想要的那個證書,大家知道證書有很多的屬性,比如序列號、有效日期、使用者、頒發者、演算法、編碼格式等等,我以其中頒發者作為key,來找到這個證書。網上有很多人說用CertFindCertificateInStore,而我選擇了CertEnumCertificatesInStore,其實都一樣。

    PCCERT_CONTEXT  pDesiredCert; 
    while(pDesiredCert = CertEnumCertificatesInStore(
        hSysStore,
        pDesiredCert))
    {

        CertGetNameString(pDesiredCert, CERT_NAME_SIMPLE_DISPLAY_TYPE,  0, NULL, issuser, MAX_PATH);
        printf("--%s\n", issuser);

        CertGetNameString(pDesiredCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, NULL, issuser, MAX_PATH);
        printf("--%s\n", issuser);

        if (!strcmp(ISSUSER, issuser))
        {
            return (0);
        }   
    }

讓我尷尬的第一步完成了,接下來的事情更尷尬,本著一路懟到底的樸素思想,既然解析證書用的是WINAPI 那後面解密就用 CryptoAPI 。
CryptoAPI它神奇的地方在於不能直接用來加密解密,它加解密的步驟是這樣的:

 //先開啟一個容器
 CryptAcquireContext(....)

 //匯入key或者產生Key 
 CryptImportKey(.....) 
 CryptGenKey(....)

//用Key進行加密或解密
CryptEncrypt(... ..)
CryptDecrypt(... ..)

我乖乖的把以上步驟都做完了,更尷尬的事情出現了,解密的時候返回錯誤:0x8009000D 沒有對應的Key選項,我查看了一下記憶體,有值啊,於是我開始網路搜尋,最後再msdn的官網上找到了一個權威的解釋,大體意思如下:

CryptEncrypt 解密的時候預設用私鑰解密,當只匯入公鑰的時候就會報 0x8009000D 沒有對應的Key的錯誤。

我這不r了大象了,我只用公鑰啊,上哪給你找私鑰去??當我懵逼了三秒之後想出了兩種解決方案:
第一種,把公鑰當做私鑰匯入到容器裡面;
第二種,把公鑰的引數解析出來,用openssl的演算法去解密。

聰明機智的我最終選擇了第二個方案….
openssl 裡面有一個函式 RSA_public_decrypt ,這個完美解決我遇到的尷尬:

int RSA_public_decrypt(
    int flen, //n 的長度 
    const unsigned char *from, //密文資料
    unsigned char *to, //解密資料
    RSA *rsa, //RSA結構體
    int padding); //是否填充

這裡面別的引數還好說,最關鍵的就是這個RSA結構體,我們先看CryptoApi裡面公鑰結構

typedef struct _PUBLICKEYSTRUC {
        BYTE    bType;
        BYTE    bVersion;
        WORD    reserved;
        ALG_ID  aiKeyAlg;
} BLOBHEADER, PUBLICKEYSTRUC;

typedef struct _RSAPUBKEY {
        DWORD   magic;                  // Has to be RSA1
        DWORD   bitlen;                 // # of bits in modulus
        DWORD   pubexp;                 // public exponent
                                        // Modulus data follows
} RSAPUBKEY;

BYTE modulus[rsapubkey.bitlen/8];
//以上為公鑰結構,公私鑰對還包含以下
BYTE prime1[rsapubkey.bitlen/16];
BYTE prime2[rsapubkey.bitlen/16];
BYTE exponent1[rsapubkey.bitlen/16];
BYTE exponent2[rsapubkey.bitlen/16];
BYTE coefficient[rsapubkey.bitlen/16];
BYTE privateExponent[rsapubkey.bitlen/8];

我們再看一下openssl 裡面RSA的結構體:


struct rsa_st
    {
    /* The first parameter is used to pickup errors where
     * this is passed instead of aEVP_PKEY, it is set to 0 */
    int pad;
    long version;
    const RSA_METHOD *meth; 
    /* functional reference if 'meth' is ENGINE-provided */
    ENGINE *engine;
    BIGNUM *n;
    BIGNUM *e;
    BIGNUM *d;
    BIGNUM *p;
    BIGNUM *q;
    BIGNUM *dmp1;
    BIGNUM *dmq1;
    BIGNUM *iqmp;
    /* be careful using this if the RSA structure is shared */
    CRYPTO_EX_DATA ex_data;
    int references;
    int flags;

    /* Used to cache montgomery values */
    BN_MONT_CTX *_method_mod_n;
    BN_MONT_CTX *_method_mod_p;
    BN_MONT_CTX *_method_mod_q;

    /* all BIGNUM values are actually in the following data, if it is not
     * NULL */
    char *bignum_data;
    BN_BLINDING *blinding;
    BN_BLINDING *mt_blinding;
    };

裡面進行公鑰解密時必須的引數為:

const RSA_METHOD *meth;
BIGNUM *n;
BIGNUM *e;

第一個引數是rsa加解密時要用到函式的地址,n和e是我們需要填充的:

RSA.n 對應的是 BYTE modulus[rsapubkey.bitlen/8];
RSA.e 對應的是 RSAPUBKEY.pubexp

下面是完整的程式碼:


#include<openssl/rsa.h>
#include<openssl/pem.h>
#include<openssl/err.h>

#define ISSUSER 
#define CONTIANER 

#define BUFSIZE 4096

HCERTSTORE hSysStore;

PCCERT_CONTEXT  pDesiredCert;   

HCRYPTPROV hTmpProv;

HCRYPTKEY hKey;

HANDLE hStoreFileHandle; 

char lpPathBuffer[MAX_PATH];

char szTempName[MAX_PATH];  

BYTE Rsa_n[BUFSIZE] = {0};

RSA* pRSAPubKey;

BN_ULONG Rsa_e;

//ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/seccrypto/security/base_provider_key_blobs.htm

//遍歷找到公鑰證書
int GetCerContext()
{
    if(hSysStore = CertOpenStore(
        CERT_STORE_PROV_SYSTEM_A,        // The store provider type
        0,                               // The encoding type is
        NULL,                            // Use the default HCRYPTPROV
        CERT_SYSTEM_STORE_CURRENT_USER,  // Set the store location CERT_SYSTEM_STORE_CURRENT_USER | CERT_SYSTEM_STORE_LOCAL_MACHINE
        "CA"                             // The store name as a Unicode "Root"|"TrustedPeople"|"TrustedPublisher" |"MY"|"TrustedPublisher"   
        ))
    {
        printf("The system store was created successfully.\n");
    }
    else
    {
        printf("An error occurred during creation "
            "of the system store \n");
        return GetLastError();
    }

    char issuser[MAX_PATH] = {0};
    while(pDesiredCert = CertEnumCertificatesInStore(
        hSysStore,
        pDesiredCert))
    {

        CertGetNameString(pDesiredCert, CERT_NAME_SIMPLE_DISPLAY_TYPE,  0, NULL, issuser, MAX_PATH);
        printf("--%s\n", issuser);

        CertGetNameString(pDesiredCert, CERT_NAME_SIMPLE_DISPLAY_TYPE, CERT_NAME_ISSUER_FLAG, NULL, issuser, MAX_PATH);
        printf("--%s\n", issuser);

        if (!strcmp(ISSUSER, issuser))
        {
            return (0);
        }   
    }

    return GetLastError();
}

//將公鑰匯入容器
int ImportKey()
{

    bool bRet = CryptAcquireContext(&hTmpProv, NULL, NULL,
        PROV_RSA_FULL, CRYPT_NEWKEYSET); // NULL表示使用系統預設CSP

    DWORD dwError = GetLastError();

    if (dwError != 0x8009000f
        && dwError != 0x00)
    {
        return dwError;
    }

    bRet = CryptAcquireContext(&hTmpProv, CONTIANER, NULL,
        PROV_RSA_FULL, CRYPT_MACHINE_KEYSET); // NULL表示使用系統預設CSP

    if (!bRet)
    {
        return GetLastError();
    }

    bRet = CryptImportPublicKeyInfo(hTmpProv, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING,
        &pDesiredCert->pCertInfo->SubjectPublicKeyInfo, &hKey);

    if (!bRet)
    {
        return GetLastError();
    }

    return 0;
}

int GetRSAPubKey()
{
    DWORD cbPubKeyBlob = BUFSIZE;

    PUBLICKEYSTRUC sPubKey;
    RSAPUBKEY      sRSAPubKey;
    DWORD dwOffset = 0;

    BYTE PubKeyBlob[BUFSIZE] = {0};

    BOOL bRet = CryptExportKey(hKey, 0, 
        PUBLICKEYBLOB, 0, PubKeyBlob, &cbPubKeyBlob);

    if (!bRet)
    {
        return GetLastError();
    }

    memcpy(&sPubKey, &PubKeyBlob[dwOffset], sizeof(PUBLICKEYSTRUC));

    dwOffset += sizeof(PUBLICKEYSTRUC);
    memcpy(&sRSAPubKey, &PubKeyBlob[dwOffset], sizeof(RSAPUBKEY));

    dwOffset += sizeof(RSAPUBKEY);
    memcpy(Rsa_n, &PubKeyBlob[dwOffset], sRSAPubKey.bitlen/8);

    pRSAPubKey =  RSA_new();


    pRSAPubKey->n = BN_new();
    pRSAPubKey->e = BN_new();

    pRSAPubKey->n->d = (BN_ULONG*)(&Rsa_n[0]);
    pRSAPubKey->n->top = sRSAPubKey.bitlen/32 ;
    pRSAPubKey->n->dmax = pRSAPubKey->n->top + 1;
    pRSAPubKey->n->flags = 1;
    pRSAPubKey->n->neg = 0;

    Rsa_e = sRSAPubKey.pubexp;
    BN_set_word(pRSAPubKey->e, Rsa_e);

    return 0;
}

//解密hash值
int DeCryptHash(char* pEnCryptData, char* pDestData)
{
    RSA *p_rsa; 
    int rsa_len = RSA_size(pRSAPubKey);

    RSA_public_decrypt(rsa_len, pEnCryptData, pDestData, pRSAPubKey, RSA_NO_PADDING);

    //NTE_BAD_FLAGS

    return 0;
}

//釋放資源
void Rlease()
{

    if (hKey)
    {
        CryptDestroyKey(hKey);
    }

    if (hTmpProv)
    {
        CryptReleaseContext(hTmpProv, 0);
    }

    if(pDesiredCert)
    {
        CertFreeCertificateContext(pDesiredCert);
    }


    if(hSysStore)
    {
        CertCloseStore(hSysStore, CERT_CLOSE_STORE_CHECK_FLAG);
    }

    if (hStoreFileHandle != NULL 
        && hStoreFileHandle != INVALID_HANDLE_VALUE)
    {
        CloseHandle(hStoreFileHandle);
    }

    return;
}

int _tmain(int argc, _TCHAR* argv[])
{

    //GetCerContext();
    //SaveCerfication();

    GetCerContext();
    ImportKey();
    GetRSAPubKey();
    DeCryptHash(...., ....);

    Rlease();
    return 0;
}