0

I need to sign some data with the SHA256withRSA algorithm (RSASSA-PKCS1-V1_5-SIGN with SHA-256 hash function) in Windows (and with Delphi or in c++). How can I do this? I want to use the Windows CryptoAPI.

This is how I do to verify a signature :

function ALVerifyRSA256Signature(const aData: AnsiString; // bytes string
                                 const aSignature: AnsiString; // bytes string
                                 const aBase64PubKeyModulus: ansiString;
                                 Const aBase64PubKeyExponent: ansiString): boolean;

  {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  procedure _bigEndianToLittleEndian(var aArr: TBytes);
  var B: Byte;
      i,j: integer;
  begin
    J := Length(aArr) - 1;
    i := low(aArr);
    while i < J do begin
      B := aArr[i];
      aArr[i] := aArr[j];
      aArr[j] := B;
      Dec(j);
      inc(i);
    end;
  end;

var pModulus: TBytes;
    cbModulus: DWORD;
    pExponent: TBytes;
    cbExponent: DWORD;
    dwExponent: Dword;
    hProv: HCRYPTPROV;
    cbKeyBlob: DWord;
    pKeyBlob: Tbytes;
    pPublicKey: PUBLICKEYSTRUC;
    pRsaPubKey: RSAPUBKEY;
    hRSAKey: HCRYPTKEY;
    hHash: HCRYPTHASH;
    pSignature: TBytes;

begin

  //init pModulus / cbModulus
  if not CryptStringToBinaryA(
           PansiChar(aBase64PubKeyModulus), // pszString: LPCSTR;
           length(aBase64PubKeyModulus), // cchString: DWORD;
           CRYPT_STRING_BASE64, // dwFlags: DWORD;
           nil, // pbBinary: pByte;
           @cbModulus, // pcbBinary: PDWORD;
           nil, // pdwSkip: PDWORD;
           nil) then raiseLastOsError; // pdwFlags: PDWORD
  setlength(pModulus, cbModulus);
  if not CryptStringToBinaryA(
           PansiChar(aBase64PubKeyModulus), // pszString: LPCSTR;
           length(aBase64PubKeyModulus), // cchString: DWORD;
           CRYPT_STRING_BASE64, // dwFlags: DWORD;
           @pModulus[0], // pbBinary: pByte;
           @cbModulus, // pcbBinary: PDWORD;
           nil, // pdwSkip: PDWORD;
           nil) then raiseLastOsError; // pdwFlags: PDWORD
  _bigEndianToLittleEndian(pModulus);

  //init pExponent / cbExponent
  if not CryptStringToBinaryA(
           PansiChar(aBase64PubKeyExponent), // pszString: LPCSTR;
           length(aBase64PubKeyExponent), // cchString: DWORD;
           CRYPT_STRING_BASE64, // dwFlags: DWORD;
           nil, // pbBinary: pByte;
           @cbExponent, // pcbBinary: PDWORD;
           nil, // pdwSkip: PDWORD;
           nil) then raiseLastOsError; // pdwFlags: PDWORD
  setlength(pExponent, cbExponent);
  if not CryptStringToBinaryA(
           PansiChar(aBase64PubKeyExponent), // pszString: LPCSTR;
           length(aBase64PubKeyExponent), // cchString: DWORD;
           CRYPT_STRING_BASE64, // dwFlags: DWORD;
           @pExponent[0], // pbBinary: pByte;
           @cbExponent, // pcbBinary: PDWORD;
           nil, // pdwSkip: PDWORD;
           nil) then raiseLastOsError; // pdwFlags: PDWORD
  _bigEndianToLittleEndian(pExponent);
  if cbExponent > sizeof(dwExponent) then
    raise Exception.CreateFmt('Wrong exponent (%s)',[aBase64PubKeyExponent]);
  dwExponent := 0;
  move(pExponent[0], dwExponent, cbExponent);

  //acquire a handle to a particular key container
  if (not CryptAcquireContextA(@hProv, // phProv: PHCRYPTPROV;
                               nil, // pszContainer: PAnsiChar;
                               nil, // pszProvider: PAnsiChar;
                               PROV_RSA_AES, // dwProvType: DWORD;
                               CRYPT_VERIFYCONTEXT)) then raiselastOsError; // dwFlags: DWORD
  try

    // create the pKeyBlob
    // The data format is: PUBLICKEYSTRUC + RSAPUBKEY + key
    cbKeyBlob := sizeof(PUBLICKEYSTRUC) + sizeof(RSAPUBKEY) + cbModulus;
    setlength(pKeyBlob, cbKeyBlob);

    // Fill in the PUBLICKEYSTRUC
    pPublicKey.bType := PUBLICKEYBLOB;
    pPublicKey.bVersion := CUR_BLOB_VERSION;  // Always use this value.
    pPublicKey.reserved := 0;                 // Must be zero.
    pPublicKey.aiKeyAlg := CALG_RSA_KEYX;     // RSA public-key key exchange.
    Move(pPublicKey,pKeyBlob[0],sizeof(PUBLICKEYSTRUC));

    // Fill in the RSAPUBKEY
    pRsaPubKey.magic := RSA1;            // Public key.
    pRsaPubKey.bitlen := cbModulus * 8;  // Number of bits in the modulus.
    pRsaPubKey.pubexp := dwExponent;     // Exponent.
    Move(pRsaPubKey,pKeyBlob[sizeof(PUBLICKEYSTRUC)],sizeof(RSAPUBKEY));

    // Fill in the modulus
    Move(pModulus[0],pKeyBlob[sizeof(PUBLICKEYSTRUC)+sizeof(RSAPUBKEY)],cbModulus);

    // Now import the key.
    if not CryptImportKey(hProv, // hProv: HCRYPTPROV;
                          @pKeyBlob[0], // const pbData: PBYTE;
                          cbKeyBlob, // dwDataLen: DWORD;
                          0, // hPubKey: HCRYPTKEY;
                          0, // dwFlags: DWORD;
                          @hRSAKey) then raiseLastOsError; // phKey: PHCRYPTKEY
    try

      //initiates the hashing of a stream of data.
      if not (CryptCreateHash(hProv, // hProv: HCRYPTPROV;
                              CALG_SHA_256, // Algid: ALG_ID;
                              0, // hKey: HCRYPTKEY;
                              0, // dwFlags: DWORD;
                              @hHash)) then raiseLastOsError;
      try

        //adds data to a specified hash object.
        if not (CryptHashData(hHash, // hHash: HCRYPTHASH;
                              pbyte(aData), // const pbData: PBYTE;
                              length(aData), // dwDataLen: DWORD;
                              0)) then raiseLastOsError; // dwFlags: DWORD

        //verifies the signature
        setlength(pSignature, length(aSignature));
        Move(Pointer(aSignature)^, Pointer(pSignature)^, Length(aSignature));
        _bigEndianToLittleEndian(pSignature);
        if not CryptVerifySignatureA(hHash, // hHash: HCRYPTHASH;
                                     @pSignature[0], // const pbSignature: PBYTE;
                                     length(pSignature), // dwSigLen: DWORD;
                                     hRSAKey, // hPubKey: HCRYPTKEY;
                                     nil, // const sDescription: LPCSTR;
                                     0) then begin // dwFlags: DWORD)
          if HRESULT(GetLastError) = NTE_BAD_SIGNATURE then exit(False)
          else raiseLastOsError;
        end;

        //everything is ok
        Result := True;

      finally
        if not CryptDestroyHash(hHash) then raiseLastOsError;
      end;

    finally
      if not CryptDestroyKey(hRSAKey) then raiseLastOsError;
    end;

  finally
    if not CryptReleaseContext(hProv,0) then raiseLastOsError;
  end;
end;

However in my case I need to sign some data using this private_key :

"-----BEGIN PRIVATE KEY-----\xxxxxxxxxxxxxxxxxxx\n-----END PRIVATE KEY-----\n"

How I can do?

zeus
  • 12,173
  • 9
  • 63
  • 184
  • 1
    Unrelated, thanks for restoring my faith in how much easier it is to do this with OpenSSL than WinCrypt. That said, is the real problem one of how to import an RSA private key in PEM form? If so, [this is probably going to help](https://stackoverflow.com/questions/8412838/how-to-import-private-key-in-pem-format-using-wincrypt-and-c), and I suspect it will be considerably easier than the hoops you're jumping through now manually snacking what appears to be a DER encoding. – WhozCraig Oct 23 '20 at 21:01
  • 1
    @WhozCraig yes I know about OpenSSL but I would like to avoid to distribute a dependency with my app and instead use only winAPi :( thanks for the link ! – zeus Oct 23 '20 at 22:47
  • @WhozCraig really Windows Crypto api not not more difficult and probably more effective than OpenSSL. but here main code really, as you say, in import PEM string data (which is native for OpenSSL but not for winapi) to crypto handle. strange that no (or i dont know about it) single api for such conversion, but not hard once write this generic code and than use – RbMm Oct 24 '20 at 16:49
  • @RbMm I delete my answer, finally it's didn't work :( – zeus Oct 25 '20 at 00:27
  • @loki - i not check your code but you on correct way. i do near your code, the but use Bcrypt api - [`this code`](https://github.com/rbmm/PEM/blob/main/pem.cpp) 100% work. here exist how convert private and public key (and public key from cert) to windows crypto handle – RbMm Oct 25 '20 at 00:31
  • @RbMn do you have any sample of how I can do this with the BCrypt API without openSSL (I want to avoid to use openSSL)? I think with CryptoApi I simply can not do RSA encoding with SHA 256 key :( – zeus Oct 25 '20 at 00:42
  • @loki -but i post you working sample - https://github.com/rbmm/PEM/blob/main/pem.cpp - CryptoApi **can** do RSA encoding with SHA 256 key - for me all work – RbMm Oct 25 '20 at 00:45
  • @loki - in your code you need `CryptDecodeObjectEx` with `PKCS_PRIVATE_KEY_INFO` first but not with `PKCS_RSA_PRIVATE_KEY` (not note different at begin) – RbMm Oct 25 '20 at 00:51
  • than i use `CNG_RSA_PRIVATE_KEY_BLOB`, for old blob format probably need use `PKCS_RSA_PRIVATE_KEY` already - can not check just now. but need 2 calls for `CryptDecodeObjectEx` - first in all case with `PKCS_PRIVATE_KEY_INFO` and second depend from are you use *Bcrypt* or legacy *Crypt* api. for *Bcrypt* this is `CNG_RSA_PRIVATE_KEY_BLOB` and for legacy i guess `PKCS_RSA_PRIVATE_KEY` but not check – RbMm Oct 25 '20 at 00:54
  • in any case i sure that windows crypto api have functional how minimum not less compare openssl. – RbMm Oct 25 '20 at 00:56
  • @rbnm, i finally make it working, but only with BCrypt. seam that legacy cryptoAPI can not do RSAWithSHA256 signature (i try but the signature is never good and if i use PROV_RSA_FULL then SHA256 is not supported) – zeus Oct 25 '20 at 19:10

0 Answers0