LockBox 3 encrypt RSA with public certyficate

1k Views Asked by At

In Delphi 10.4 I am trying to encrypt string with RSA, using public key from certificate (*.cer). The problem is that, I do not know, how to pass this certificate to RSA function. I have feelings, that LockBox 3 have his own format to store public and private keys in a file.

I had wrote like this:

type
  TGen = class(TObject)
    private
      keyRSA: TBytes;
      fileKey: TBytes;
      cryptoLib: TCryptographicLibrary;

      procedure readKeyWithoutHeader();

    public
      constructor Create();
      destructor Destroy(); override;

      function getRSA(const inputS: string): string;
  end;

constructor TGen.Create();
var
  path: string;
  tmp: TFileStream;
begin
  inherited;

  cryptoLib := TCryptographicLibrary.Create(nil);
  
  tmp := TFileStream.Create(path, fmOpenRead);
  tmp.Position := 0;
  SetLength(fileKey, tmp.Size);
  plik.Read(fileKey, tmp.Size);
  tmp.Free();

  readKeyWithoutHeader();
end;

function TGen.getRSA(const inputS: string): string;
var
  key: TSymetricKey;
  rsa: TCodec;
  t: TStream;
begin
  try
    t := TMemoryStream.Create();
    Base64_to_stream(keyRSA, t);
    t.Position := 0;

    rsa := TCodec.Create(nil);
    rsa.Reset();
    rsa.CryptoLibrary := cryptoLib;
    rsa.StreamCipherId := uTPLb_Constants.RSA_ProgId;
    //rsa.ChainModeId := uTPLb_Constants.ECB_ProgId;
    //rsa.AsymetricKeySizeInBits := 2048;


    key := rsa.Asymetric_Engine.CreateFromStream(t, [partPublic]);
    rsa.InitFromKey(key);

    rsa.EncryptString(inputS, Result, TEncoding.UTF8);

  finally
    rsa.Burn();
    FreeAndNil(rsa);
    
    FreeAndNil(t);
  end;
end;

procedure TGen.readKeyWithoutHeader();
    var
  pozStart: integer;
  pozKoniec: integer;
  i: integer;
  poz: integer;
begin
  pozStart := 0;
  pozKoniec := 0;
  SetLength(kluczRSA, Length(plikKlucza));

  //znajdź koniec --- begin * ---
  //wyszukaj pierwszej #13#10, ale nowa linia może mieć tylko #13 lub #10
  for i := 0 to Length(plikKlucza) - 1 do
  begin
    if plikKlucza[i] in [13, 10] then pozStart := i
    else if pozStart > 0 then break;
  end;

  //teraz znajdź początek --- end * ----
  //przygotwany klucz może mieć jedną dodatkową linię, inne pliki obecnie nie są wspierane
  for i := Length(plikKlucza) - 4 downto 0 do
  begin
    if plikKlucza[i] in [13, 10] then pozKoniec := i
    else if pozKoniec > 0 then break;         
  end;

  if (pozStart = 0) or (pozKoniec = 0) then
    raise Exception.Create('Błędny plik z kluczem - nie zawiera nagłówków');

  if pozStart >= pozKoniec then
    raise Exception.Create('Błąd przy pobieraniu wartości klucza - nie rozpoznano nagłówków');

  //teraz usuń wszystkie nowe linie, to co jest zakodowane w base64 między nagłówkami ma zostać
  poz := 0;
  for i := pozStart to pozKoniec do
  begin
    if not (plikKlucza[i] in [13, 10]) then
    begin
      kluczRSA[poz] := plikKlucza[i];
      Inc(poz);
    end;
  end;

  SetLength(kluczRSA, poz);

end;

But I getting error: Stream read error.

I am used openssl to extract public key from public certificate:

openssl x509 -pubkey -noout -in cert.cer  > pubkey.pem

And output is:

-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr5qMxLWtgkId2oRUfnPf 6MX+UouBQKOzyfG0J9LW9yya8Nr+ilPTSPp+hSBL/TD1ijUZ2RClyegnrojOKHS7 kp1ZFDQJwmKSW660NKeLbyu2fbcJFBuDmSVK8XwRsUaIpf4eixqx5wAZg8q64kJ9 R9e07WPqrC2+8p2F/7zlKsZ263CWZ/xE0M6I4RiKSA24iaiGVrppnIrX1oX2v/dq UNaQL3uIgH1WWtf4apnDA7MVei2Iz2NjFzLJ569wxzO92XBUrcEkqA7Xx0or6xij h0oFKxsygNqHzK3qf56McRi/xy1VFrGQsiZL1u4+cdIAu5/tWaecLFl0UOBBbYxz 9QIDAQAB -----END PUBLIC KEY-----

Because this certificate is public, and widely accessed from website, I paste here to show example:

-----BEGIN CERTIFICATE----- MIIFGTCCBAGgAwIBAgITYwABPJqEcB3zrmJ2agABAAE8mjANBgkqhkiG9w0BAQwF ADBdMRIwEAYKCZImiZPyLGQBGRYCcGwxEzARBgoJkiaJk/IsZAEZFgNnb3YxEjAQ BgoJkiaJk/IsZAEZFgJtZjEeMBwGA1UEAxMVUmVzb3J0IEZpbmFuc293IEkxIENB MB4XDTIwMDcyMzExMjgyM1oXDTIyMDcyMzExMjgyM1owgZExCzAJBgNVBAYTAlBM MRQwEgYDVQQIEwttYXpvd2llY2tpZTERMA8GA1UEBxMIV2Fyc3phd2ExHzAdBgNV BAoMFk1pbmlzdGVyc3R3byBGaW5hbnPDs3cxHDAaBgNVBAsTE0FwbGlrYWNqZSBL cnl0eWN6bmUxGjAYBgNVBAMTEWV0dy10c3QubWYuZ292LnBsMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr5qMxLWtgkId2oRUfnPf6MX+UouBQKOzyfG0 J9LW9yya8Nr+ilPTSPp+hSBL/TD1ijUZ2RClyegnrojOKHS7kp1ZFDQJwmKSW660 NKeLbyu2fbcJFBuDmSVK8XwRsUaIpf4eixqx5wAZg8q64kJ9R9e07WPqrC2+8p2F /7zlKsZ263CWZ/xE0M6I4RiKSA24iaiGVrppnIrX1oX2v/dqUNaQL3uIgH1WWtf4 apnDA7MVei2Iz2NjFzLJ569wxzO92XBUrcEkqA7Xx0or6xijh0oFKxsygNqHzK3q f56McRi/xy1VFrGQsiZL1u4+cdIAu5/tWaecLFl0UOBBbYxz9QIDAQABo4IBmzCC AZcwDgYDVR0PAQH/BAQDAgWgMD4GCSsGAQQBgjcVBwQxMC8GJysGAQQBgjcVCIS3 mFWE7NgBhrmTDYO18X6DrtM2gUKEotdOhMuPDgIBZAIBDDAdBgNVHQ4EFgQUnFcp uSFJZYUBUkIplCvJERbNN98wHwYDVR0jBBgwFoAUfR+t1nzLSol4VWy6EPLtM9Yx us0wRgYDVR0fBD8wPTA7oDmgN4Y1aHR0cDovL3BraS5tZi5nb3YucGwvY2RwL3Jl c29ydF9maW5hbnNvd19pMV9jYSgxKS5jcmwwdQYIKwYBBQUHAQEEaTBnMD4GCCsG AQUFBzAChjJodHRwOi8vcGtpLm1mLmdvdi5wbC9haWEvcmVzb3J0X2ZpbmFuc293 X2kxX2NhLmNydDAlBggrBgEFBQcwAYYZaHR0cDovL3BraS5tZi5nb3YucGwvb2Nz cDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwJwYJKwYBBAGCNxUKBBow GDAKBggrBgEFBQcDATAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQwFAAOCAQEAHatd TuoQDgaydiNDZFPvbvnqO0wmD/sH54LkXnf49hXHcGQRjhBc7xsSDbKzxHEORJ08 wNWWJa0H8cbKCLytBq49FQnENYoi5sgMetM5XGqGSYHwFLTFyAf8A9af8rpqfAEY HwmL6iIjz6NmSD52i3owOaMCop4pyC+UU5CZDqBrYd10JY4bWRm5NPdjIga0+mDm hQYEkoW/AEQQam+VmyLsJFGtBl/kr5+EFIUFw8X2tFAubNTuxQsveLo91Q0lHtSt BMBJ7MYnJeFCGDeuPM65nJtCxexDfWfWSTZvGrdJSUooD5iFSZodCkReP7ZLiQmB dPB1oIhW3y/VUem3+A== -----END CERTIFICATE-----

The error "Error Read Stream" is in function:

function TRSAKeyPair.LoadHugeCardinal_IfNotAlready(
  StoreStream: TStream; var Number: IHugeCardinalWrap): boolean;
  // virtual method.
var
  L: cardinal;
  ValueStream: TMemoryStream;
begin
result := not assigned( Number);
if not result then exit; // Only load if we are not already loaded.
StoreStream.ReadBuffer( L, SizeOf( L));    // <-- L have very big value for example 882233221
ValueStream := TMemoryStream.Create;
try
ValueStream.Size := L;
if L > 0 then
  StoreStream.ReadBuffer( ValueStream.Memory^, L);   // <--- error throw
ValueStream.Position := 0;
Number := NewWrap( THugeCardinal.CreateFromStreamIn(
                   L*8, LittleEndien, ValueStream, FPool))
finally
ValueStream.Free
end;
if Number.isZero then
  Number := nil
end;
2

There are 2 best solutions below

1
On BEST ANSWER

I found better solution. Just resigned from TP LockBox 2 or 3 and I have downloaded wrapper from https://github.com/lminuti/Delphi-OpenSSL and I am using OpenSSL for encryption in RSA or AES.

function TGeneratorSzyfrow.zaszyfrujRSA(const wejscie: TBytes): string;
var
  rsa: TRSAUtil;
  certyfikat: TX509Cerificate;
  bufWejscie: TMemoryStream;
  bufWyjscie: TMemoryStream;
  bajty: TBytes;
begin
  rsa := TRSAUtil.Create();
  certyfikat := TX509Cerificate.Create();
  bufWejscie := TMemoryStream.Create();
  bufWyjscie := TMemoryStream.Create();

  try
    certyfikat.LoadFromFile(sciezkaDoPlikuRSA);
    rsa.PublicKey.LoadFromCertificate(certyfikat);

    bufWejscie.write(wejscie, Length(wejscie));
    bufWejscie.Position := 0;

    rsa.PublicEncrypt(bufWejscie, bufWyjscie, rpPKCS);

    setLength(bajty, bufWyjscie.Size);
    bufWyjscie.Position := 0;
    bufWyjscie.Read(bajty, bufWyjscie.Size);

    Result := kodujBase64(bajty);
  finally
    FreeAndNil(rsa);
    FreeAndNil(certyfikat);
    FreeAndNil(bufWejscie);
    FreeAndNil(bufWyjscie);
  end;

end;
5
On

Now this code tells me you have no clue what you're doing:

tmp := TStringList.Create();
tmp.LoadFromFile(path, TEncoding.UTF8);
tmp.Delete(0);
tmp.Delete(tmp.Count - 1);
cert := TEncoding.UTF8.GetBytes(stringReplace(tmp.Text, sLineBreak, '', [rfReplaceAll]));
  1. You want to read an ASCII text file. Should it countain UTF-8 then it cannot be valid at all. Using this as text encoding misses any point.
  2. You delete the first and the last line, because you expect them to be -----BEGIN something----- and -----END something----- without even checking. But those lines don't exist for fun: they clearly mark where data is supposed to start and where it is supposed to end, while before and after these markers lines other text may occur. One file may contain multiple keys and it's up to you to figure out which you want.
  3. Are you sure you removed all linebreaks? Have you actually checked the content of cert?
  4. You ignore that within an ASCII context binary data is most likely Base64 encoded. Doesn't it put questions marks over your head that everything almost only consists of latin letters and arabic numbers? And yet you treat them as bytes (or UTF-8)?
  5. Why not looking at LockBox's own example? It uses its own Base64_to_stream() on a constant whose content looks very familiar to the data of your public key. But instanciating a TStringStream might manipulate what was decoded into bytes again, because now you treat it as text again for reasons unknown.

Look at your text file and read it as such, separated by lines. From those lines you want, remove any whitespaces (i.e. linebreaks, spaces, etc.), since Base64 doesn't know those - the linebreaks are there to make it e-mail compatible. Look at what you got now - it must be one long String that must all be in ['A'.. 'Z', 'a'.. 'z', '0'.. '9', '+', '/'], nothing else. Now convert this Base64 into raw bytes, preferably with an instance of TMemoryStream.Create().

And debug your "Stream read error" by looking at which point in your code it happens and at which point in LockBox's code it is raised. Then try again, this time looking even closer how soon or at which point of accessing the Stream the exception occurs. And what the Stream at that position contains.