locked
How to get CryptVerifySignature to validate a signature created using .Net crypto classes RRS feed

  • Question

  • I'm trying to verify a signature using CryptoAPI that was signed using .Net Cryptography facilities.
    .Net code to verify the signature works fine. Here's the crux of what it does:

     X509Certificate2 cert = new X509Certificate2( MyPublicCertData );
     RSACryptoServiceProvider csp = (RSACryptoServiceProvider) cert.PublicKey.Key;
     SHA1Managed sha1 = new SHA1Managed();
     byte[] hash = sha1.ComputeHash( data );
     csp.VerifyHash( hash, CryptoConfig.MapNameToOID( "SHA1" ), signature );

    ... and here's what I think is equivalent CryptoAPI code:

     CryptAcquireContext( &hCryptProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT );
     CryptCreateHash( hCryptProv, CALG_SHA1, 0, 0, &hHash );
     CryptHashData( hHash, pOriginalData, dwLenData, 0 );
     PCCERT_CONTEXT pSignerCert = CertCreateCertificateContext( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pCertificate, dwLenCertificate );
     CryptImportPublicKeyInfo( hCryptProv, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, &pSignerCert->pCertInfo->SubjectPublicKeyInfo, &hPubKey );

     CryptVerifySignature( hHash, pbSignatureData, cbSignatureBinary, hPubKey,NULL, 0 /*also tried with CRYPT_NOHASHOID - no difference!*/);

    Fails with 0x80090006  NTE_BAD_SIGNATURE

    I've verified in the debugger that the bytes for the signature data, the hash, and the public certificate (which is embedded in the application resource) is identical in both content and length for both the .Net & CryptoAPI uses. Although there's no error checking shown I do verify that all the preceeding CryptoAPI calls succeed.

    Presumably I'm doing something wrong, but I can't see what.
    Does anyone have any suggestions?

    Wednesday, May 18, 2011 1:25 PM

Answers

  • The good news is that I was able to verify the signature.
    So I reversed the signature to be little endian:
    7c 90 f2 59 ff c0 38 ab 38 c6 d8 cf 56 ab 39 a3
    65 1d d7 5b 70 d7 22 da de 67 13 95 f5 5e 14 a8
    04 ef 7e 87 13 cd ba 76 75 b6 af 58 63 80 bb 5a
    e2 c4 86 17 41 4a 88 b5 f1 c4 0c 71 c1 b2 9f 31
    3c 95 04 b0 8a 2f cf 39 89 95 fa e9 71 c5 db cd
    5d d6 6b 42 89 27 a7 3c 97 c6 d7 d2 7e 85 11 cd
    53 ce d3 9a 25 9b 1e 46 8e ab e1 c3 20 ac e7 ae
    a9 7c b5 bb ae 69 78 20 82 9e 87 7f 41 d6 88 78
    with this code:
    void Cspprov::reverse( vector< unsigned char > & data )
    {
     int len = (int)data.size();
     unsigned char t;
     int b = len-1;
     for ( int i=0; i < (len/2); i++ ) {
      t= data[i];
      data[i]= data[b];
      data[b]= t;
      b--;
     }
    }
    Did your reversal do exactly the same thing ?
     
    I used the hash: 7d b3 f4 d6 09 88 20 3a 47 12 dc c9 da a5 20 37 f0 1b fb df
    You did not hash the hash by accident I assume ?
     
    Since all I have is the hash, I used this code:
    // Signature is in little endian
    //
    bool Cspprov::VerifySignatureOfHash( HCRYPTKEY hPubKey,
     const vector<unsigned char> & hashData,
     const vector<unsigned char> & sig, DWORD dwFlags, int hashAlg )
    {
     if ( 0 == sig.size() ) return false;

     HCRYPTHASH hHash;
     if ( ! CryptCreateHash( itsProvider, hashAlg, 0, 0, &hHash) ) return( false );
     if ( ! CryptSetHashParam( hHash, HP_HASHVAL, hashData.data(), 0 ) ) return( false );
     bool ok= (TRUE == ::CryptVerifySignature( hHash, sig.data(), sig.size(), hPubKey, 0, dwFlags ));
     CryptDestroyHash( hHash );
     return( ok );
    }
    and the signature verified. 
    Hopefully we are very close ! 
     
    Your signature, when turned into plaintext with the public key is:
    00 01 ff ff ff ff ff ff ff ff ff ff ff ff ff ff
    ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
    ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
    ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
    ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
    ff ff ff ff ff ff ff ff ff ff ff ff
    00 30 21 30 09 06 05 2b 0e 03 02 1a 05 00
    04 14   7d b3 f4 d6 09 88 20 3a 47 12 dc c9 da a5 20 37 f0 1b fb df
    which matches the hash you sent.  
     
    and certificate
    30 82 01 b8 30 82 01 62 a0 03 02 01 02 02 10 48
    25 9c 1e e8 c0 41 ac 4b 8d 5e bc 92 76 55 83 30
    0d 06 09 2a 86 48 86 f7 0d 01 01 04 05 00 30 16
    31 14 30 12 06 03 55 04 03 13 0b 52 6f 6f 74 20
    41 67 65 6e 63 79 30 1e 17 0d 31 31 30 35 31 32
    31 30 32 39 31 31 5a 17 0d 33 39 31 32 33 31 32
    33 35 39 35 39 5a 30 13 31 11 30 0f 06 03 55 04
    03 13 08 44 4c 6f 77 6e 64 65 73 30 81 9f 30 0d
    06 09 2a 86 48 86 f7 0d 01 01 01 05 00 03 81 8d
    00 30 81 89 02 81 81 00 8e 92 f2 8a aa d2 92 c9
    d8 08 6d 8d 65 1a 08 4c 73 04 bc dd 46 8b df c3
    b7 c6 fa 66 9e e8 d2 03 72 53 2b 5b 5e 09 15 fc
    e6 ff 14 5b 30 6a 93 cb 8f 63 a2 8c b5 6b 01 46
    29 c5 ff e7 3a d0 7c 38 7d 62 c2 83 95 8c cf 48
    d7 00 04 78 58 bd d1 3a b2 52 e2 0a f2 b2 86 6a
    9c 78 ea 0c 55 98 5b 36 28 b7 26 db a7 1f b0 2c
    8b 89 e8 6d 3e b5 ad 61 2b 1f 7b 6c 29 ca b1 4c
    7a 26 9a 20 40 5e e4 f1 02 03 01 00 01 a3 4b 30
    49 30 47 06 03 55 1d 01 04 40 30 3e 80 10 12 e4
    09 2d 06 1d 1d 4f 00 8d 61 21 dc 16 64 63 a1 18
    30 16 31 14 30 12 06 03 55 04 03 13 0b 52 6f 6f
    74 20 41 67 65 6e 63 79 82 10 06 37 6c 00 aa 00
    64 8a 11 cf b8 d4 aa 5c 35 f4 30 0d 06 09 2a 86
    48 86 f7 0d 01 01 04 05 00 03 41 00 47 8c b2 8d
    b6 0d 19 ed a9 61 2b 11 ac 5c 9e 39 67 a4 9e 16
    61 12 ff 60 1d 21 40 be 1e 4e 03 23 f5 bc 74 cd
    c1 d2 2b f8 de 71 4b a5 33 b9 93 e6 d5 66 ac 97
    b8 38 8e a0 59 60 11 1d a2 35 45 12
    Wednesday, July 6, 2011 10:46 PM
  •  void calc::CertVerifySignature( DWORD dwFlags )
    {
     vector<unsigned char> certData= Iolib::StringToBytes( textField[ CERT ]->getText() );
     PCCERT_CONTEXT certcon= ::CertCreateCertificateContext( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, certData.data(), certData.size() );
     cspkey pubkey( itsProv, certcon );

     SmartUtil::tstring hexstring;

     if ( pubkey.m_valid_key ) {

      // Fetch the data
      vector<unsigned char> hashdat= fetchIn();

      // Verify the signature in OUT_DAT to the hash(in_dat).
      vector<unsigned char> sig= fetchOut();

      if ( pubkey.verifySignatureOfHash( itsProv, hashdat, sig, dwFlags, CALG_SHA1 ) ) {
       status->setText( _T("VerifySignatureOfHash ok.") );
    __________

     


    // Constructor of a public key from a certificate.
    //
    cspkey( Cspprov & prov, PCCERT_CONTEXT certcon )
    {
     PCERT_PUBLIC_KEY_INFO pNewPubKeyinfo;
     pNewPubKeyinfo = &(certcon->pCertInfo->SubjectPublicKeyInfo);

     m_valid_key= CryptImportPublicKeyInfo( prov.getHCryptProv(), X509_ASN_ENCODING,
                pNewPubKeyinfo, &m_hKey )
       ? true : false;
    }

     

    ____________

     

     

     

    Thursday, July 7, 2011 9:44 AM

All replies

  • Watch out for the little endian assumption in CryptoAPI.

     

    As the documentation for CryptVerifySignature  says:

     

    "If you generate a signature by using the .NET Framework APIs and try to verify it by using the CryptVerifySignature function, the function will fail and GetLastError will return NTE_BAD_SIGNATURE. This is due to the different byte orders between the native Win32 API and the .NET Framework API.

    The native cryptography API uses little-endian byte order while the .NET Framework API uses big-endian byte order. If you are verifying a signature generated by using a .NET Framework API, you must swap the order of signature bytes before calling the CryptVerifySignature function to verify the signature."

     

    In other words, pbSignatureData points to little endian signatures !

     

     

     

    Wednesday, July 6, 2011 6:46 PM
  • Hi Andrew,

    I had previously seen such recomendations, but no details to explain precisely how to do it. I tried a direct front to back reversal as the person in this thread did:

    http://www.winserverkb.com/Uwe/Forum.aspx/windows-cryptography/76/CryptVerifySignature-for-PDF

    ... but I couldn't get it to work either.

    Do you have any details on exactly how to change the endianness of the ASN1 DER blob?

    Wednesday, July 6, 2011 7:51 PM
  • Lets see the hash, the signature, and the certificate or public key.  I can handle hex like

    03 04 65 etc.

    Wednesday, July 6, 2011 8:18 PM
  • Here you go...

    Pub Key/cert

    >db /Count:444 0x011b41e0
    0x011B41E0  30 82 01 b8 30 82 01 62 a0 03 02 01 02 02 10 48 
    0x011B41F0  25 9c 1e e8 c0 41 ac 4b 8d 5e bc 92 76 55 83 30 
    0x011B4200  0d 06 09 2a 86 48 86 f7 0d 01 01 04 05 00 30 16 
    0x011B4210  31 14 30 12 06 03 55 04 03 13 0b 52 6f 6f 74 20 
    0x011B4220  41 67 65 6e 63 79 30 1e 17 0d 31 31 30 35 31 32 
    0x011B4230  31 30 32 39 31 31 5a 17 0d 33 39 31 32 33 31 32 
    0x011B4240  33 35 39 35 39 5a 30 13 31 11 30 0f 06 03 55 04 
    0x011B4250  03 13 08 44 4c 6f 77 6e 64 65 73 30 81 9f 30 0d 
    0x011B4260  06 09 2a 86 48 86 f7 0d 01 01 01 05 00 03 81 8d 
    0x011B4270  00 30 81 89 02 81 81 00 8e 92 f2 8a aa d2 92 c9 
    0x011B4280  d8 08 6d 8d 65 1a 08 4c 73 04 bc dd 46 8b df c3 
    0x011B4290  b7 c6 fa 66 9e e8 d2 03 72 53 2b 5b 5e 09 15 fc 
    0x011B42A0  e6 ff 14 5b 30 6a 93 cb 8f 63 a2 8c b5 6b 01 46 
    0x011B42B0  29 c5 ff e7 3a d0 7c 38 7d 62 c2 83 95 8c cf 48 
    0x011B42C0  d7 00 04 78 58 bd d1 3a b2 52 e2 0a f2 b2 86 6a 
    0x011B42D0  9c 78 ea 0c 55 98 5b 36 28 b7 26 db a7 1f b0 2c 
    0x011B42E0  8b 89 e8 6d 3e b5 ad 61 2b 1f 7b 6c 29 ca b1 4c 
    0x011B42F0  7a 26 9a 20 40 5e e4 f1 02 03 01 00 01 a3 4b 30 
    0x011B4300  49 30 47 06 03 55 1d 01 04 40 30 3e 80 10 12 e4 
    0x011B4310  09 2d 06 1d 1d 4f 00 8d 61 21 dc 16 64 63 a1 18 
    0x011B4320  30 16 31 14 30 12 06 03 55 04 03 13 0b 52 6f 6f 
    0x011B4330  74 20 41 67 65 6e 63 79 82 10 06 37 6c 00 aa 00 
    0x011B4340  64 8a 11 cf b8 d4 aa 5c 35 f4 30 0d 06 09 2a 86 
    0x011B4350  48 86 f7 0d 01 01 04 05 00 03 41 00 47 8c b2 8d 
    0x011B4360  b6 0d 19 ed a9 61 2b 11 ac 5c 9e 39 67 a4 9e 16 
    0x011B4370  61 12 ff 60 1d 21 40 be 1e 4e 03 23 f5 bc 74 cd 
    0x011B4380  c1 d2 2b f8 de 71 4b a5 33 b9 93 e6 d5 66 ac 97 
    0x011B4390  b8 38 8e a0 59 60 11 1d a2 35 45 12 


    >db /Count:128 pbSignatureData
    0x00597CC8  78 88 d6 41 7f 87 9e 82 20 78 69 ae bb b5 7c a9 
    0x00597CD8  ae e7 ac 20 c3 e1 ab 8e 46 1e 9b 25 9a d3 ce 53 
    0x00597CE8  cd 11 85 7e d2 d7 c6 97 3c a7 27 89 42 6b d6 5d 
    0x00597CF8  cd db c5 71 e9 fa 95 89 39 cf 2f 8a b0 04 95 3c 
    0x00597D08  31 9f b2 c1 71 0c c4 f1 b5 88 4a 41 17 86 c4 e2 
    0x00597D18  5a bb 80 63 58 af b6 75 76 ba cd 13 87 7e ef 04 
    0x00597D28  a8 14 5e f5 95 13 67 de da 22 d7 70 5b d7 1d 65 
    0x00597D38  a3 39 ab 56 cf d8 c6 38 ab 38 c0 ff 59 f2 90 7c 


    Hash
        [0]: 0x7d '}'
        [1]: 0xb3 '³'
        [2]: 0xf4 'ô'
        [3]: 0xd6 'Ö'
        [4]: 0x09 ' '
        [5]: 0x88 'ˆ'
        [6]: 0x20 ' '
        [7]: 0x3a ':'
        [8]: 0x47 'G'
        [9]: 0x12 ''
        [10]: 0xdc 'Ü'
        [11]: 0xc9 'É'
        [12]: 0xda 'Ú'
        [13]: 0xa5 '¥'
        [14]: 0x20 ' '
        [15]: 0x37 '7'
        [16]: 0xf0 'ð'
        [17]: 0x1b ''
        [18]: 0xfb 'û'
        [19]: 0xdf 'ß'

    Wednesday, July 6, 2011 9:59 PM
  • The good news is that I was able to verify the signature.
    So I reversed the signature to be little endian:
    7c 90 f2 59 ff c0 38 ab 38 c6 d8 cf 56 ab 39 a3
    65 1d d7 5b 70 d7 22 da de 67 13 95 f5 5e 14 a8
    04 ef 7e 87 13 cd ba 76 75 b6 af 58 63 80 bb 5a
    e2 c4 86 17 41 4a 88 b5 f1 c4 0c 71 c1 b2 9f 31
    3c 95 04 b0 8a 2f cf 39 89 95 fa e9 71 c5 db cd
    5d d6 6b 42 89 27 a7 3c 97 c6 d7 d2 7e 85 11 cd
    53 ce d3 9a 25 9b 1e 46 8e ab e1 c3 20 ac e7 ae
    a9 7c b5 bb ae 69 78 20 82 9e 87 7f 41 d6 88 78
    with this code:
    void Cspprov::reverse( vector< unsigned char > & data )
    {
     int len = (int)data.size();
     unsigned char t;
     int b = len-1;
     for ( int i=0; i < (len/2); i++ ) {
      t= data[i];
      data[i]= data[b];
      data[b]= t;
      b--;
     }
    }
    Did your reversal do exactly the same thing ?
     
    I used the hash: 7d b3 f4 d6 09 88 20 3a 47 12 dc c9 da a5 20 37 f0 1b fb df
    You did not hash the hash by accident I assume ?
     
    Since all I have is the hash, I used this code:
    // Signature is in little endian
    //
    bool Cspprov::VerifySignatureOfHash( HCRYPTKEY hPubKey,
     const vector<unsigned char> & hashData,
     const vector<unsigned char> & sig, DWORD dwFlags, int hashAlg )
    {
     if ( 0 == sig.size() ) return false;

     HCRYPTHASH hHash;
     if ( ! CryptCreateHash( itsProvider, hashAlg, 0, 0, &hHash) ) return( false );
     if ( ! CryptSetHashParam( hHash, HP_HASHVAL, hashData.data(), 0 ) ) return( false );
     bool ok= (TRUE == ::CryptVerifySignature( hHash, sig.data(), sig.size(), hPubKey, 0, dwFlags ));
     CryptDestroyHash( hHash );
     return( ok );
    }
    and the signature verified. 
    Hopefully we are very close ! 
     
    Your signature, when turned into plaintext with the public key is:
    00 01 ff ff ff ff ff ff ff ff ff ff ff ff ff ff
    ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
    ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
    ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
    ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
    ff ff ff ff ff ff ff ff ff ff ff ff
    00 30 21 30 09 06 05 2b 0e 03 02 1a 05 00
    04 14   7d b3 f4 d6 09 88 20 3a 47 12 dc c9 da a5 20 37 f0 1b fb df
    which matches the hash you sent.  
     
    and certificate
    30 82 01 b8 30 82 01 62 a0 03 02 01 02 02 10 48
    25 9c 1e e8 c0 41 ac 4b 8d 5e bc 92 76 55 83 30
    0d 06 09 2a 86 48 86 f7 0d 01 01 04 05 00 30 16
    31 14 30 12 06 03 55 04 03 13 0b 52 6f 6f 74 20
    41 67 65 6e 63 79 30 1e 17 0d 31 31 30 35 31 32
    31 30 32 39 31 31 5a 17 0d 33 39 31 32 33 31 32
    33 35 39 35 39 5a 30 13 31 11 30 0f 06 03 55 04
    03 13 08 44 4c 6f 77 6e 64 65 73 30 81 9f 30 0d
    06 09 2a 86 48 86 f7 0d 01 01 01 05 00 03 81 8d
    00 30 81 89 02 81 81 00 8e 92 f2 8a aa d2 92 c9
    d8 08 6d 8d 65 1a 08 4c 73 04 bc dd 46 8b df c3
    b7 c6 fa 66 9e e8 d2 03 72 53 2b 5b 5e 09 15 fc
    e6 ff 14 5b 30 6a 93 cb 8f 63 a2 8c b5 6b 01 46
    29 c5 ff e7 3a d0 7c 38 7d 62 c2 83 95 8c cf 48
    d7 00 04 78 58 bd d1 3a b2 52 e2 0a f2 b2 86 6a
    9c 78 ea 0c 55 98 5b 36 28 b7 26 db a7 1f b0 2c
    8b 89 e8 6d 3e b5 ad 61 2b 1f 7b 6c 29 ca b1 4c
    7a 26 9a 20 40 5e e4 f1 02 03 01 00 01 a3 4b 30
    49 30 47 06 03 55 1d 01 04 40 30 3e 80 10 12 e4
    09 2d 06 1d 1d 4f 00 8d 61 21 dc 16 64 63 a1 18
    30 16 31 14 30 12 06 03 55 04 03 13 0b 52 6f 6f
    74 20 41 67 65 6e 63 79 82 10 06 37 6c 00 aa 00
    64 8a 11 cf b8 d4 aa 5c 35 f4 30 0d 06 09 2a 86
    48 86 f7 0d 01 01 04 05 00 03 41 00 47 8c b2 8d
    b6 0d 19 ed a9 61 2b 11 ac 5c 9e 39 67 a4 9e 16
    61 12 ff 60 1d 21 40 be 1e 4e 03 23 f5 bc 74 cd
    c1 d2 2b f8 de 71 4b a5 33 b9 93 e6 d5 66 ac 97
    b8 38 8e a0 59 60 11 1d a2 35 45 12
    Wednesday, July 6, 2011 10:46 PM
  • So close and so far. :(

    Can you show me how you've got your hPubKey from the cert data please?

    Thursday, July 7, 2011 8:10 AM
  •  void calc::CertVerifySignature( DWORD dwFlags )
    {
     vector<unsigned char> certData= Iolib::StringToBytes( textField[ CERT ]->getText() );
     PCCERT_CONTEXT certcon= ::CertCreateCertificateContext( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, certData.data(), certData.size() );
     cspkey pubkey( itsProv, certcon );

     SmartUtil::tstring hexstring;

     if ( pubkey.m_valid_key ) {

      // Fetch the data
      vector<unsigned char> hashdat= fetchIn();

      // Verify the signature in OUT_DAT to the hash(in_dat).
      vector<unsigned char> sig= fetchOut();

      if ( pubkey.verifySignatureOfHash( itsProv, hashdat, sig, dwFlags, CALG_SHA1 ) ) {
       status->setText( _T("VerifySignatureOfHash ok.") );
    __________

     


    // Constructor of a public key from a certificate.
    //
    cspkey( Cspprov & prov, PCCERT_CONTEXT certcon )
    {
     PCERT_PUBLIC_KEY_INFO pNewPubKeyinfo;
     pNewPubKeyinfo = &(certcon->pCertInfo->SubjectPublicKeyInfo);

     m_valid_key= CryptImportPublicKeyInfo( prov.getHCryptProv(), X509_ASN_ENCODING,
                pNewPubKeyinfo, &m_hKey )
       ? true : false;
    }

     

    ____________

     

     

     

    Thursday, July 7, 2011 9:44 AM
  • Cheers. It's working for me now.

    The problem I had was that the code I'd started from used the CRYPT_NOHASHOID flag in the call to CryptVerifySignature. Changing that to 0 and doing the byte reversal of the signature (which I had tried before), were the key things wrong.

    Thanks tremendously - I'll try to find time now to remember what I was aiming to do before I got stuck on this experiment.

     

    Thursday, July 7, 2011 10:14 AM
  • Great.

    It would have been easy to try these choices:

    No flags  with unreversed signature:    Would fail.

    CRYPT_NOHASHOID with unreversed signature:    Would fail.

    CRYPT_NOHASHOID  with reversed signature:    Would fail.

    and forget to actually try:

    No flags  with reversed signature:    Would work.


    Once we knew that the hash, signature, and public key were likely correct in:

          ::CryptVerifySignature( hHash, sig.data(), sig.size(), hPubKey, 0, dwFlags ));

    as Sherlock Holmes says, What ever remains after eliminating the impossible must be true.

        so the dwFlags must be the culprit.



    Thursday, July 7, 2011 2:33 PM
  • >as Sherlock Holmes says, What ever remains after eliminating the impossible must be true.

    Indeed, that was the case. I think I'd gone through every permutation I could think of - and missed one.

    I just wish it wasn't guess work.

    Thanks again for the sanity check - it worked wonders :)

    Thursday, July 7, 2011 3:42 PM