0

I need to use RSA for signature but I am weak on SSL thingy so I find some code in internet.

I have find below code which I can used to sign ansistring successfully :

function GenarateRSASign(PrivateKey, Content: AnsiString): AnsiString;
var
  buffer: PAnsiChar;
  rsa_out, md: array[0..1023] of AnsiChar;
  r: PRSA;
  rsa_out_len: Cardinal;
  key: PBIO;
  hIdCrypto: HMODULE;
  hash: array[0..SHA_DIGEST_LENGTH - 1] of AnsiChar;
type
  Trsa_sign = function(_type: LongInt; const m: PAnsiChar; m_length: LongWord; sigret: PAnsiChar; var siglen: Cardinal; const rsa: PRSA): LongInt; cdecl;
  Tsha1 = function(d: PAnsiChar; n: Cardinal; md: PAnsiChar): PAnsiChar; cdecl;

  function LoadFunctionCLib(const FceName: string; const ACritical: Boolean = True): Pointer;
  begin
    Result := GetProcAddress(hIdCrypto, PChar(FceName));
  end;
var
  rsa_sign: Trsa_sign;
  sha1: Tsha1;
  bytes: TIdBytes;
begin
  LoadOpenSSLLibrary;
  hIdCrypto := LoadLibrary('libeay32.dll');
  Assert(hIdCrypto <> 0, 'Cannot load libeay32.dll');
  try
    key := BIO_new_mem_buf(@PrivateKey[1], Length(PrivateKey));
    r := PEM_read_bio_RSAPrivateKey(key, nil, nil, nil);
    if r = nil then
      Exit;
    buffer := PAnsiChar(Content);
    FillChar(md[0], 1024, 0);
    FillChar(hash[0], SHA_DIGEST_LENGTH, 0);
    sha1 := LoadFunctionCLib('SHA1');
    Assert(@sha1 <> nil, 'Cannot load SHA1');
    sha1(buffer, Length(Content), @hash[0]);
    rsa_sign := LoadFunctionCLib('RSA_sign');
    Assert(@rsa_sign <> nil, 'Cannot load RSA_sign');
    FillChar(rsa_out[0], 1024, 0);
    rsa_sign(EVP_sha1()._type, @hash[0], SHA_DIGEST_LENGTH, @rsa_out[0], rsa_out_len, r);
    SetLength(bytes, rsa_out_len);
    if rsa_out_len > 0 then
      Move(rsa_out[0], bytes[0], rsa_out_len);
    Result := AnsiString(TIdEncoderMIME.EncodeBytes(bytes));
    BIO_free(key);
    RSA_free(r);
  finally
    FreeLibrary(hIdCrypto);
    UnLoadOpenSSLLibrary;
  end;
end;

However, I need to sign utf8 string As I don't know if there is widestring version of 'rsa_sign' and 'sha1' functions inside libeay32.dll and their names, I just don't know how to alter the code to suit for utf8 string signature.

I would like to have some suggestion on how to modify these code or have other alternative to do the same work. Thank you.

Edit 1 : Based on David's Advice, I had modify the code as below :

function GenarateRSASign(PrivateKey: AnsiString, Content: String): String;
var
  buffer : TBytes;
  rsa_out, md: array[0..1023] of Byte;
  r: PRSA;
  rsa_out_len: Cardinal;
  key: PBIO;
  hIdCrypto: HMODULE;
  hash: array[0..SHA_DIGEST_LENGTH - 1] of Byte;
type
  Trsa_sign = function(_type: LongInt; const m: PBype; m_length: LongWord; sigret: PBype; var siglen: Cardinal; const rsa: PRSA): LongInt; cdecl;
  Tsha1 = function(d: PBype; n: Cardinal; md: PBype): PByte; cdecl;

  function LoadFunctionCLib(const FceName: string; const ACritical: Boolean = True): Pointer;
  begin
    Result := GetProcAddress(hIdCrypto, PChar(FceName));
  end;
var
  rsa_sign: Trsa_sign;
  sha1: Tsha1;
  bytes: TIdBytes;
begin
  LoadOpenSSLLibrary;
  hIdCrypto := LoadLibrary('libeay32.dll');
  Assert(hIdCrypto <> 0, 'Cannot load libeay32.dll');
  try
    key := BIO_new_mem_buf(@PrivateKey[1], Length(PrivateKey));
    r := PEM_read_bio_RSAPrivateKey(key, nil, nil, nil);
    if r = nil then
      Exit;
    FillChar(md[0], 1024, 0);
    FillChar(hash[0], SHA_DIGEST_LENGTH, 0);
    sha1 := LoadFunctionCLib('SHA1');
    Assert(@sha1 <> nil, 'Cannot load SHA1');
    buffer := TEncoding.UTF8.GetBytes(Content);
    sha1(@buffer[0], Length(Buffer), @hash[0]);
    rsa_sign := LoadFunctionCLib('RSA_sign');
    Assert(@rsa_sign <> nil, 'Cannot load RSA_sign');
    FillChar(rsa_out[0], 1024, 0);
    rsa_sign(EVP_sha1()._type, @hash[0], SHA_DIGEST_LENGTH, @rsa_out[0], rsa_out_len, r);
    SetLength(bytes, rsa_out_len);
    if rsa_out_len > 0 then
      Move(rsa_out[0], bytes[0], rsa_out_len);
    Result := TIdEncoderMIME.EncodeBytes(bytes);
    BIO_free(key);
    RSA_free(r);
  finally
    FreeLibrary(hIdCrypto);
    UnLoadOpenSSLLibrary;
  end;
end;

I get successful result when the content contain only English, which is same as before. However, when there is some Chinese Content, the result is rejected by the other party (Alipay) which reported "Illegal Sign". The input_charset is specified as 'UTF8'. Anythings else which I had done wrong here? Thank you.

Justmade
  • 1,496
  • 11
  • 16
  • 2
    Don't ever use string data types for encryption. Use binary. Pick an encoding. You want UTF8. Encode the string as UTF8 bytes. Encrypt that. You current code makes that all too familiar mistake of sending text to encryption functions. A mistake typically seen in php and delphi programs. You've mistranslated the rsa_sign function. The C++ function accepts `unsigned char*`. That's not text. That's binary data. An array of bytes. – David Heffernan Apr 18 '18 at 05:49
  • So where you used `PAnsiChar` is should be `PByte`. The rest is simple. Pass a native `string` to the function. Convert to byte array with `TEncoding.UTF8.GetBytes`. Pass the address of the first byte to the sign function. – David Heffernan Apr 18 '18 at 05:53
  • @DavidHeffernan Thank you very much for your help. I had edit the post to show modified code based on your comment. However, I still got illegal sign result. Can you help to see if I got any other wrong coding please? Thank you. – Justmade Apr 18 '18 at 09:14

0 Answers0