无法将使用php openssl生成的RSA密钥导入到Windows CryptoAPI中

时间:2014-01-03 20:47:28

标签: php delphi encryption openssl cryptoapi

将使用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坏标记值符合“错误。密钥非常相似,只是php生成的密钥比我在Internet示例中发现的密钥略长,尽管它们都是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;

3 个答案:

答案 0 :(得分:1)

我找到了解决方案 与CryptoAPI密钥相比,PHP生成带有ASN.1格式的额外字段的密钥。

1.借助此工具,我将base64密钥解码为二进制文件:base64 decoder

2.然后我从这个文件中先切出26个字节到下一个标题序列,从“30 xx xx”开始并保存它。

3.使用:base64 encoder

将此文件编码回base64

现在我可以将公钥和私钥导入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 :(得分:1)

我遇到类似的情况,我使用PHP5的openssl API生成密钥,并使用Windows CryptoAPI导入密钥,除了我使用C ++。

26字节偏移固定导入私钥,但我发现公钥的偏移量为24字节。正如Mike K.指出的那样,你需要偏移到下一个标题序列,我发现序列的前两个字节将是&#34; 30 82&#34;。因此,如果遇到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之外,导入私钥的代码几乎相同。

答案 2 :(得分:0)

正确的代码(例如不使用“神奇数字”)应该是:

{
Remove optional ASN.1 header from source.

Header:
Offset| Len  |LenByte|
======+======+=======+======================================================================
     0|   630|      3| SEQUENCE :
     4|     1|      1|    INTEGER : 0
     7|    13|      1|    SEQUENCE :
     9|     9|      1|       OBJECT IDENTIFIER : rsaEncryption [1.2.840.113549.1.1.1]
    20|     0|      1|       NULL :
    22|   608|      3|    OCTET STRING / BIT STRING:
                             ... actual key data ...
}
function ASN1SkipOptionalHeader(const ADER: DATA_BLOB): DATA_BLOB;
const
  ASN1_INTEGER     = $02;
  ASN1_BITSTRING   = $03;
  ASN1_OCTETSTRING = $04;
  ASN1_SEQUENCE    = $30;
var
  P: PByte;
  Len: Cardinal;
begin
  Result := ADER;
  P := Result.pbData;

  // Check for header:
  // SEQUENCE:
  //   INTEGER
  //   SEQUENCE
  //   OCTET STRING/BIT STRING
  // or:
  // SEQUENCE:
  //   SEQUENCE
  //   OCTET STRING/BIT STRING

  // both header and any key start with ASN1_SEQUENCE, skip it
  if P^ <> ASN1_SEQUENCE then
    Exit;
  Inc(P);
  ASN1ReadLength(P);

  // both header and key have ASN1_INTEGER as first field, skip it
  if P^ = ASN1_INTEGER then
  begin
    Inc(P);
    Len := ASN1ReadLength(P);
    Inc(P, Len);
  end;

  // now, check if it is a header or a key
  // a header has ASN1_SEQUENCE as second field,
  // a key has ASN1_INTEGER as second field
  if P^ <> ASN1_SEQUENCE then
    Exit;
  Inc(P);
  Len := ASN1ReadLength(P);
  Inc(P, Len);

  // now, seek to real key data
  if P^ = ASN1_OCTETSTRING then
  begin
    Inc(P);
    Result.cbData := ASN1ReadLength(P);
    Result.pbData := P;
  end
  else
  if P^ = ASN1_BITSTRING then
  begin
    Inc(P);
    Result.cbData := ASN1ReadLength(P) - 1;
    if P^ = 0 then // unused bits must be 0
    begin
      Inc(P);
      Result.pbData := P;
    end
    else
      Result.cbData := ADER.cbData; // fail, not support non-zero unused bits
  end;

{
  Now Result should be:

Offset| Len  |LenByte|
======+======+=======+======================================================================
     0|   604|      3| SEQUENCE :
     4|     1|      1|    INTEGER : 0
     7|   129|      2|    INTEGER :
      |      |       |       00... cut ...65
   139|     3|      1|    INTEGER : 65537
   144|   128|      2|    INTEGER :
      |      |       |       29... cut ...51
   275|    65|      1|    INTEGER :
      |      |       |       00... cut ...8F
   342|    65|      1|    INTEGER :
      |      |       |       00... cut ...CB
   409|    64|      1|    INTEGER :
      |      |       |       61... cut ...05
   475|    64|      1|    INTEGER :
      |      |       |       3D... cut ...11
   541|    65|      1|    INTEGER :
      |      |       |       00... cut ...37
}
end;

function ASN1ReadLength(var P: PByte): Cardinal;
var
  B: Byte;
begin
  Result := 0;
  B := P^;
  Inc(P);
  if B and $80 <> 0 then
  begin
    B := B and $7F;
    if B > 4 then
    begin
      Result := 0;
      Exit;
    end;
    if B > 0 then
    begin
      while B > 0 do
      begin
        Result := (Result shl 8) or P^;
        Inc(P);
        Dec(B);
      end;
    end
    else
      Result := 0;
  end
  else
    Result := B;
end;