2014-01-03 3 views
1

имеет проблемы импортирующих ключи, созданные с помощью PHP OpenSSL для CryptoAPIНевозможно импортировать ключи RSA, созданные с помощью PHP OpenSSL для окон CryptoAPI

Я успешно создать пару ключей в PHP, крипта/не расшифровывает строку с ним - нет проблем

<?php 
$privateKey = openssl_pkey_new(array(
    'private_key_bits' => 1024, 
    'private_key_type' => OPENSSL_KEYTYPE_RSA)); 

openssl_pkey_export($privateKey, $s); 

$info = openssl_pkey_get_details($privateKey); 
$public = openssl_pkey_get_public($info['key']); 
$private = openssl_pkey_get_private($s); 

$s = ''; 
$s1 = ''; 
openssl_public_encrypt('bla bla bla', $s, $public); 
openssl_private_decrypt($s, $s1, $private); 
echo('$s.'<br>'.$s1); 
?> 

Затем я написал программу в Delphi, которая должна импортировать частные и открытые ключи в CryptoAPI. Я нашел некоторый пример частного ключа RSA в Интернете, и он импортирует с моим кодом отлично, но когда я пытаюсь импортировать ключ, сгенерированный с помощью моего php-кода, он не работает на CryptDecodeObjectEx с «asn1 bad tag value met» Ошибка. Клавиши очень похожи, за исключением того PHP генерирует немного длиннее ключ, чем я нашел в примере Интернет, хотя они оба 1024 бит ...

php generated key (does not work): 
priv_key: string = 
    'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAN/NfimL4/8Pmp7+' + 
    'j299I7yaT6SpF1jwrFlwlLLjDibehqjBOcao+CaLK8Se+hysqZGGwr2walUprGxG' + 
    'Z5hnfCQCOchbTs5CiXnBCIX1aPKaRMx/SX3b4moT+wnkLrGOnHnUM+2c+jqZUjdh' + 
    '06hwlv1LCVcCtTW9NWU3Qi3G+r9bAgMBAAECgYBjjuSK0uJP+r8L764bKI4XPoYj' + 
    'd90dAaOJ/h0IHx2SiPdaZuqux0fszYhg5V/aFa0xQcOr4qjKzckYOZGoKJD+FtCq' + 
    'bNBEg1eZsKWYVJvTO8N2H0Lx4VSCiG7PjiqLGFfsmXZDXLPXhzsuCOUACmfcVoqh' + 
    'NlXOEAKtaTZI+uAakQJBAPB8sIQN7xTgCQcP2F8IbWR3VRAlnr4LWZQ5k96uxWjC' + 
    'wC6R8c7NnvUj+Fzs3XMXR8e3aTRme9OyHAWy7ReO+scCQQDuPUjBXXxuYGQq4ho5' + 
    'Pq4QEtHNKECDNDtKBaLvr9r7aXYOfMM/XiXqFqHAZqcrTRtMXD1sUhg4o+vIYkrg' + 
    '5qLNAkEA6+Z0RGVitAh78ohxh+89V4LTV05/5A5AJe1BBvxLu1LmsAgLuf/rwK4z' + 
    'L/xN0lrw15EryvII34VkhZaZijV/+wJAfX52xrTSCOppmVVE7wafdgQT0/fyE6r9' + 
    '2D4j2BJQTcL91x/NUaHsYuTNC6aHRH33dT/ZcyfDboKafxGX0+RpuQJBAMdPGszm' + 
    'JYhD9F8kz+Q9R04iuwupLxUU6Q60yVVZxRDBQ7OLxBQwrHa2WQ0TA8WC73TMNaph' + 
    'VN4ayHJHK8shjt0='; 

example key (works fine and it is shorter than php key): 
priv_key: string = 
    'MIICXAIBAAKBgQCf6YAJOSBYPve1jpYDzq+w++8YVoATI/YCi/RKZaQk+l2ZfoUQ' + 
    'g0qrYrfkzeoOa/qd5VLjTTvHEgwXnlDXMfo+vSgxosUxDOZXMTBqJGOViv5K2QBv' + 
    'k8A1wi4k8tuo/7OWya29HvcfavUk3YXaV2YFe8V6ssaZjNcVWmDdjqNkXwIDAQAB' + 
    'AoGALrd+ijNAOcebglT3ioE1XpUbUpbir7TPyAqvAZUUESF7er41jY9tnwgmBRgL' + 
    'Cs+M1dgLERCdKBkjozrDDzswifFQmq6PrmYrBkFFqCoLJwepSYdWnK1gbZ/d43rR' + 
    '2sXzSGZngscx0CxO7KZ7xUkwENGd3+lKXV7J6/vgzJ4XnkECQQDTP6zWKT7YDckk' + 
    'We04hbhHyBuNOW068NgUUvoZdBewerR74MJx6nz28Tp+DeNvc0EveiQxsEnbV8u+' + 
    'NRkX5y0xAkEAwcnEAGBn5kJd6SpU0ALA9XEpUv7tHTAGQYgCRbfTT59hhOq6I22A' + 
    'ivjOCNG9c6E7EB2kcPVGuCpYUhy7XBIGjwJAK5lavKCqncDKoLwGn8HJdNcyCIWv' + 
    'q5iFoDw37gTt1ricg2yx9PzmabkDz3xiUmBBNeFJkw/FToXiQRGIakyGIQJAJIem' + 
    'PPPvYgZssYFbT4LVYO8d/Rk1FWVyKHQ9CWtnmADRXz7oK7l+m7PfEuaGsf9YpOcR' + 
    'koGJ/TluQLxNzUNQnQJBAImwr/yYFenIx3HQ6UX/fCt6qpGDv0VfOLyR64MNeegx' + 
    'o7DhNxHbFkIGzk4lKhMKcHKDrawZbdJtS9ie2geSwVQ='; 

кода в Delphi, который импортирует ключ:

var 
    dwBufferLen, cbKeyBlob, i: longword; 
    pbBuffer, pbKeyBlob: pointer; 
    hProv: HCRYPTPROV; 
    hKey: HCRYPTKEY; 
begin 
    hProv := 0; 
    hKey := 0; 

    // convert key string to a binary 
    if not(CryptStringToBinary(PWideChar(priv_key), 0, 1, nil, @dwBufferLen, nil, nil)) then 
    exit; 

    GetMem(pbBuffer, dwBufferLen); 
    if not(CryptStringToBinary(PWideChar(priv_key), 0, 1, pbBuffer, @dwBufferLen, nil, nil)) then 
    exit; 

    // convert binary to a key blob 
    if not(CryptDecodeObjectEx(X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, 
    PKCS_RSA_PRIVATE_KEY, pbBuffer, dwBufferLen, 0, nil, nil, @cbKeyBlob)) then 
    begin 
     // first key generates error here 
     ShowMessage(SysErrorMessage(GetLastError)); 
     exit; 
    end; 

    GetMem(pbKeyBlob, cbKeyBlob); 
    if not(CryptDecodeObjectEx(X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, 
    PKCS_RSA_PRIVATE_KEY, pbBuffer, dwBufferLen, 0, nil, pbKeyBlob, @cbKeyBlob)) then 
    exit; 

    if not(CryptAcquireContext(@hProv, nil, MS_ENHANCED_PROV, PROV_RSA_FULL, 
    CRYPT_VERIFYCONTEXT)) then 
    exit; 

    if not(CryptImportKey(hProv, pbKeyBlob, cbKeyBlob, 0, 0, @hKey)) then 
    exit; 

    //... 

    if hKey <> 0 then 
    CryptDestroyKey(hKey); 
    if hProv <> 0 then 
    CryptReleaseContext(hProv, 0); 
end; 
+0

Вы пытались понять, что отличается между двумя разными ключами (что может объяснить, почему функция Windows API жалуется). Вы должны декодировать кодированный ключ Base64/ASN.1. SO даже дает подсказку о том, как декодировать документ ASN.1 здесь: http://stackoverflow.com/questions/5153059/any-asn1-implementation-in-delphi –

+0

Спасибо за ваш комментарий, я проверил их и посмотрел, что php Сгенерированный ключ добавляет дополнительные поля по сравнению с ключом CryptoAPI. Есть ли у вас какие-либо идеи о том, как создавать PHP-ключи без добавления дополнительной информации? Может быть, некоторые флаги переданы openssl_pkey_new? Эта функция недостаточно хорошо документирована. –

ответ

1

Я нашел решение.
PHP генерирует ключи с дополнительными полями в формате ASN.1 по сравнению с ключами CryptoAPI.

1. Я декодируюсь ключом base64 в двоичный файл с помощью этого инструмента: base64 decoder

2. Тогда я просто вырезать из этого файла первых 26 байт следующей последовательности заголовков, начиная от чего-то вроде: «30 хх хх» и сохранил его.

3. И закодирован этот файл обратно в base64 с: base64 encoder

Теперь я могу импортировать государственные и частные ключи в CryptoAPI без проблем с следующим кодом:

// key types 
const 
    PKCS_RSA_PRIVATE_KEY = LPCSTR(43); 
    PKCS_RSA_PUBLIC_KEY = LPCSTR(19); 

function ImportKey(hProv: HCRYPTPROV; KeyType: pointer; key: string): hKey; 
var 
    BuffSize, BlobSize: longword; 
    buff, blob: pointer; 
begin 
    result := 0; 
    buff := nil; 
    blob := nil; 

    try 
    if not(CryptStringToBinary(PWideChar(key), 0, 1, nil, @BuffSize, nil, nil)) then 
     exit; 

    GetMem(buff, BuffSize); 
    if not(CryptStringToBinary(PWideChar(key), 0, 1, buff, @BuffSize, nil, nil)) then 
     exit; 

    if not(CryptDecodeObjectEx(X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, 
     KeyType, buff, BuffSize, 0, nil, nil, @BlobSize)) then 
     exit; 

    GetMem(blob, BlobSize); 
    if not(CryptDecodeObjectEx(X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, 
     KeyType, buff, BuffSize, 0, nil, blob, @BlobSize)) then 
     exit; 

    if not(CryptImportKey(hProv, blob, BlobSize, 0, 0, @result)) then 
     exit; 

    finally 
    if buff <> nil then 
     FreeMem(buff); 
    if blob <> nil then 
     FreeMem(blob); 
    end; 
end; 
1

я столкнулся с аналогичной ситуацией, когда я был генерируя ключи, используя API-интерфейс openssl PHP5, и импортируя их с помощью Windows CryptoAPI, за исключением того, что я использую C++.

26-байтовый фиксированный импорт личных ключей для меня, но я нашел смещение для открытых ключей как 24 байта. Как указал Майк К., вам нужно компенсировать до следующей последовательности заголовков, я обнаружил, что первые два байта последовательности будут «30 82». Поэтому, если вы столкнулись с ситуацией, когда 24 байта или 26 байтов не работают, проверьте ключ в шестнадцатеричном редакторе и найдите эти байты.

Вот код, который я имел успех:

DWORD PEMToPublicKeyBlob(char* keyData, DWORD keyDataLen, BYTE** publicKeyBlob, DWORD* length) 
{ 
    DWORD ret = ERROR_SUCCESS; 
    wchar_t message[256]; 

    BYTE* der = NULL; 
    DWORD derLen = 0; 

    unsigned int offset = 0; 
    // if the header is this, and not -----BEGIN RSA PUBLIC KEY----- the key was created on the webserver 
    if(strstr(keyData, "-----BEGIN PUBLIC KEY-----")) 
     offset = 24;  // php puts 24 extra bytes in the header that screws up the import 

    // Convert from PEM format to DER format 
    // get length 
    if (!CryptStringToBinaryA(keyData, keyDataLen, CRYPT_STRING_ANY, NULL, &derLen, NULL, NULL)) 
    { 
     ret = GetLastError(); 
     swprintf_s(message, 256, L"PEMToPublicKeyBlob: CryptStringToBinaryA(len) failed %u", ret); 
     writeToLog(message); 
     goto out; 
    } 

    der = new BYTE[derLen]; 
    if(!der) 
    { 
     ret = ERROR_NOT_ENOUGH_MEMORY; 
     swprintf_s(message, 256, L"PEMToPublicKeyBlob: der blob allocation failed %u", ret); 
     writeToLog(message); 
     goto out; 
    } 
    if (!CryptStringToBinaryA(keyData, keyDataLen, CRYPT_STRING_ANY, der, &derLen, NULL, NULL)) 
    { 
     ret = GetLastError(); 
     swprintf_s(message, 256, L"PEMToPublicKeyBlob: CryptStringToBinaryA failed %u", ret); 
     writeToLog(message); 
     goto out; 
    } 

    // Decode from DER format to PUBLICKEYBLOB 
    // get length 
    *length = 0; 
    if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB, 
           der + offset, derLen - offset, 0, NULL, NULL, length)) 
    { 
     ret = GetLastError(); 
     swprintf_s(message, 256, L"PEMToPublicKeyBlob: CryptDecodeObjectEx(len) failed %u", ret); 
     writeToLog(message); 
     goto out; 
    } 

    *publicKeyBlob = new BYTE[*length]; 
    if(!*publicKeyBlob) 
    { 
     ret = ERROR_NOT_ENOUGH_MEMORY; 
     swprintf_s(message, 256, L"PEMToPublicKeyBlob: publickey blob allocation failed %u", ret); 
     writeToLog(message); 
     goto out; 
    } 

    if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB, 
           der + offset, derLen - offset, 0, NULL, *publicKeyBlob, length)) 
    { 
     ret = GetLastError(); 
     swprintf_s(message, 256, L"PEMToPublicKeyBlob: CryptDecodeObjectEx failed %u", ret); 
     writeToLog(message); 
     goto out; 
    } 
out: 
    if(der) delete[] der; 
    return ret; 
} 

Код для импорта закрытых ключей в значительной степени то же самое, кроме вас указать PKCS_RSA_PRIVATE_KEY вместо RSA_CSP_PUBLICKEYBLOB.