OpenSSL: RSA分段解密實戰

簡介

本篇是繼 OpenSSL: 簡單易上手的RSA加解密 後的補充篇,實戰篇。html

在實際項目中,並無像上篇文章寫的那麼簡單,實際狀況要複雜的多。萬變不離其宗,抽絲剝繭,復瑣事務的背後必定是有其本質緣由和原理的存在,而咱們就是挖掘原理,探索本質的福爾摩斯。c++

今天這篇文章,帶領你們參與到實際項目中運用RSA加解密,在閱讀下面內容以前,指望你們能夠下載 openssl 的源碼,或者下載我 上篇文中 的代碼示例。git

我下載的是 openssl-source-1.1.0f 這個版本的源碼,正好對應我從 precompiled-openssl 下載的編譯版本。 github

在這裏插入圖片描述

項目概述

該項目的開發語言仍然採用C語言來實現,咱們藉助 openssl 來模擬實際項目中的案例。安全

服務端使用 RSA 加密原始數據,而後採用 Base64 編碼該加密數據通過 HTTP 傳輸給到客戶端;app

客戶端接收到該數據,先使用 Base64 解碼數據,而後再使用 RSA 解密數據,最終獲得原始數據。函數

這裏特別注意,客戶端收到的數據大小可能會大於 128 字節,咱們知道 RSA 加密明文最大長度 117 字節,而解密的最大值是 128 字節,因此超過該大小須要分段解密數據。ui

大概流程圖以下: this

在這裏插入圖片描述
很簡單的一個項目,對吧,接着往下看吧 :)-

解個小惑

也許有些朋友會問,爲毛 RSA 加密的明文大小是 117 字節,而解密的最大字節數是 128 字節,二者同樣不是更好嗎,至少好理解呀?編碼

得出上面結論的前提是咱們RSA密鑰長度是 1024 位即 128 字節(1024/8=128),同理若是是 512 位的密鑰,那麼最大的 RSA 解密字節長度應該是(512/8)64 字節,最大加密的明文長度是(64-11)53 字節。

在 openssl 源碼中,咱們能夠看到以下代碼:

# define RSA_PKCS1_PADDING_SIZE 11
複製代碼

rsa_sign.c 文件中能夠看到 RSA_sign 函數:

int RSA_sign(int type, const unsigned char *m, unsigned int m_len, unsigned char *sigret, unsigned int *siglen, RSA *rsa) {
    int encrypt_len, encoded_len = 0, ret = 0;
    unsigned char *tmps = NULL;
    const unsigned char *encoded = NULL;

    if (rsa->meth->rsa_sign) {
        return rsa->meth->rsa_sign(type, m, m_len, sigret, siglen, rsa);
    }

    /* Compute the encoded digest. */
    if (type == NID_md5_sha1) {
        /* * NID_md5_sha1 corresponds to the MD5/SHA1 combination in TLS 1.1 and * earlier. It has no DigestInfo wrapper but otherwise is * RSASSA-PKCS1-v1_5. */
        if (m_len != SSL_SIG_LENGTH) {
            RSAerr(RSA_F_RSA_SIGN, RSA_R_INVALID_MESSAGE_LENGTH);
            return 0;
        }
        encoded_len = SSL_SIG_LENGTH;
        encoded = m;
    } else {
        if (!encode_pkcs1(&tmps, &encoded_len, type, m, m_len))
            goto err;
        encoded = tmps;
    }

    if (encoded_len > RSA_size(rsa) - RSA_PKCS1_PADDING_SIZE) {
        RSAerr(RSA_F_RSA_SIGN, RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY);
        goto err;
    }
    encrypt_len = RSA_private_encrypt(encoded_len, encoded, sigret, rsa,
                                      RSA_PKCS1_PADDING);
    if (encrypt_len <= 0)
        goto err;

    *siglen = encrypt_len;
    ret = 1;

err:
    OPENSSL_clear_free(tmps, (size_t)encoded_len);
    return ret;
}
複製代碼

能夠看出,RSA_PKCS1_PADDING 這種填充模式是佔用了 11 個字節的,那麼 127+11 正好也是 128 字節。

每次RSA加密的明文的長度是受RSA填充模式限制的,以下表:

填充方式 輸入 輸出 備註
RSA_PKCS1_PADDING 必須比RSA鑰模長(modulus) 短至少11個字節, 也就是RSA_size(rsa) – 11,對於1024bit的密鑰,RSA_size(rsa)=128字節,即明文爲128-11=117字節;若是輸入的明文過長,必須切割,而後填充。 和modulus同樣長 最經常使用的填充方式
RSA_PKCS1_OAEP_PADDING RSA_size(rsa) – 41 和modulus同樣長 最優非對稱填充OAEP,安全性是最高的
RSA_NO_PADDING 能夠和RSA鑰模長同樣長,若是輸入的明文過長,必須切割,而後填充。 和modulus同樣長 -

這裏注意下面結論:

  • 在不一樣的padding模式下,使用相同長度的密鑰能夠加密的數據最大長度不一樣;
  • 在不一樣密鑰長度下,使用相同的padding模式能夠加密的數據最大長度也不一樣;

能夠閱讀 rfc2313 中關於 PKCS #1: RSA Encryption Version 1.5 的部分。

開戰

實戰代碼主要在 main.c 文件中的 example_rsa3() 函數中。

原始數據是字符串 www.veryitman.com,以下還包括了公私鑰。

// 原始數據爲字符串:www.veryitman.com
	unsigned char plainText[] = "www.veryitman.com";

	unsigned char publicKey[] = "-----BEGIN PUBLIC KEY-----\n"
		"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrPgCMJW17JN2DW7tZFk/FB6pU\n"
		"pLvLOo6G/EuND8XZptffXbyiY2VscMRhP+kKVeaLO9HuEYR3Zl78x8oR6prytstc\n"
		"/MueersWDxh4iGSHsZXGxA41hXrXLRElrSTRc43ea18o0zMxZoVZiR2JFt7QcgM+\n"
		"T6eOrvj59MhXv9O46QIDAQAB\n"
		"-----END PUBLIC KEY-----\n";

	unsigned char privateKey[] = "-----BEGIN RSA PRIVATE KEY-----\n"
		"MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKs+AIwlbXsk3YNb\n"
		"u1kWT8UHqlSku8s6job8S40Pxdmm199dvKJjZWxwxGE/6QpV5os70e4RhHdmXvzH\n"
		"yhHqmvK2y1z8y556uxYPGHiIZIexlcbEDjWFetctESWtJNFzjd5rXyjTMzFmhVmJ\n"
		"HYkW3tByAz5Pp46u+Pn0yFe/07jpAgMBAAECgYBj1YH8MtXhNVzveEuBZMCc3hsv\n"
		"vdq+YSU3DV/+nXN7sQmp77xJ8CjxT80t5VS38dy2z+lUImJYOhamyNPGHkC2y84V\n"
		"7i5+e6ScQve1gnwHqRKGBjtSCaYOqm9rTDECCTT1oMU26sfYznWlJqMrkJp1jWn7\n"
		"aAwr+3FcX2XhD74ZAQJBAN34Y6fmHLRPv21MsdgGqUjKgyFvJfLUmtFFgb6sLEWc\n"
		"k22J3BAFAcNCTLYHFZwMhL/nwaw9/7rIUJD+lcl6n3cCQQDFfrN14qKC3GJfoBZ8\n"
		"k9S6F7Ss514DDPzIuenbafhoUjZDVcjLw9EmYZQjpfsQ3WdNICUKRrDHZay1Pz+s\n"
		"YkKfAkB+OKfaquS5t/t/2LPsxuTuipIEqiKnMjSTOfYsidVnBEFlcZZc2awF76aV\n"
		"f/PO1+OJCO2910ebXBtMSCi++GbDAkEAmc7zNPwsVH4OnyquWJdJNSUBMSd/sCCN\n"
		"PkaMOrVtINHmMMq+dvMqEBoupRS/U4Ma0JYYQsiLJL+qof2AOWDNQQJAcquLGHLT\n"
		"eGDDLluHo+kkIGwZi4aK/fDoylZ0NCEtYyMtShQ3JmllST9kmb9NJX2gMsejsirc\n"
		"H6ObxqZPbka6UA==\n"
		"-----END RSA PRIVATE KEY-----\n";
複製代碼

對數據進行私鑰加密,示例以下:

// 私鑰加密
int encrypted_length = private_key_encrypt(plainText, len, privateKey, encrypted_str);
if (-1 == encrypted_length)
{
	printf("Private Encrypt failed\n");
	exit(0);
}
複製代碼

私鑰加密以後,進行 Base64 編碼:

char *base64_content;
size_t encrypted_str_length = strlen(encrypted_str);
int encode_res = mzc_base64_encode(encrypted_str, encrypted_str_length, &base64_content);
if (0 != encode_res)
{
	printf("Base64 encode failed\n");
	exit(0);
}
printf("Base64 encode content: %s\n\n", base64_content);
printf("Base64 encode content's length: %i\n\n", strlen(base64_content));
複製代碼

至此,上面兩個步驟就模擬完成了服務端加密的過程。下面咱們來繼續模擬客戶端解密的過程。

首先,對 Base64 編碼以後的數據進行 Base64 解碼。

char *base64DecodeOutput;
size_t decode_output_length;
int decode_res = mzc_base64_decode(base64_content, &base64DecodeOutput, &decode_output_length);
printf("base64 decode content: %s\n\n", base64DecodeOutput);
printf("base64 decode content's length: %i\n\n", decode_output_length);
if (0 != decode_res)
{
    printf("Base64 decode failed\n");
    exit(0);
}
複製代碼

看一下打印結果:

base64 decode content's length: 160
複製代碼

很明顯,長度要大於 128,須要進行分段處理。

// 最大解密長度
#define RSA_MAX_DECRYPT_SIZE 128

// 每段解密的長度
int chunk = 0;
unsigned char tmp_dstr[RSA_MAX_DECRYPT_SIZE];
memset(tmp_dstr, '\0', sizeof(tmp_dstr));

// (數據被)分段解密(公鑰解密)
while (chunk <= decode_output_length)
{
    int decrypted_length = public_key_decrypt(base64DecodeOutput, RSA_MAX_DECRYPT_SIZE, publicKey, tmp_dstr);
    memcpy(decrypted_str, tmp_dstr, decrypted_length);
    printf("Current decrypted content length =%d\n", decrypted_length);
    if (-1 == decrypted_length)
    {
        printf("Public Decrypt failed\n");
        exit(0);
    }
    chunk += decrypted_length;
}

printf("......\n\n");
printf("Final decrypted string =%s\n", decrypted_str);
複製代碼

輸出結果:

......

Final decrypted string =www.veryitman.com

複製代碼

至此整個過程簡單模擬結束。

你們若是感興趣的話,能夠實現分段加密的過程。我就再也不演示這個過程了,後續加入到源代碼中去。


問君能有幾多愁,恰似一江春水向東流。

在這裏插入圖片描述
相關文章
相關標籤/搜索