views:

984

answers:

1

I would like to rewrite this php digital signing function into Delphi code.

  function SaySig() {

  $privKeyFilePath = "c:\temp\myrsakey.pem";

  $data = "sign this string";
  $fp = fopen($privKeyFilePath, "r");
  $priv_key = fread($fp, 8192);
  fclose($fp);
  $privatekeyid = openssl_get_privatekey($priv_key);
  openssl_sign($data, $signature, $privatekeyid, OPENSSL_ALGO_SHA1);
  openssl_free_key($privatekeyid);
  $sig = base64_encode($signature);
  echo "<br>";
  echo "Signature:".$sig."<br><br>";
  }

I don't care which component is used (lockbox, DelphiOpenSSL, Chilkat Crypt ActiveX, etc). I have tried all of these with no success; which makes me think I am doing something fundamentally wrong.

My key was generated as such:

openssl req -x509 -nodes -days 365 -newkey rsa:1024 -sha1 -subj "/C=US/ST=CA/L=Mountain View/CN=www.mycompany.com" -keyout myrsakey.pem -out c:\temp\myrsacert.pem

The contents of the key (don't worry - I will generate a new one):

-----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQCqsR7s4X74LfTiLv1PP6Yn0SBpGBtbzkBSQ95E2b9Haa3Qtf0a KjDJpZLMwXC/IrSP7K2Gxbl2cZotT19GVgw6PcYPTBBWX2gJoVrnQZP8uPdlGAgS plODP55R9f4F0KzIpE6d+dpTGfJ1wysFqYN8fxtlu8K7YO/Mh8tNzN5VOQIDAQAB AoGBAIvCvRyeQlU5Y+JzMSvbZNQDUrNabsRL67SwJ2VemVUCvbQ/3v62fv4M2VdY KFYIN6oE08yfRw0pVWE2NT+lIxqSQx7+qv84Y7duqT7155wpCFj+a/6pYyNTFNFi 5wiTnN13eyHNgKxZm7QcMH67T/noTgz0LoT5p54ynmfNcjyBAkEA3DCEQ6Dm2xYH Nhk3+7sNEVklN20zNqyYvrCunNLAiLioF1jDApdfcT8YtVd29L7tH1ZdJYG5DXJ8 Bs7eKLGekQJBAMZzy0Q7LZHdWQxSRi7wy0eq6SqZMqi0pb9VPuXjWG1y+rtRr1vV vyMaGz4rcE7mkbq/Nkn+AQXc30GOj3GE8CkCQQCMDVwDfBN6pL8/fLjsJ+S+9RnD 8HRTwWKCX/UgkLif/fwEpZOcUVYGvSBlL9XdBJfkh9VFZwaidABJgEk0Tw3RAkBd 6pjMnpDvUeh9e0Y5mr0pGookHcIqsuspxEby9od3rI1aLsslU9+T1hwEbPxGarmW vj0MAUgspR2G4deiqn4ZAkEAnWxV7NhtVPLs5Y2ZYeHz7ipdcSL4/keLW4PwKerF 7LJj4s7/6ZqnHA6Z0yhCcziflYQArWt1ViLMIYZ8grr5Kg== -----END RSA PRIVATE KEY-----

And the output looks as such:

jcIIsr145dTwDrT8g4jb2HZ5FP5UL6/9mK7hF6hC2lCZGlM0W4QqFqytghWaU0w3Z6JkMVUlxxWtQ2R+ vWQVB0F3htAtbVZkiA67x0zor+zmpClBIazmfVJlng4sG1R7CCUZ0gGhdm4JMc08VsWU25utudcG6inpl whQiZgefW0=

It might appear as if I am requesting someone to "do my work". But I have been pounding away at this for some time with no success. Thanks.

Someone requested I post some of my code. Below are some of the things I have tried..

function TMainWeb.sign(mstring: String): string;
var
  mPrivateKey: TLbRSAKey;
  LbRSASSA1: TLbRSASSA;
begin
  LbRSASSA1:= TLbRSASSA.create(nil);
  LbRSASSA1.PrivateKey.LoadFromFile('C:\temp\myrsakey.der');
  LbRSASSA1.HashMethod := TRSAHashMethod(hmSHA1);
  LbRSASSA1.SignString(mString);
  result := LbRSASSA1.Signature.IntStr;
end;

function TMainWeb.sign1(mstring: String): string;
var
  LbDSA1: TLbDSA;
  mPrivateKey: TLbRSAKey;
begin
  mPrivateKey := TLbRSAKey.Create(aks1024);
  mPrivateKey.LoadFromFile('C:\temp\myrsakey.der');
  LbDSA1 := TLbDSA.create(application);
  lbDSA1.PrivateKey.Assign(mPrivateKey);
  LbDSA1.SignString(mString);
end;

function TMainWeb.Sign2(mString: String): string;
var
  signer: TMessageSigner;
begin
  signer := TMessageSigner.Create;
  signer.LoadPrivateKey('C:\temp\myrsakey.pem');
  signer.PlainMessage := mString;
  signer.MIMESign;
  result := signer.SignedMessage;
end;
+1  A: 

Try this.
I don't claim it's perfect code(!) but it compiles :-)   and gives the same result you've quoted. Uses the OpenSSL API from M Ferrante that you mention above. A lot of the stuff it does you would normally only do once at startup - such as load private key, InitSSL etc. I use the Jedi JCL for base64 stuff - it's more straightforward.
Also some of this looks a bit odd (uses TBytes where PChar would do etc etc) as I originally wrote it using my modified Delphi 2010 API headers but then realised you were using D2007 and TEncoding wasn't available and a few mods were needed.
(SignStringToBase64 is the main call, right at the bottom of the listing)

uses libeay32, jclmime;

const
  LIBEAY_DLL_NAME = 'libeay32.dll';

// These aren't defined in the original libeay32.pas file
procedure EVP_MD_CTX_init(ctx: PEVP_MD_CTX); cdecl; external LIBEAY_DLL_NAME;
function EVP_MD_CTX_cleanup(ctx: PEVP_MD_CTX): integer; cdecl; external LIBEAY_DLL_NAME;

procedure InitSSL;
begin
  OpenSSL_add_all_algorithms;
  OpenSSL_add_all_ciphers;
  OpenSSL_add_all_digests;
  ERR_load_crypto_strings;
  // Seed the pseudo-random number generator
  // This should be something a little more "random"!
  RAND_load_file('c:\windows\paint.exe', 512);
end;

procedure FinalizeSSL;
begin
  EVP_cleanup;
end;

function GetSSLErrorMessage: string;
const
  BUFF_SIZE = 128; // OpenSSL docs state should be >= 120 bytes
var
  err: TBytes;
begin
  SetLength(err, BUFF_SIZE);
  ERR_error_string(ERR_get_error, @err[0]);
  result := string(err);
end;

function RSALoadPrivateKey(const AFileName, APassPhrase: string): PRSA;
var
  bp: pBIO;
  fn, pp: PAnsiChar;
  pk: PRSA;
begin
  fn := PAnsiChar(AnsiString(AFileName));
  pp := PAnsiChar(AnsiString(APassPhrase));
  bp := BIO_new(BIO_s_file());
  BIO_read_filename(bp, fn);
  pk := nil;
  result := PEM_read_bio_RSAPrivateKey(bp, pk, nil, pp);
  if result = nil then
    raise Exception.Create('Private key failure.' + GetSSLErrorMessage);
end;

function LoadPrivateKey(const AFileName, APass: string): PEVP_PKEY;
var
  rkey: PRSA;
begin
  rkey := RSALoadPrivateKey(AFileName, APass);
  result := EVP_PKEY_new;
  EVP_PKEY_assign(result, EVP_PKEY_RSA, rkey);
end;

procedure CleanUpKey(AKey: PEVP_PKEY);
begin
  if (AKey <> nil) then
  begin
    EVP_PKEY_free(AKey);
    // The OpenSSL docs state that the related rsa key will also
    // be freed when the parent key is freed
  end;
end;

function EVPSign(ASource: TBytes; const APrivateKey: PEVP_PKEY): TBytes;
var
  keysize: integer;
  ks: cardinal;
  ctx: EVP_MD_CTX;
begin
  keysize := EVP_PKEY_size(APrivateKey);
  SetLength(result, keysize);

  EVP_MD_CTX_init(@ctx);
  try
    EVP_SignInit(@ctx, EVP_sha1);
    EVP_SignUpdate(@ctx, @ASource[0], Length(ASource));
    EVP_SignFinal(@ctx, @result[0], ks, APrivateKey);
    SetLength(result, ks);
  finally
    EVP_MD_CTX_cleanup(@ctx);
  end;
end;

function Base64EncodeBytes(Input: TBytes): string;
var
  b64: TBytes;
begin
  SetLength(b64, jclMime.MimeEncodedSizeNoCRLF(Length(Input)));
  jclMime.MimeEncodeNoCRLF(Input[0], Length(Input), b64[0]);
  result := string(b64);
end;

function SignStringToBase64(const AText: string): string;
var
  key: PEVP_PKEY;
  src, enc: TBytes;
begin
  InitSSL;
  try
    key := LoadPrivateKey('c:\temp\priv-key.pem', '');
    try
      SetLength(src, Length(AText)); 
      CopyMemory(@src[0], @AText, Length(AText));           
      enc := EVPSign(src, key);
      result := Base64EncodeBytes(enc);
    finally
      CleanUpKey(key);
    end;
  finally
    FinalizeSSL;
  end;
end;
shunty
Thank you very much. Trying to implement it now but having problems getting Jedi to install/compile (got it from http://sourceforge.net/projects/jvcl/files/). jclmime.pas requires jcl.inc, which requires jcld11.inc, which cannot be found. Will keep digging.
M Schenkel
Just run install.bat in the root of the JCL install - it's remarkably sophisticated. jcld11.inc gets created automagically with a load of version specific stuff.(Or use your own favourite Base64 library)
shunty
Actually put it down for a bit and then got back on it. Got the source from http://sourceforge.net/projects/jcl/files/ and it compiles.
M Schenkel
I probably should know the answer to this. But should the same string digitally signed by two methods, but employing the same algorithm, yield the exact same result? Or is there some hashing alorithm embedding into digital signing which will cause them to be different?
M Schenkel
I have everything going. The signature generated looks correct (about the same length, ends with the "=" sign). But when I pass it along for authorization to Google AuthSub it is responding "Invalid Authsub token". Should the digital signature always be the same size (i.e. lenght)?
M Schenkel
I tried signing a short phrase: SignStringToBase64('SomeShortString'). Get an access violation at src := TBytes(AText); The php version does sign this short phrase.
M Schenkel
Sorry, wasn't allowed to play computers this week-end. Work-life balance and all that :-)According to the OpenSSL docs the sig will be at most EVP_PKEY_size(pkey) bytes. And the actual size is returned in the EVP_SignFinal call. As far as I am aware it's always the same size for a given private key/algorithm combination.The AV occurs because of my shoddy way of casting a string to TBytes! Replace the src := TBytes(... line with the following 2 lines: SetLength(src, Length(AText)); CopyMemory(@src[0], @AText, Length(AText));Always gives the same sig for the same input/key/algorithm.
shunty
Did the PHP func work OK with Google? The OpenSSL funcs above give *exactly* the same sig as the PHP one you described - so it seems odd that one would work and not the other. Perhaps Google requires proper MIME encoding - ie with line breaks at 76 chars?
shunty