![](/img/trans.png)
[英]Can an RSA OpenSSL key generated with C/C++ be decrypted with PHP?
[英]Can not import RSA keys generated with php openssl to windows 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 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;
我找到了解决方案。
与 CryptoAPI 密钥相比,PHP 生成具有 ASN.1 格式的额外字段的密钥。
1.我在这个工具的帮助下将base64密钥解码成一个二进制文件: base64decoder
2. 然后我只是从这个文件的前 26 个字节剪切到下一个标题序列,从“30 xx xx”开始并保存它。
3. 将此文件编码回 base64 使用: 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 CryptoAPI 导入它们,但我使用的是 C++。
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_KEY 而不是 RSA_CSP_PUBLICKEYBLOB 之外,导入私钥的代码几乎相同。
正确的代码(例如,不使用“神奇数字”)将是:
{
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;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.