3

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;
InnerWorld
  • 585
  • 7
  • 26
  • How is `cert` defined and initialized? Which units did you use? – AmigoJack Jun 30 '21 at 08:20
  • The file is a .cer extenstion. It conitain -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- I only use TMemoryStream.LoadFromFile('certificate.cer'). But I also try to extract public key: openssl x509 -pubkey -noout -in cert.cer > pubkey.pem and I have file with -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- loaded as TMemoryStream.LoadFromFile('pubkey.pem'). – InnerWorld Jun 30 '21 at 08:45
  • I have used conversion to -----BEGIN RSA PUBLIC KEY----- from command: openssl rsa -inform PEM -outform PEM -in pubkey.pem -out pubkey.rsa -pubin -pubout -RSAPublicKey_out but I got error: Invalid RSA Key – InnerWorld Jun 30 '21 at 09:01
  • Please edit your question to include additional code instead of using unformatted comments. LockBox surely expects binary keys, while your `.cer` file most likely has Base64 between its BEGIN/END text lines, so you either need to feed LockBox this way or decode Base64 into binary first. – AmigoJack Jun 30 '21 at 09:55
  • I edited my question, example added – InnerWorld Jun 30 '21 at 11:13
  • Try this: `openssl x509 -inform der -in public.der.cer -out cert.pem` – FredS Jun 30 '21 at 14:37
  • @FredS 15656:error:0D0680A8:asn1 encoding routines:asn1_check_tlen:wrong tag:crypto\asn1\tasn_dec.c:1149 - this cert is in PEM format – InnerWorld Jun 30 '21 at 15:47
  • You can use those error codes to see why you need a PEM generated from a DER file in Windows. I don't recall all the details but my notes stated that this is a know issue with PEM generated outside of Windows.. most of that info came via https://superuser.com/ – FredS Jun 30 '21 at 16:25
  • The compiled LB3 demo on my system fails with either format of PEM. Suggest you try to get this to work first using DER or binary format. CryptStringToBinaryA with CRYPT_STRING_ANY will convert either Base64 with or without header and DER (already binary) to binary for you. more notes: OpenSSL public keys are not ANS.1 encoded by default, Data not ANS.1 encoded Error: ASN1 bad tag value met – FredS Jun 30 '21 at 17:01
  • `CryptStringToBinaryA` converts the full certificate to binary without issue and `CryptImportPublicKeyInfoEx2` imports it just fine. The LB3 demo still can't load it or use it. – FredS Jul 03 '21 at 14:02

2 Answers2

0

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.

AmigoJack
  • 5,234
  • 1
  • 15
  • 31
  • With this points, you have right, but I am focused now on one certificate and I know, how it looks. I see, that problem is with extracted public key from certificate, because I have copies to const value my public key, and Publickey_1500 from example. Mine not works, but that from example, work. – InnerWorld Jun 30 '21 at 17:41
  • If you still cling to the imprecise phrase of "not works" and don't include relevant code in your question I'm done here. If the example code "works" then it should be trivial to read the data from a file into a variable instead of using the constant. Extend the example code and you'll most likely see that your problem might only be related to reading files. – AmigoJack Jun 30 '21 at 20:13
  • I have updated example. But I see, that read data from file, should be ok, because I pasted key from LockBox example and that key was readed. Problem is in TRSAKeyPair.LoadHugeCardinal_IfNotAlready - because program read very big number to L value. Propably I have wrong key data in file, extracted from certyficate. – InnerWorld Jun 30 '21 at 23:19
  • Loading 882233221 bytes = 841 MiB into memory **is** possible - isolate your problem into solving this first. Consider setting `$SetPEFlags` and using _FastMM4_, see answers to [How can I ... use 4gb of memory](https://stackoverflow.com/q/1849344/4299358) – AmigoJack Jul 01 '21 at 08:48
  • It's wrong value. I am reading RSA Key 2048 bit, before encoding my 32 bytes simetric key. There is no need to allocate so much memory for few bytes. I think, the problem is with padding/ asn1 encoding - the public keys (even self-generated) start with MIIB value, thiese from examples do not have that. – InnerWorld Jul 01 '21 at 09:15
0

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;
InnerWorld
  • 585
  • 7
  • 26
  • That class is really called `TX509Cerificate` in **OpenSSL.RSAUtils.pas**. Even other variables/parameters are misspelled the same way. Who knows which other mistakes wait in that code... – AmigoJack Jul 12 '21 at 16:09