Как использовать Windows Crypto API для создания подписи rsa-sha1 для XMLDSIG - PullRequest
0 голосов
/ 15 марта 2012

Предположим, что документ XMLDSig SignedInfo выглядит следующим образом:

<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
  <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>
  <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod>
  <Reference URI="#object">
    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
    <DigestValue>OPnpF/ZNLDxJ/I+1F3iHhlmSwgo=</DigestValue>
  </Reference>
</SignedInfo>

SHA1 указанного выше узла SignedInfo:

5a c8 ef ab 04 5a 9a 46 fe 00 1a c5 8c 25 36 46 ff 88 dc 6a

Чтобы сгенерировать SignatureValue в Windows CryptoAPI, я запускаю это:

var hProv: HCRYPTPROV;
    hHash: HCRYPTHASH;
    hKey: HCRYPTKEY;
    s: string;
    B, bSign: TBytes;
    bPrefix, bPubKeyModulus, bExp: TBytes;
    iFFLen, iPos, iSize, iHashSize: Cardinal;
    PubKey: TRsaPubKey;
begin
  s := '<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">' + #$0A +
       '  <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>' + #$0A +
       '  <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod>' + #$0A +
       '  <Reference URI="#object">' + #$0A +
       '    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>' + #$0A +
       '    <DigestValue>OPnpF/ZNLDxJ/I+1F3iHhlmSwgo=</DigestValue>' + #$0A +
       '  </Reference>' + #$0A +
       '</SignedInfo>';

  B := TEncoding.ANSI.GetBytes(s);

  {get context for crypt default provider}
  Win32Check(CryptAcquireContext(hProv, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT));
  try
    Win32Check(CryptGenKey(hProv, AT_SIGNATURE, RSA1024BIT_KEY or CRYPT_EXPORTABLE or ALG_SID_RSA_PKCS, hKey));

    {create hash-object (SHA algorithm)}
    Win32Check(CryptCreateHash(hProv, CALG_SHA1, 0, 0, hHash));

    Win32Check(CryptHashData(hHash, @B[0], Length(B), 0));

    // Obtain hash size
    iSize := SizeOf(iSize);
    SetLength(B, iSize);
    Win32Check(CryptGetHashParam(hHash, HP_HASHSIZE, @B[0], iSize, 0));
    Move(B[0], iSize, iSize);

    // obtain hash value
    SetLength(B, iSize);
    Win32Check(CryptGetHashParam(hHash, HP_HASHVAL, @B[0], iSize, 0));

    Print('Hex: ' + ToHex(B));
    Print('Base64: ' + ToBase64(B));

    // Signature of Hash
    Win32Check(CryptSignHash(hHash, AT_SIGNATURE, nil, 0, nil, iSize));
    SetLength(bSign, iSize);
    Win32Check(CryptSignHash(hHash, AT_SIGNATURE, nil, 0, @bSign[0], iSize));

    Print;
    Print('Signature: ');
    Print(ToBase64(Reverse(bSign)));

    // Public key
    Win32Check(CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, nil, iSize));

    SetLength(B, iSize);
    Win32Check(CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, @B[0], iSize));

    Print;
    Print('Public Key: ');
    Print(ToHexASCII(B));

    Move(B[SizeOf(TPublicKeyStruc)], PubKey, SizeOf(PubKey));

    // Public key: Get Modulus
    SetLength(bPubKeyModulus, PubKey.bitlen div 8);
    Move(B[SizeOf(TPublicKeyStruc) + SizeOf(TRsaPubKey)], bPubKeyModulus[0], Length(bPubKeyModulus));
    Print;
    Print('Modulus: ');
    Print(ToHex(bPubKeyModulus));
    Print;
    Print(ToBase64(bPubKeyModulus));

    // Public key: Get Exponent
    Print;
    Print('Exponent: ');
    Print(IntToStr(PubKey.pubexp));
    SetLength(bExp, SizeOf(PubKey.pubexp));
    Move(PubKey.pubexp, bExp[0], Length(bExp));
    while bExp[Length(bExp) - 1] = 0 do
      SetLength(bExp, Length(bExp) - 1);
    Print('Base64: ' + ToBase64(bExp));

    Print;
    if CryptVerifySignature(hHash, @bSign[0], Length(bSign), hKey, nil, 0) then
      Print('signature verified')
    else
      RaiseLastOSError;

    Win32Check(CryptDestroyKey(hKey));

    {destroy hash-object}
    Win32Check(CryptDestroyHash(hHash));
  finally
    {release the context for crypt default provider}
    Win32Check(CryptReleaseContext(hProv, 0));
  end;
end;

Я могу сгенерировать подпись и проверить ее с помощью CryptoAPI, которую я не уверен, выполняю ли я согласно спецификации XMLDSig.

Чтобы дополнительно проверить, правильно ли я это делаю, попробуйте онлайн-верификатор: http://www.aleksey.com/xmlsec/xmldsig-verifier.html

Я заменяю модуль и SignatureValue следующего документа XMLDSig и проверяю его с помощью онлайн-верификатора:

<?xml version="1.0" encoding="UTF-8"?>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
  <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>
  <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod>
  <Reference URI="#object">
    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
    <DigestValue>OPnpF/ZNLDxJ/I+1F3iHhlmSwgo=</DigestValue>
  </Reference>
</SignedInfo>
<SignatureValue>nihUFQg4mDhLgecvhIcKb9Gz8VRTOlw+adiZOBBXgK4JodEe5aFfCqm8WcRIT8GLLXSk8PsUP4//SsKqUBQkpotcAqQAhtz2v9kCWdoUDnAOtFZkd/CnsZ1sge0ndha40wWDV+nOWyJxkYgicvB8POYtSmldLLepPGMz+J7/Uws=</SignatureValue>
<KeyInfo>
  <KeyValue>
    <RSAKeyValue><Modulus>4IlzOY3Y9fXoh3Y5f06wBbtTg94Pt6vcfcd1KQ0FLm0S36aGJtTSb6pYKfyX7PqCUQ8wgL6xUJ5GRPEsu9gyz8ZobwfZsGCsvu40CWoT9fcFBZPfXro1Vtlh/xl/yYHm+Gzqh0Bw76xtLHSfLfpVOrmZdwKmSFKMTvNXOFd0V18=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
  </KeyValue>
</KeyInfo>
<Object xmlns="http://www.w3.org/2000/09/xmldsig#" Id="object">some text
  with spaces and CR-LF.</Object>
</Signature>

И, очевидно, это не удалось.

Я думаю, что я не сгенерировал значение подписи правильно.

Ответы [ 2 ]

1 голос
/ 16 марта 2012

Я наконец заработал. Наконец-то это была проблема с порядком байтов. Microsoft CryptoAPI представляет модуль и значение подписи в битовой строке Little Endian. В то время как спецификация XMLDSIG представлена ​​в битовой строке Big Endian.

После получения значения модуля и сигнатуры из функций CryptoAPI просто преобразуйте его в битовую строку Big Endian, изменив порядок байтов:

function Reverse(A: TBytes): TBytes;
var B: Byte;
    i: integer;
begin
  SetLength(Result, Length(A));
  i := Length(A) - 1;
  for B in A do begin
    Result[i] := B;
    Dec(i);
  end;
end;

Print(ToBase64(reverse(bSign)));
Print(ToBase64(reverse(bPubKeyModulus)));
1 голос
/ 15 марта 2012

Если вам действительно нужно проверить документ XMLDSig в собственном коде, а не в .NET, я бы рекомендовал вам использовать MSXML 5.0 .См. пример кода для проверки подписи и еще один для создания подписи.

...