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

无法将使用php openssl生成的RSA密钥导入windows CryptoAPI,php,delphi,encryption,openssl,cryptoapi,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 = op

将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中导入密钥的代码:

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;
我找到了解决办法。 与CryptoAPI密钥相比,PHP生成的密钥具有ASN.1格式的额外字段。 1.我使用此工具将base64密钥解码为二进制文件: 2.然后我只是从这个文件的前26个字节剪切到下一个头序列,从类似于:30xx xx的地方开始,然后保存它。 3.并使用以下代码将此文件编码回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;
我找到了解决办法。 与CryptoAPI密钥相比,PHP生成的密钥具有ASN.1格式的额外字段。 1.我使用此工具将base64密钥解码为二进制文件: 2.然后我只是从这个文件的前26个字节剪切到下一个头序列,从类似于:30xx xx的地方开始,然后保存它。 3.并使用以下代码将此文件编码回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;

我遇到了类似的情况,我用PHP5的OpenSSL API生成密钥,并使用Windows CytoAPI导入它们,除了我使用C++ + < /P> 26字节的偏移量为我修复了导入私钥的问题,但我发现公钥的偏移量为24字节。正如Mike K.指出的,需要偏移直到下一个头序列,我发现序列的前两个字节将是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;
}

< P>导入私钥的代码大致相同,除了指定PKCS-RSAYPrimaType密钥而不是RSAYCSPYPuxKyBub。

< P>我遇到了类似的情况,我用PHP5的OpenSSL API生成密钥,并使用Windows CytoAPI导入它们,除了使用C++ + < /P> 26字节的偏移量为我修复了导入私钥的问题,但我发现公钥的偏移量为24字节。正如Mike K.指出的,需要偏移直到下一个头序列,我发现序列的前两个字节将是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_密钥而不是RSA_CSP_PUBLICKEYBLOB。

您是否尝试查看两个不同密钥之间的不同之处,这可能解释了Windows API函数的投诉原因。您应该解码Base64/ASN.1编码密钥。因此,这里甚至给出了如何解码ASN.1文档的提示:感谢您的评论,我检查了它们,发现php生成的密钥与CryptoAPI密钥相比增加了一些额外的字段。你知道如何让php在不添加额外信息的情况下创建键吗?也许有一些标志传递给openssl_pkey_new?此函数的文档记录得不够好。您是否尝试查看两个不同键之间的差异,这可能解释了Windows API函数的投诉原因。您应该解码Base64/ASN.1编码密钥。因此,这里甚至给出了如何解码ASN.1文档的提示:感谢您的评论,我检查了它们,发现php生成的密钥与CryptoAPI密钥相比增加了一些额外的字段。你知道如何让php在不添加额外信息的情况下创建键吗?也许有一些标志传递给openssl_pkey_new?这个函数没有很好的文档记录。如果我能输入+500,我会帮你节省时间!!如果我能投+500,我会帮你救我一天!!