RSA加密:利用模數和指數生成公鑰加密


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

<RSAKeyValue>
<Modulus>
wVwBKuePO3ZZbZ//gqaNuUNyaPHbS3e2v5iDHMFRfYHS/bFw+79GwNUiJ+wXgpA7SSBRhKdLhTuxMvCn1aZNlXaMXIOPG1AouUMMfr6kEpFf/V0wLv6NCHGvBUK0l7O+2fxn3bR1SkHM1jWvLPMzSMBZLCOBPRRZ5FjHAy8d378=
</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
View Code

推薦看下這篇博客快速了解一下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;
};
View Code

  開始推薦的博客中有關於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;
View Code

  一番嘗試后沒有搞定,之后轉向第三方庫,比如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);
    }
}
View Code

  生成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) 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM