2014-06-07 51 views
1

我有此JWK(來自http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-26#appendix-A.1):如何從JWK將私鑰加載到openSSL中?

{"kty":"RSA", 
     "n":"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx 
      HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs 
      D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH 
      SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV 
      MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8 
      NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ", 
     "e":"AQAB", 
     "d":"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I 
      jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0 
      BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn 
      439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT 
      CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh 
      BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ" 
     } 

我需要將它加載到一個OpenSSL的RSA結構,這樣我可以送入一個EVP_SignFinal呼叫此。什麼格式是「D」? PEM?或二進制?我如何將它加載到一個rsa結構中?

+0

很明顯,你base64解碼d的值 - 但那又如何? –

+1

我不相信OpenSSL有'PRSA'。也許你在想另一個圖書館? – jww

+1

Doh! 「PRSA」是出於delphi翻譯的頭文件。更正了問題 –

回答

3

什麼格式是「d」? PEM?或二進制?

格式Base64URL編碼或 「Base 64編碼與URL和文件名安全字母」 的RFC 4648(見第7頁的第5,表2)。


我怎麼把它加載到一個RSA結構?

行,所以OpenSSL 痛。要將其加載到RSA結構中,您不需要將n,edBase64URL轉換爲Base64。以下是我做到了在加密+(你可以做到這一點在OpenSSL的,但它會傷害):

string nz = "ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx" 
      "HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs" 
      "D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH" 
      "SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV" 
      "MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8" 
      "NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ"; 

string ez = "AQAB"; 

string dz = "Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I" 
      "jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0" 
      "BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn" 
      "439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT" 
      "CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh" 
      "BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ"; 

string nn, ee, dd; 

// First, convert Base64URL encoding to Base64 
std::replace(nz.begin(), nz.end(), '-', '+'); 
std::replace(ez.begin(), ez.end(), '-', '+'); 
std::replace(dz.begin(), dz.end(), '-', '+'); 
std::replace(nz.begin(), nz.end(), '_', '/'); 
std::replace(ez.begin(), ez.end(), '_', '/'); 
std::replace(dz.begin(), dz.end(), '_', '/'); 

// Now, Base64 decode 
StringSource ss1(nz, true, new Base64Decoder(new StringSink(nn))); 
StringSource ss2(ez, true, new Base64Decoder(new StringSink(ee))); 
StringSource ss3(dz, true, new Base64Decoder(new StringSink(dd))); 

編輯:加密現在++有Base64URLEncoderBase64URLDecoder類,所以你不需要查找/替換操作。

在上面的代碼運行後,nn,eedd是二進制字符串(即非ASCII字符)。從那裏,你可以將其加載到Integer的,並得到了10個基本串:

Integer n((byte*)nn.data(), nn.size()); 
Integer e((byte*)ee.data(), ee.size()); 
Integer d((byte*)dd.data(), dd.size()); 

cout << "N: " << endl << n << endl << endl; 
cout << "E: " << endl << e << endl << endl; 
cout << "D: " << endl << d << endl << endl; 

$ ./cryptopp-test.exe 

N: 
20446702916744654562596343388758805860065209639960173505037453331270270518732245 
08977372301204320323609709562340204469011575537734525469644875960570778896584888 
95017468362112062706438336639499925362469853626937363871851454247879222415857219 
92924045675229348655595626434390043002821512765630397723028023792577935108185822 
75369257422156693093780503115582009714681996492027000881132703628678639279359312 
17624250488602118597634417704467037220158572506211078553986931332640811506974231 
88751482418465308470313958250757758547155699749157985955379381294962058862159085 
915015369381046959790476428631998204940879604226680285601. 

E: 
65537. 

D: 
23583109899396195101799862623499368829246520235662137651186064319555667005065389 
11356936879137503597382515919515633242482643314423192704128296593672966061810149 
31632061789402182278402640746140338406535182197235078430096761014345948432406842 
76746396884059179774424728049430754391920261073195321175575450790865379829879825 
22396626690057355718157403493216553255260857777965627529169195827622139772389760 
13057175483467867984218114225248961766503010944557397801270779301059273764049922 
00150833924259148778478404572782464027609558833769999511998277062853834711506435 
61410605789710883438795588594095047409018233862167884701. 

OpenSSL的需要ned,爲私鑰操作pqd mod p-1,d mod q-1inv q mod p是可選的。只有n,e,d,您需要解決缺少的參數(至少pq)。兩個艱難的是pq。這裏的加密++代碼來解決他們(隨意轉換成OpenSSL的):

Integer p, q; 

RSA_solve(n, e, d, p, q); 

cout << "P: " << endl << p << endl << endl; 
cout << "Q: " << endl << q << endl << endl; 

和:

void RSA_solve(const Integer& n, const Integer& e, const Integer& d, Integer& p, Integer& q) 
{ 
    AutoSeededRandomPool prng; 
    Integer g = 1; 
    unsigned int SAFETY = 0; 

STEP_1: 
    const Integer k = e * d - 1; 
    if(!k.IsEven()) 
     throw runtime_error("e * d - 1 is not even"); 

STEP_2: 
    // g = 3, 5, 7, ... 
    g += 2; while(!VerifyPrime(prng, g)) g += 2; 
    Integer t = k; 

STEP_3: 
    if(SAFETY++ > 128) 
     throw runtime_error("could not factor n"); 

    if(!t.IsEven()) 
     goto STEP_2; 

    t /= 2; 
    Integer x = a_exp_b_mod_c(g, t, n); 

STEP_4: 
    if(!(x > 1)) 
     goto STEP_3; 

    Integer y = GCD(x-1, n); 
    if(!(y > 1)) 
     goto STEP_3; 

    p = std::max(y, n/y); 
    q = std::min(y, n/y); 

    Integer check = p * q; 
    if(n != check) 
     throw runtime_error("n != p * q"); 
} 

導致:

P: 
15737705590244743839558616502896029191493197327877753279847020015603526753735923 
90718294084119093232085749598005372477289597182368848096852332845373492076546615 
30801859889389455120932077199406250387226339056140578989122526711937239401762061 
949364440402067108084155200696015505170135950332209194782224750221639. 

Q: 
12992175256740635899099334754006444501823007340248226099417932857332386190837921 
12746269565434716649972371852989646481333243433270528522640603220881224011247812 
49085873464824282666514908127141915943024862618996371026577302203267804867959037 
802770797169483022132210859867700312376409633383772189122488119155159. 

d mod p-1d mod q-1inv q mod p是作爲練習留給讀者(但它們很容易,特別是在Crypto ++中)。修改後的RSA_solve可能看起來像:

void RSA_solve(const Integer& n, const Integer& e, const Integer& d, 
       Integer& p, Integer& q, 
       Integer& dmodp1, Integer& dmodq1, Integer& invqmodp) 

現在,切換到OpenSSL的與您的基數10(十進制)的字符串:

const char nz[] = 
    "20446702916744654562596343388758805860065209639960173505037453331270270518732245" 
    "08977372301204320323609709562340204469011575537734525469644875960570778896584888" 
    "95017468362112062706438336639499925362469853626937363871851454247879222415857219" 
    "92924045675229348655595626434390043002821512765630397723028023792577935108185822" 
    "75369257422156693093780503115582009714681996492027000881132703628678639279359312" 
    "17624250488602118597634417704467037220158572506211078553986931332640811506974231" 
    "88751482418465308470313958250757758547155699749157985955379381294962058862159085" 
    "915015369381046959790476428631998204940879604226680285601"; 

const char ez[] = "65537"; 

const char dz[] = 
    "23583109899396195101799862623499368829246520235662137651186064319555667005065389" 
    "11356936879137503597382515919515633242482643314423192704128296593672966061810149" 
    "31632061789402182278402640746140338406535182197235078430096761014345948432406842" 
    "76746396884059179774424728049430754391920261073195321175575450790865379829879825" 
    "22396626690057355718157403493216553255260857777965627529169195827622139772389760" 
    "13057175483467867984218114225248961766503010944557397801270779301059273764049922" 
    "00150833924259148778478404572782464027609558833769999511998277062853834711506435" 
    "61410605789710883438795588594095047409018233862167884701"; 

const char pz[] = 
    "15737705590244743839558616502896029191493197327877753279847020015603526753735923" 
    "90718294084119093232085749598005372477289597182368848096852332845373492076546615" 
    "30801859889389455120932077199406250387226339056140578989122526711937239401762061" 
    "949364440402067108084155200696015505170135950332209194782224750221639"; 

const char qz[] = 
    "12992175256740635899099334754006444501823007340248226099417932857332386190837921" 
    "12746269565434716649972371852989646481333243433270528522640603220881224011247812" 
    "49085873464824282666514908127141915943024862618996371026577302203267804867959037" 
    "802770797169483022132210859867700312376409633383772189122488119155159"; 

using BN_ptr = std::unique_ptr<BIGNUM, decltype(&::BN_free)>; 
using RSA_ptr = std::unique_ptr<RSA, decltype(&::RSA_free)>; 
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>; 
using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX, decltype(&::EVP_MD_CTX_destroy)>; 

#define UNUSED(x) ((void)x) 

int main(int argc, char* argv[]) 
{ 
    UNUSED(argc); UNUSED(argv); 

    int rc; 
    long err; 

    RSA_ptr rsa(RSA_new(), ::RSA_free); 
    BIGNUM *n = NULL, *e = NULL, *d = NULL, *p = NULL, *q = NULL; 

    rc = BN_dec2bn(&n, nz); 
    if(rc == 0 || n == NULL) { 
     cerr << "BN_dec2bn failed for n" << endl; 
     exit(1); 
    } 
    rsa->n = n; 

    rc = BN_dec2bn(&e, ez); 
    if(rc == 0 || e == NULL) { 
     cerr << "BN_dec2bn failed for e" << endl; 
     exit(1); 
    } 
    rsa->e = e; 

    rc = BN_dec2bn(&d, dz); 
    if(rc == 0 || d == NULL) { 
     cerr << "BN_dec2bn failed for d" << endl; 
     exit(1); 
    } 
    rsa->d = d; 

    rc = BN_dec2bn(&p, pz); 
    if(rc == 0 || p == NULL) { 
     cerr << "BN_dec2bn failed for p" << endl; 
     exit(1); 
    } 
    rsa->p = p; 

    rc = BN_dec2bn(&q, qz); 
    if(rc == 0 || q == NULL) { 
     cerr << "BN_dec2bn failed for q" << endl; 
     exit(1); 
    } 
    rsa->q = q; 

    [Exercise left to the reader] 

    rc = RSA_check_key(rsa.get()); 
    err = ERR_get_error(); 
    if(rc != 1) { 
     cerr << "RSA_check_key failed, error 0x" << std::hex << err << endl; 
     exit(1); 
    } 

    [Continues at next question below] 
    ... 
} 

這裏有您需要struct rsa提供(從<openssl dir>/crypto/rsa/rsa.h)的字段:

struct rsa_st 
    { 
    ... 
    /* 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; 
    ... 
    }; 

,這樣我可以喂到EVP_SignFinal調用這個...

EVP_SignFinal需要一個EVP_PKEY和你有一個RSA。所以:

EVP_PKEY_ptr pkey(EVP_PKEY_new(), ::EVP_PKEY_free); 

rc = EVP_PKEY_set1_RSA(pkey.get(), rsa.get()); 
err = ERR_get_error(); 
if(rc != 1) { 
    cerr << "EVP_PKEY_set1_RSA failed, error 0x" << std::hex << err << endl; 
    exit(1); 
} 

set1意味着引用計數的RSA*碰撞。沒關係。如果是set0,您將不得不放棄您的副本(即,使用rsa.release()而不是rsa.get())以避免雙倍空閒。

EVP_MD_CTX_ptr ctx(EVP_MD_CTX_create(), ::EVP_MD_CTX_destroy); 
EVP_MD_CTX_init(ctx.get()); 

const EVP_MD* md = EVP_sha256(); 
rc = EVP_SignInit(ctx.get(), md); 
err = ERR_get_error(); 
if(rc != 1) { 
    cerr << "EVP_SignInit_ex failed, error 0x" << std::hex << err << endl; 
    exit(1); 
} 

const char message[] = "Now is the time for all good men..."; 

rc = EVP_SignUpdate(ctx.get(), message, (unsigned int)sizeof(message)); 
err = ERR_get_error(); 
if(rc != 1) { 
    cerr << "EVP_SignUpdate failed, error 0x" << std::hex << err << endl; 
    exit(1); 
} 

const unsigned int req = std::max(EVP_MD_size(md), EVP_PKEY_size(pkey.get())); 
unique_ptr<unsigned char[]> signature(new unsigned char[req]); 
unsigned int size = req; 

rc = EVP_SignFinal(ctx.get(), signature.get(), &size, pkey.get()); 
err = ERR_get_error(); 
if(rc != 1) { 
    cerr << "EVP_SignFinal failed, error 0x" << std::hex << err << endl; 
    exit(1); 
} 

size = std::min(size, (unsigned int)EVP_MD_size(md)); 

cout << "Signature: "; 
for(unsigned i = 0; i < size; i++) 
    cout << std::hex << (signature[i] & 0xFF); 
cout << endl; 

這裏的加密++代碼的上面使用的引擎收錄:http://pastebin.com/9Rm7bxZp

下面是上面使用的OpenSSL代碼的Pastebin:http://pastebin.com/aGVpj4FW

這裏的OpenSSL的程序的輸出:

$ ./openssl-test.exe 
Signature: 78f2c9af23b9a2a42e3b57dec454fa43ea6627992f48d40a33da6a7c93f98b4 
+1

非常感謝您的提問,它包含了我需要的關鍵信息 - 圍繞使用BN_BN2bin。然而,「OpenSSL需要n,e,d,p,q,d mod p-1,d mod q-1和inv q mod p」並不是真的 - 它只需要n,e和d(嗯,我可以正確簽署並驗證他們)。 (幸運的是,我沒有花費數小時的時間試圖弄清楚你留下的東西,「作爲讀者的一個簡單的練習」) –

+0

@ Grahame - 你需要'n','e','d','p'和'q' 。如果你沒有'p'和'q',那麼'RSA_check_key'將會失敗,錯誤爲0x407b093。在使用之前,您必須驗證密鑰。 – jww

+0

RSA_Check_Key:「該函數驗證RSA密鑰,它檢查p和q實際上是素數,並且n = p * q,它還檢查d * e = 1 mod(p-1 * q-1),以及dmp1,dmq1和iqmp設置正確或爲NULL。「 - 爲了檢查我填寫的內容是否正確,我做了一大堆工作來填寫一些內容 - 並且p和q是質數?假設來自CA的簽名證書確實是最好的,是否真的不安全? –

3

我能做到這一點使用JWK到PEM(https://github.com/Brightspace/node-jwk-to-pem#readme)。我只好讓JWK到PEM在我的服務器上的NodeJS環境中正常工作的問題,所以我只是做了它的在線瀏覽:

https://tonicdev.com/npm/jwk-to-pem

將這個位於頂部的代碼框中(此使用的值您所提供):

var jwkToPem = require('jwk-to-pem'); 
var options = { private: false }; 
var jwk = { 
    "kty":"RSA", 
    "n":"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ", 
    "e":"AQAB", 
    "d":"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97IjlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYTCBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLhBOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ" 
}, pem = jwkToPem(jwk, options); 
console.log(pem); 

然後點擊「 - >運行」按鈕,你會得到下面的結果:

-----BEGIN RSA PUBLIC KEY----- 
MIIBCgKCAQEAofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd/wWJcyQoTbji9k0l8W2 
6mPddxHmfHQp+Vaw+4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL+yRT+SFd2lZS+pCgNMs 
D1W/YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb/7OMg0LOL+bSf63kpaSHSXndS5 
z5rexMdbBYUsLA9e+KXBdQOS+UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxv 
b3qCo5ZwKh9kG4LT6/I5IhlJH7aGhyxXFvUK+DWNmoudF8NAco9/h9iaGNj8q2et 
hFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQIDAQAB 
-----END RSA PUBLIC KEY----- 

我能夠用這個便利着想rt我的私人用戶密鑰,用於letsencrypt(letsencrypt.org)從JWK到PEM。

要轉換私鑰,請將private選項的值更改爲true,然後將jwk變量的值更改爲JWK格式中的特定鍵。

很明顯,您可以在console.log()javascript函數之外的其他方法中輸出pem變量的內容。

+1

'openssl rsa -inform PEM -pubin'命令不能解析這個pem鍵。這是什麼格式? – Velkan