Is there any way with, say Perl or PHP, that I can grab output from another process that outputs to the Windows cmd shell? I have a game server that outputs certain information, for example say 'player finished track in 43s' and I want to grab that line and use Perl or PHP to send a request to a webserver to update ranks on a web page. Is there a way to grab that output pipe in Perl or PHP? Or could I achieve this using C++ Windows API maybe?
Let me clarify here: I want to execute a seperate Perl or PHP script that grabs output from the Windows cmd shell, and the output that is being displayed to the Windows cmd shell is coming from a different process.
You could use IPC::Open3 to read from the other process' standard output. Note that inter-process communication assumes a parent/child relationship between the processes. If that's not the case... I'm not aware of a mechanism for attaching to the output of a pre-existing process. In that case you may need to alter the producer to write data to a log file (or database) that your application can read from.
If all you care about is STDOUT, you can just use open2 from IPC::Open2:
#!/usr/bin/perl
use strict;
use warnings;
use IPC::Open2;
#if there are arguments pretend to be the server
#for this example
if (#ARGV) {
local $| = 1;
for my $i (1 .. 100) {
print "pid $$ iter $i\n";
sleep 1;
}
exit;
}
#run perl with the current script as its argument,
#pass in an arg so that we trigger the behaviour
#above
open2 my $out, my $in, $^X, $0, 1
or die "could not run '$^X $0 1': $!\n";
while (<$out>) {
s/[\r\n]//g;
print "pid $$ saw [$_]\n";
}
You need to start your server within Perl:
my $server_out = `server.exe`; # Note the backticks.
Now $server_out contains the output of server.exe. But the trick here is that you have to wait until server.exe exits to get the out put.
Try IPC::Run (which is not a core module)
use English;
use IPC::Run;
my ($stdout, $stderr);
IPC::Run::run([$cmd, $arg1, $arg2, $argN], \undef, \$stdout, $stderr);
while(<$stdout>) {
print "Cmd said $_\n";
}
Note: Code not tested.
Found the info here.
Capturing the output in Perl is as simple as:
$output = qx(command);
or
$output = `command`; # backticks
Refer: perldoc perlop
This code redirects the STDOUT of a console application to a stringlist, which you can use on a memo for example. It's Delphi code, but in C++ the basic idea is exactly the same.
I use it to run console applications hidden, while redirecting the output to my own application, to show in a pane. It adds a new line to AStrings as soon as data comes in, so you'll have access to the output of the other application before it finishes.
procedure RunConsoleApp(const CommandLine: string; AStrings: TStrings);
type
TCharBuffer = array[0..MaxInt div SizeOf(Char) - 1] of Char;
const
MaxBufSize = 1024;
var
I: Longword;
SI: TStartupInfo;
PI: TProcessInformation;
SA: PSecurityAttributes;
SD: PSECURITY_DESCRIPTOR;
NewStdIn: THandle;
NewStdOut: THandle;
ReadStdOut: THandle;
WriteStdIn: THandle;
Buffer: ^TCharBuffer;
BufferSize: Cardinal;
Last: WideString;
Str: WideString;
ExitCode_: DWORD;
Bread: DWORD;
Avail: DWORD;
begin
GetMem(SA, SizeOf(TSecurityAttributes));
case Win32Platform of
VER_PLATFORM_WIN32_NT:
begin
GetMem(SD, SizeOf(SECURITY_DESCRIPTOR));
SysUtils.Win32Check(InitializeSecurityDescriptor(SD, SECURITY_DESCRIPTOR_REVISION));
SysUtils.Win32Check(SetSecurityDescriptorDacl(SD, True, nil, False));
SA.lpSecurityDescriptor := SD;
end; {end VER_PLATFORM_WIN32_NT}
else
SA.lpSecurityDescriptor := nil;
end; {end case}
SA.nLength := SizeOf(SECURITY_ATTRIBUTES);
SA.bInheritHandle := True;
SysUtils.Win32Check(CreatePipe(NewStdIn, WriteStdIn, SA, 0));
if not CreatePipe(ReadStdOut, NewStdOut, SA, 0) then
begin
CloseHandle(NewStdIn);
CloseHandle(WriteStdIn);
SysUtils.RaiseLastWin32Error;
end; {end if}
GetStartupInfo(SI);
SI.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
SI.wShowWindow := {SW_SHOWNORMAL} SW_HIDE;
SI.hStdOutput := NewStdOut;
SI.hStdError := NewStdOut;
SI.hStdInput := NewStdIn;
if not CreateProcess(nil, PChar(CommandLine), nil, nil, True, CREATE_NEW_CONSOLE, nil, nil, SI, PI) then
begin
CloseHandle(NewStdIn);
CloseHandle(NewStdOut);
CloseHandle(ReadStdOut);
CloseHandle(WriteStdIn);
SysUtils.RaiseLastWin32Error;
end; {end if}
Last := '';
BufferSize := MaxBufSize;
Buffer := AllocMem(BufferSize);
try
repeat
SysUtils.Win32Check(GetExitCodeProcess(PI.hProcess, ExitCode_));
PeekNamedPipe(ReadStdOut, Buffer, BufferSize, #Bread, #Avail, nil);
if (Bread <> 0) then
begin
if (BufferSize < Avail) then
begin
BufferSize := Avail;
ReallocMem(Buffer, BufferSize);
end; {end if}
FillChar(Buffer^, BufferSize, #0);
Windows.ReadFile(ReadStdOut, Buffer^, BufferSize, Bread, nil);
Str := Last;
I := 0;
while (I < Bread) do
begin
case Buffer^[I] of
#0: inc(I);
#7: begin
inc(I);
Windows.Beep(800, 50);
Str := Str + '^';
end;
#10:
begin
inc(I);
AStrings.Add(Str);
Str := '';
end; {end #10}
#13:
begin
inc(I);
if (I < Bread) and (Buffer^[I] = #10) then
inc(I);
AStrings.Add(Str);
Str := '';
end; {end #13}
else
begin
Str := Str + Buffer^[I];
inc(I);
end; {end else}
end; {end case}
end; {end while}
Last := Str;
end; {end if}
Sleep(1);
Application.ProcessMessages;
until (ExitCode_ <> STILL_ACTIVE);
if Last <> '' then
AStrings.Add(Last);
finally
FreeMem(Buffer);
end; {end try/finally}
CloseHandle(PI.hThread);
CloseHandle(PI.hProcess);
CloseHandle(NewStdIn);
CloseHandle(NewStdOut);
CloseHandle(ReadStdOut);
CloseHandle(WriteStdIn);
end; {end procedure}
Here is a PHP specific solution, the project allows PHP to obtain and interact dynamically with a real cmd terminal. Get it here: https://github.com/merlinthemagic/MTS
After downloading you would simply use the following code:
//if you prefer Powershell, replace 'cmd' with 'powershell'
$shellObj = \MTS\Factories::getDevices()->getLocalHost()->getShell('cmd');
$strCmd1 = 'some_app.exe -param "test"';
$return1 = $shellObj->exeCmd($strCmd1);
The return will give you the command return OR error from cmd, just as if you sat at the console.
Furthermore, you can issue any command you like against the $shellObj, the environment is maintained throughout the life of the PHP script. So instead of bundling commands in a script file, just issue them one by one using the exeCmd() method, that way you can also handle the return and any exceptions.
Related
I was hoping someone had already implemented this in golang as I am far from even good at cryptography. However in porting a project from php to golang I have run into an issue with porting the openssl_encrypt method found here. I have also dug into the source code a little with no avail.
Here is the method I have implemented in golang. which gives me the output
lvb7JwaI4OCYUrdJMm8Q9uDd9rIILnvbZKJb/ozFbwCmLKkxoJN5Zf/ODOJ/RGq5
Here is the output I need when using php.
lvb7JwaI4OCYUrdJMm8Q9uDd9rIILnvbZKJb/ozFbwDV98XaJjvzEjBQp7jc+2DH
And here is the function I used to generate it with php.
$data = "This is some text I want to encrypt";
$method = "aes-256-cbc";
$password = "This is a really long key and su";
$options = 0;
$iv = "MMMMMMMMMMMMMMMM";
echo openssl_encrypt($data, $method, $password, $options, $iv);
To me it looks like it is very close and I must be missing something obvious.
You were very close, but you had the padding wrong. According to this answer (and the PHP docs), PHP uses the default OpenSSL padding behavior, which is to use the required number of padding bytes as the padding byte value.
The only change I made was:
copy(plaintextblock[length:], bytes.Repeat([]byte{uint8(extendBlock)}, extendBlock))
You can see the full updated code here.
Others beat me to the answer while I was playing with it, but I have a "better" fixed version of your example code that also takes into account that padding is always required (at least to emulate what the php code does).
It also shows the openssl command line that you'd use to do the same thing, and if available runs it (of course the playground won't).
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"log"
"os/exec"
"strings"
)
func main() {
const input = "This is some text I want to encrypt"
fmt.Println(opensslCommand(input))
fmt.Println(aesCBCenctypt(input))
}
func aesCBCenctypt(input string) string {
// Of course real IVs should be from crypto/rand
iv := []byte("MMMMMMMMMMMMMMMM")
// And real keys should be from something like PBKDF2, RFC 2898.
// E.g. use golang.org/x/crypto/pbkdf2 to turn a
// "passphrase" into a key.
key := []byte("This is a really long key and su")
// Make sure the block size is a multiple of aes.BlockSize
// Pad to aes.BlockSize using the pad length as the padding
// byte. If we would otherwise need no padding we instead
// pad an entire extra block.
pad := (aes.BlockSize - len(input)%aes.BlockSize)
if pad == 0 {
pad = aes.BlockSize
}
data := make([]byte, len(input)+pad)
copy(data, input)
for i := len(input); i < len(input)+pad; i++ {
data[i] = byte(pad)
}
cb, err := aes.NewCipher(key)
if err != nil {
log.Fatalln("error NewCipher():", err)
}
mode := cipher.NewCBCEncrypter(cb, iv)
mode.CryptBlocks(data, data)
return base64.StdEncoding.EncodeToString(data)
}
// Just for comparison, don't do this for real!
func opensslCommand(input string) string {
iv := []byte("MMMMMMMMMMMMMMMM")
key := []byte("This is a really long key and su")
args := []string{"enc", "-aes-256-cbc", "-base64"}
// "-nosalt", "-nopad"
args = append(args, "-iv", fmt.Sprintf("%X", iv))
args = append(args, "-K", fmt.Sprintf("%X", key))
cmd := exec.Command("openssl", args...)
// Show how you could do this via the command line:
fmt.Println("Command:", strings.Join(cmd.Args, " "))
cmd.Stdin = strings.NewReader(input)
result, err := cmd.CombinedOutput()
if err != nil {
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
// openssl not available
return err.Error() // XXX
}
// some other error, show it and the (error?) output and die
fmt.Println("cmd error:", err)
log.Fatalf("result %q", result)
}
// Strip trailing '\n' and return it.
if n := len(result) - 1; result[n] == '\n' {
result = result[:n]
}
return string(result)
}
Playground
I'm making a Delphi application and I'm trying to encode with base64 encoding stream and I made two functions:
function EncodedInputParams(input: TStringStream): string;
var
output: TStringStream;
encoder: TIdEncoderMIME;
begin
input.Encoding.UTF8;
input.Position := 0;
output := TStringStream.Create;
try
encoder := TIdEncoderMIME.Create(nil);
encoder.Encode(input, output);
output.Seek(0, soFromBeginning);
Result := output.DataString;
finally
output.Free;
end;
end;
function SecondEncoding(input: TStringStream): string;
var
output: TStringStream;
midRes: string;
begin
input.Encoding.UTF8;
input.Position := 0;
output := TStringStream.Create;
try
EncodeStream(input, output);
output.Seek(0, soFromBeginning);
midRes := output.DataString;
midRes := StringReplace(midRes, #13#10, EmptyStr, [rfReplaceAll]);
result := midRes;
finally
output.Free;
end;
end;
Functions are working fine but the problem is that these solutions return different string than a string encoded same way in PHP: http://www.tools4noobs.com/online_php_functions/base64_encode/ .
Any ideas how to rewrite one of the function to return string like one written in PHP.
I have to upload a file with my delphi program and handle server side with php
This is my complete code :
unit Unit6;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Buttons,
IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP,
Vcl.ComCtrls, IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack,IdSSLOpenSSL;
type
TForm6 = class(TForm)
IdHTTP1: TIdHTTP;
BitBtn1: TBitBtn;
od1: TOpenDialog;
ProgressBar1: TProgressBar;
m1: TMemo;
IdIOHandlerStack1: TIdIOHandlerStack;
procedure BitBtn1Click(Sender: TObject);
procedure HTTPWorkBegin(Sender: TObject; AWorkMode: TWorkMode;const AWorkCountMax: Integer);
procedure IdHTTPWork(Sender: TObject; AWorkMode: TWorkMode;const AWorkCount: Integer);
procedure IdHTTPWorkEnd(Sender: TObject; AWorkMode: TWorkMode);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form6: TForm6;
implementation
{$R *.dfm}
procedure TForm6.HTTPWorkBegin(Sender: TObject; AWorkMode: TWorkMode;
const AWorkCountMax: Integer);
begin
if AWorkMode = wmRead then
begin
ProgressBar1.Max := AWorkCountMax;
ProgressBar1.Position := 0;
end;
end;
procedure TForm6.IdHTTPWork(Sender: TObject; AWorkMode: TWorkMode;
const AWorkCount: Integer);
begin
if AWorkMode=wmRead then
ProgressBar1.Position := AWorkCount;
end;
procedure TForm6.IdHTTPWorkEnd(Sender: TObject; AWorkMode: TWorkMode);
begin
ProgressBar1.Position := 0;
end;
procedure TForm6.BitBtn1Click(Sender: TObject);
var
Response:string;
LHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
if od1.Execute then
begin
// LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
// IdHTTP1.IOHandler:=LHandler;
Response := IdHTTP1.Post('http://localhost/delphi/index.php?asd', od1.FileName);
m1.Text := Response;
end;
end;
end.
The server side is in php:
test1
<?php
print_r($_FILES);
?>
also i changed it to:
test1
<?php
print_r($_POST);
?>
but at the all tests the delphi response is an empty array.
test1
Array
(
)
Which part of my code is the problem?!
You are passing a filename to TIdHTTP.Post(). That posts the raw content of the file as-is. That will not populate PHP's $_FILES array (used for multipart/form-data posts) or $_POST array (used for application/x-www-form-urlencoded posts).
Add the file to a TIdMultipartFormDataStream and post that instead. It will send a multipart/form-data post that should populate the $_FILES array, eg:
uses
..., IdMultipartFormData;
procedure TForm6.BitBtn1Click(Sender: TObject);
var
//LHandler: TIdSSLIOHandlerSocketOpenSSL;
PostData: TIdMultipartFormDataStream;
begin
if od1.Execute then
begin
//LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
//IdHTTP1.IOHandler := LHandler;
PostData := TIdMultipartFormDataStream.Create;
try
PostData.AddFile('file', od1.FileName);
m1.Text := IdHTTP1.Post('http://localhost/delphi/index.php?asd', PostData);
finally
PostData.Free;
end;
end;
end;
I need parse PHP extension parameters using lazarus/freepascal, but I do not know how to define the C function zend_parse_parameters in lazarus as:
int zend_parse_parameters ( int num_args TSRMLS_DC, char* type_spec, ... )
Can anybody help me?
Probably something like
uses ctypes;
function zend_parse_parameters(num_args:cint;type_spec:pchar):cint;cdecl; varargs;
but I don't know what to do with the macro (I assume it is merely a helper macro that links together the variable part and the num_args).
here is my test code of lazarus under CENTOS6.3:
procedure encryptext (ht : integer; return_value : pzval; this_ptr : pzval;
return_value_used : integer; TSRMLS_DC : pointer); cdecl;
var
getmyparameters: function(argu_num:integer;
type_apec:pansichar;Args : Array of const):integer;cdecl;
a:integer;
rtnstr:string;
begin
if (PHPLib < 1) then exit;
getmyparameters := GetProcAddress(PHPLib, 'zend_parse_parameters');
if (#getmyparameters = nil) then
begin
raise EPHP4DelphiException.Create('zend_parse_parameters');
exit;
end;
if ht < 1 then
begin
zend_wrong_param_count(TSRMLS_DC);
Exit;
end;
a := 1;
if (getmyparameters(ht,pansichar('s'),[pansichar(rtnstr),#a]) <> SUCCESS ) then exit;
ZVAL_STRING(return_value,pansichar(rtnstr),true);
end;
php code of /var/www/html/a.php:
<?php
echo encryptext('hello');
?>
the error message is:
PHP Warning: encryptext() expects exactly 0 parameters, 1 given in /var/www/html/a.php on line 2
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;
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;