引子
目前做一款金融產品,由於涉及到資金安全,采用動態公鑰的方式,即客戶端每次登錄服務端返回一個不同的XML串,由公鑰的模數和指數構成,我需要用這個串生成公鑰加密相關信息。
服務端返回的XML串形如:

<RSAKeyValue> <Modulus> wVwBKuePO3ZZbZ//gqaNuUNyaPHbS3e2v5iDHMFRfYHS/bFw+79GwNUiJ+wXgpA7SSBRhKdLhTuxMvCn1aZNlXaMXIOPG1AouUMMfr6kEpFf/V0wLv6NCHGvBUK0l7O+2fxn3bR1SkHM1jWvLPMzSMBZLCOBPRRZ5FjHAy8d378= </Modulus> <Exponent>AQAB</Exponent> </RSAKeyValue>
推薦看下這篇博客快速了解一下RSA:http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html
問題
- 對RSA不了解。
- 如何用所謂的模數和指數生成公鑰來加密相關信息。
過程
熟悉RSA。先看下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; };
開始推薦的博客中有關於RSA模數和指數的介紹,對應到結構中分別是其中的 n 和 e ,模反數對應d,最開始的質數因子對應 p和 q。n和e決定了公鑰,n和d決定了私鑰。結構體中其它元素不論,能知道的是模數和指數決定了公鑰。
如果能生成一個der或者pem文件,就可以用系統的方法去獲取公鑰,如下:

// 1 der證書的base64編碼形式 NSString *cert = @"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBXAEq5487dlltn/+Cpo25Q3Jo8dtLd7a/mIMcwVF9gdL9sXD7v0bA1SIn7BeCkDtJIFGEp0uFO7Ey8KfVpk2Vdoxcg48bUCi5Qwx+vqQSkV/9XTAu/o0Ica8FQrSXs77Z/GfdtHVKQczWNa8s8zNIwFksI4E9FFnkWMcDLx3fvwIDAQAB"; // 2 解碼base64 NSData *publicKeyFileContent = [NSData dataFromBase64String:cert]; // 3 創建der證書對象 certificate = SecCertificateCreateWithData(kCFAllocatorDefault, ( __bridge CFDataRef)publicKeyFileContent); if (certificate == nil) { DLog(@"Can not read certificate from pub.der"); return nil; } // 4 驗證證書 policy = SecPolicyCreateBasicX509(); OSStatus returnCode = SecTrustCreateWithCertificates(certificate, policy, &trust); if (returnCode != 0) { DLog(@"SecTrustCreateWithCertificates fail. Error Code: %ld", returnCode); return nil; } // 5 返回公鑰 SecTrustResultType trustResultType; returnCode = SecTrustEvaluate(trust, &trustResultType); if (returnCode != 0) { DLog(@"SecTrustEvaluate fail. Error Code: %ld", returnCode); return nil; } // 不管是否信任都會嘗試返回公鑰,也有可能被信任但是返不回公鑰 publicKey = SecTrustCopyPublicKey(trust); if (publicKey == nil) { DLog(@"SecTrustCopyPublicKey fail"); return nil; } maxPlainLen = SecKeyGetBlockSize(publicKey) - 12;
一番嘗試后沒有搞定,之后轉向第三方庫,比如openssl,openssl用C++實現。可以借助該庫用模數和指數構造公鑰。在網上也找到一篇解決這個問題的博客:Converting RSA public key Modulus and Exponent into PEM file

unsigned char *base64_decode(const char* base64data, int* len) { BIO *b64, *bmem; size_t length = strlen(base64data); unsigned char *buffer = (unsigned char *)malloc(length); b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); bmem = BIO_new_mem_buf((void*)base64data, length); bmem = BIO_push(b64, bmem); *len = BIO_read(bmem, buffer, length); BIO_free_all(bmem); return buffer; } BIGNUM* bignum_base64_decode(const char* base64bignum) { BIGNUM* bn = NULL; int len; unsigned char* data = base64_decode(base64bignum, &len); if (len) { bn = BN_bin2bn(data, len, NULL); } free(data); return bn; } EVP_PKEY* RSA_fromBase64(const char* modulus_b64, const char* exp_b64) { BIGNUM *n = bignum_base64_decode(modulus_b64); BIGNUM *e = bignum_base64_decode(exp_b64); if (!n) printf("Invalid encoding for modulus\n"); if (!e) printf("Invalid encoding for public exponent\n"); if (e && n) { EVP_PKEY* pRsaKey = EVP_PKEY_new(); RSA* rsa = RSA_new(); rsa->e = e; rsa->n = n; EVP_PKEY_assign_RSA(pRsaKey, rsa); return pRsaKey; } else { if (n) BN_free(n); if (e) BN_free(e); return NULL; } } void assert_syntax(int argc, char** argv) { if (argc != 4) { fprintf(stderr, "Description: %s takes a RSA public key modulus and exponent in base64 encoding and produces a public key file in PEM format.\n", argv[0]); fprintf(stderr, "syntax: %s <modulus_base64> <exp_base64> <output_file>\n", argv[0]); exit(1); } }
生成pem文件之后可以從pem文件里面讀取公鑰加密相關信息。
最后封裝了一下,放在github上:https://github.com/yrs244742688/GeneratePemWithMoAndEx
注解
ASN.1:ASN.1抽象語法標記(Abstract Syntax Notation One) ASN.1是一種 ISO/ITU-T 標准,描述了一種對數據進行表示、編碼、傳輸和解碼的數據格式。它提供了一整套正規的格式用於描述對象的結構,而不管語言上如何執行及這些數據的具體指 代,也不用去管到底是什么樣的應用程序。標准的ASN.1編碼規則有基本編碼規則(BER,Basic Encoding Rules)、規范編碼規則(CER,Canonical Encoding Rules)、唯一編碼規則(DER,Distinguished Encoding Rules)、壓縮編碼規則(PER,Packed Encoding Rules)和XML編碼規則(XER,XML Encoding Rules)。
x.509 : 常見通用的證書格式。
der:DER是ASN.1編碼規則的其中一種。x.509證書通過DER編碼(ASCII)后綴是:.DER .CER .CRT,通過PAM編碼(Base64)的后綴是:.PEM .CER .CRT。.cer/.crt是用於存放證書,它是2進制形式存放的,不含私鑰。
pem文件 : der文件經過base64轉碼后的文件。
(Email: yangxu0905@foxmail.com)