【问题标题】:How do I load an OpenSSL-generated RSA1024 plaintext public key with the CryptoAPI?如何使用 CryptoAPI 加载 OpenSSL 生成的 RSA1024 明文公钥?
【发布时间】:2015-09-07 07:58:49
【问题描述】:

我使用 OpenSSL 生成了一个公钥,使用以下命令创建一个 1024 位 RSA 密钥,然后导出公钥,将公钥保存在 .pem 文件中。

> openssl genrsa -out private_key.pem 1024
> openssl rsa -pubout -in private_key.pem -out public_key.pem

我正在使用私钥对 PHP 中的一些数据进行签名,并希望使用 CryptoAPI 在 Windows 中验证签名。如果可能,我不希望链接到 Windows OpenSSL 端口。

公钥是文本格式。如何成功加载并使用它来验证签名?

目前的做法

我正在尝试使用CryptImportKey 导入格式为的公钥 blob:

  • PUBLICKEYSTRUC
  • 以下字节数组的大小,DWORD
  • 作为字节数组的键(窄 / ANSI 字符串)

在压缩结构中。

密钥字节数组只是公钥 .pem 文件的内容,包括 BEGIN 和 END 语句,作为 ANSI(非 Unicode)字符串。请注意,我使用的是 Unicode 应用程序,但 Crypto API 似乎没有 ANSI/Unicode 版本。 (是吗?)

当我这样做时,CryptImportKey 失败,GetLastError 的值为 0x80090005NTE_BAD_DATA。我不确定为什么会这样......因此问题:)

代码

公钥blob结构定义为:

TPublicKeyBlob = packed record
  strict private
    FHeader : BLOBHEADER;
    FKeyLength : DWORD;
    FKey : array[0..4095] of Byte;
  public
    procedure Init(const KeyStr : string);
    function Size : DWORD;
  end;

其中的方法有:

procedure TPublicKeyBlob.Init(const KeyStr: string);
var
  KeyAnsi : AnsiString;
begin
  KeyAnsi := AnsiString(KeyStr);
  if Length(KeyAnsi) > Length(FKey) then
    raise Exception.Create('Key too long');

  FHeader.bType := PLAINTEXTKEYBLOB;
  FHeader.bVersion := CUR_BLOB_VERSION;
  FHeader.reserved := 0;
  FHeader.aiKeyAlg := CALG_RSA_KEYX;

  FKeyLength := Length(KeyAnsi) * sizeof(KeyAnsi[1]);
  assert(sizeof(KeyAnsi[1]) = 1); // Should be single byte strings!

  Move(KeyAnsi[1], FKey[0], Length(KeyAnsi) * sizeof(KeyAnsi[1]));
end;

function TPublicKeyBlob.Size: DWORD;
begin
  // Return only the used size - not all the byte array will be used
  Result := SizeOf(FHeader) + Sizeof(FKeyLength) + FKeyLength;
end;

这应该会创建一个与 Microsoft 的 Importing a Plaintext Key example 中的示例相匹配的内存布局。可能是。 CALG_RSA_KEYX 是否适用于 RSA 1024 位密钥(或者更确切地说,是从其中生成的公钥)?我对Move() 的用法正确吗? (应该类似于memcpy,如果您是 C 程序员,但参数是 source、dest、size,并且字符串是 1-indexed 但数组是 0-indexed。) Size() 是否应该返回整个结构,还是只是键数组?

参数是公钥(下)作为字符串。

然后按如下方式使用:

function TLicenseInfo.VerifySignature: Boolean;
const
  PublicKeyStr = '-----BEGIN PUBLIC KEY-----' +
    'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeyh3x8qbGgq+ZlXm4erhjFMKY' +
    'RGhAyFc8DUupkaLlSSzXDcHHC27VtuxAKtAVvm97OGJMbdbtAUy6rmVH0GSQmP+0' +
    'Mct+7ncQRfpXJ4kAYg3gpv4dSsBl83rWDuQ06QDPjIT7eMdNMuUTm11GIYFnvyv4' +
    'C9Vn92hBC2QeRRlslwIDAQAB' +
    '-----END PUBLIC KEY-----';
var
  hCryptProv, hHash: wcrypt2.HCRYPTPROV;
  hKey: Cardinal;
  PublicKeyBlob : TPublicKeyBlob;
begin
  Result := False;

  PublicKeyBlob.Init(PublicKeyStr);

  if not CryptAcquireContext(@hCryptProv, 'Test', nil, PROV_RSA_FULL, 0) then
    if not CryptAcquireContext(@hCryptProv, 'Test', nil, PROV_RSA_FULL, CRYPT_NEWKEYSET) then
      Exit;

  try
    if CryptImportKey(hCryptProv, @PublicKeyBlob, PublicKeyBlob.Size, 0, 0, @hKey) then
    // ^ this is the line that fails
    begin
      // ... check the signature.
    end;
  finally
    CryptReleaseContext(hCryptProv, 0);
  end;
end;

调用CryptImportKey 的标记行失败,GetLastError 返回0x80090005NTE_BAD_DATA

我尝试了很多变体 - 算法的一些变体,试验大小,删除密钥字符串的 BEGIN 和 END 部分等。但我不熟悉 Crypto API,我确定这对其他人来说是显而易见的:)

【问题讨论】:

  • 首先尝试对密钥进行 Base64 解码(我认为在这种情况下,明文意味着它没有加密,而不是它实际上是 ASCII 格式)
  • @JonathanPotter 啊......我认为那是那些“哦,当然......”的时刻之一。嘎。我会尝试并更新:)
  • 解码在同一个地方产生同样的错误码。请注意,我必须剥离密钥的 BEGIN/END 部分才能对其进行解码,但我不确定这是否正确 - 我认为纯文本密钥需要这些部分作为其格式的一部分。

标签: delphi winapi cryptography cryptoapi


【解决方案1】:

多年前(2012 年)我回答过一个类似的问题:https://stackoverflow.com/a/10827239/707093

基本上,您必须使用CryptStringToBinary 来解码BASE64 值,然后使用CryptDecodeObjectExCryptImportKey。 显示如何执行此操作的 C 示例位于 https://www.idrix.fr/Root/Samples/capi_pem.cpp。将其翻译成 delphi 并不太难。

我希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-05
    • 2018-10-07
    • 2022-07-26
    • 1970-01-01
    • 1970-01-01
    • 2015-05-14
    相关资源
    最近更新 更多