AES encryption

Greetings,

I'm trying to perform an AES encryption based on the CryptoChatClient demo in Web Core:

unit CryLib;

interface

Uses SysUtils, Classes, WEBLib.WebSocketClient, JS, XData.Web.Client, WEBLib.Controls, Web, WEBLib.Crypto,WEBLib.REST, WEBLib.JSON;

Type
  TBtEncryptionClient = class
        EncryptedString:String;
        DecryptedString:String;
        aesGCM: TAESEncryption;
    procedure CryptoError(AError: string);
    procedure CryptoKeyCreated(Sender: TObject);
    procedure MessageEncrypted(ADataAsArrayBuffer: TJSArrayBuffer);
    procedure MessageDecrypted(AData: string);
    procedure DoCreate;
    procedure DoEncrypted(str:String);
    procedure DoDecrypted(str:String);

  end;

implementation

Const
  KEY_FIRST = '{"alg":"A128GCM","ext":true,"k":"CQ4H9zufO9lxBhecfx-aXQ","key_ops":["encrypt","decrypt"],"kty":"oct"}';

procedure TBtEncryptionClient.CryptoError(AError: string);
begin
{
  if ContainsText(AError, 'OperationError') then
  begin
    lst.BeginUpdate;
    lst.HTML.Add('A message has arrived. Decoding was unsuccessful with the selected key.<br>');
    lst.EndUpdate;
  end;
}
end;

procedure TBtEncryptionClient.CryptoKeyCreated(Sender: TObject);
begin
  aesGCM.ImportKey(KEY_FIRST);
end;

procedure TBtEncryptionClient.MessageEncrypted(ADataAsArrayBuffer: TJSArrayBuffer);
begin
  BtMes('MessageEncrypted');
  EncryptedString := ABToBinaryString(ADataAsArrayBuffer);
  BtMEs('hjahaha: ' +EncryptedString);
  //WebSocketClient1.Send(s);
end;

procedure TBtEncryptionClient.MessageDecrypted(AData: string);
begin
  DecryptedString := AData;
end;

procedure TBtEncryptionClient.DoCreate;
begin
  aesGCM := TAESEncryption.Create(aetGCM);//(Nil);
  aesGCM.OnKeyCreated := CryptoKeyCreated; //Change key to a predefined one when class is ready
  aesGCM.OnEncrypted := MessageEncrypted;
  aesGCM.OnDecryptedString := MessageDecrypted;
  aesGCM.OnError := CryptoError;
  aesGCM.ImportKey(KEY_FIRST);
end;

procedure TBtEncryptionClient.DoEncrypted(str:String);
begin
  aesGCM.Encrypt(str);
end;

procedure TBtEncryptionClient.DoDecrypted(str:String);
var
  s: String;
  ab: TJSArrayBuffer;
begin
  s := str;
  ab := BinaryStringToAB(s);
  aesGCM.Decrypt(ab, drtString);
end;

function NewSocketEnCrypt(str:String):String;
Var
  ss:TBtEncryptionClient;
Begin
  ss:=TBtEncryptionClient.Create;
  ss.DoCreate;
  ss.DoEncrypted(str);
  Result := ss.EncryptedString;
End;

procedure BtnClick(Sender: TObject);
begin
   NewSocketEnCrypt('ABCD');
end;

This results in an error: Uncaught SyntaxError: Unexpected end of JSON input.

What am I missing?

Kind regards.

Hi,

We could not reproduce your JSON error, but the Web Crypto API is asynchronous. Specifically the part below won't work, as the key is probably not imported at the time you try to encrypt your data.

ss.DoCreate;
ss.DoEncrypted(str);

You can use the OnKeyImported event to detect when your key is imported and then you can safely encode your message.

If you still have a problem with JSON then please send a small test project that can reproduce the issue for further investigation.

Hello,

Thank you for your answer. That indeed solved a part of the problem.

Now there is a problem regarding the interaction between the client and server. The client sends an AES encrypted request, and the server has to decrypt and then process the request.

This doesn't work. On the server-side I get the following error: the string is not a base64 string.

The pre-steps were as follows: I copied the Encrypt part of the AESEncrypt demo and put it in the client and copied the Decrypt part of the AESEncryptVcl demo into my server. That's it.

Instead of trying to decrypt the request on the server-side, I tried the same request with the same Secret Key on the client-side right after encrypting that particular request there. This gave me the correct result, no error.

Is it possible that for some reason the encrypt and decrypt of these two different platforms can't work together?

Kind regards.

We need exactly what you are doing (code, reproduce) to give you some more specific advice.

Hello,

Please see the files attached below.

We need to encrypt our data on the client-side before sending the data over to the server, in this case, using AES encryption. The client is a web application and the server is a VCL application.

When we encrypt on the web, we get weird characters. Trying to decrypt these characters using the TMS VCL Crypto Demo results in an error. A snapshot of this procedure is included in the zip provided below. The other image shows the AES settings that are used in the VCL Demo.

If you need more specific info that I didn't provide, please ask ahead. But, this demo project roughly sums it all up.

Kind regards.

AES_Demo.zip (223.2 KB)

Greetings kind sir, I'm very sorry to intrude like this, but is there any update regarding this matter?

I was puzzled like you and finally went the route depicted in the TMS AES demo. I extracted the relevant code and put it into the following 2 handy functions.

Keep in mind though, that the " Web Cryptography API" is provided by the browser, not by TMS, and that not all browsers and browser-versions support it. Some related comment is this:

Browser support for the Web Cryptography API varies, and even though all common browsers provide the API, there is no guarantee that a specific algorithm is supported in all browsers. If a web application requires a specific algorithm and is expected to work in browsers that might not support it, it might be necessary dynamically to switch to a JavaScript implementation (“polyfill”).
Taken from this IBM article:

In order for my code to work, you need to use a Unit named "CP.Func.AES.pas", which gets delivered with the TMS AES demo in "C:\Users\Public\Documents\tmssoftware\TMS WEB Core Demos\Basics\AES". For your convenience, you find it attached.

Here are my Encode/Decode functions:

Type EncDecThenProc = Reference to Procedure(Const Result : String; Failed : Boolean);

{ Async encrypt and decrypt. After enc/dec is completed, the function in AndThen
  will be called with the Result and an error flag in Failed. If Failed is True,
  then Result holds the error message. A call to Decrypt/Encrypt is async and therefore 
  always returns immediately. }

Procedure Decrypt(Const Key, Encrypted : String; AndThen : EncDecThenProc);
Begin
 If (not assigned(Key)) or (not assigned(Encrypted)) or
    (Key='') or (Encrypted='') then AndThen('Arg missing',True)
 Else
  Begin
   {$IfDef USECRYPTO}
   TAESFunc.Decrypt(Key, TBclUtils.DecodeBase64Url(Encrypted),
    Procedure(const AEncrypted: string)
     Begin
      AndThen(AEncrypted,False);
     End,
    Procedure(Const AError: string)
     Begin
      AndThen(AError,True);
     End);
   {$Else}
    AndThen(Encrypted,False);
   {$EndIf}
  End;
End;

{---------------------------------------}

Procedure Encrypt(Const Key, Decrypted : String; AndThen : EncDecThenProc);
Begin
 If (not assigned(Key)) or (not assigned(Decrypted)) or
    (Key='') or (Decrypted='') then AndThen('Arg missing',True)
 Else
  Begin
   {$IfDef USECRYPTO}
    TAESFunc.Encrypt(Key, Decrypted,
     Procedure(const ABytes: TBytes)
      Begin
       AndThen(TBclUtils.EncodeBase64Url(ABytes),False);
      End,
     Procedure(Const AError: string)
      Begin
       AndThen(AError,True);
      End);
   {$Else}
    AndThen(Decrypted,False);
   {$EndIf}
  End;
 End;

In case I already know that the destination browser does not provide the crypto API, I undefine "USECRYPTO", in which case your code will still run, but without encryption.

CP.Func.AES.pas.zip (1.5 KB)

Hope this is of any use to you.

Kind regards, Walter

Hello,

It looks like there's some confusion around how to properly use the secret key so I'd like to clear that up.

First of all, I see that you tried to modify the existing keys from the WebCrypto demo, but that is not going to work like that as those keys were generated with different settings and you can't just delete values from the key and make it shorter:

procedure TForm1.AESKeyExported(AJSON: string);
begin
  WebMemo1.Text := AJSON;
end;

procedure TForm1.AESKeyGenerated(Sender: TObject);
begin
  aesCBC.ExportKey(efJSON);
end;

procedure TForm1.ElectronFormCreate(Sender: TObject);
begin
  aesCBC := TAESEncryption.Create(aetCBC, akl128, True);
  aesCBC.OnKeyCreated := AESKeyGenerated;
  aesCBC.OnKeyExportedJSON := AESKeyExported;
end;

The code above will produce something like:

{"alg":"A128CBC","ext":true,"k":"tvece8xvegKJEIn_JWVqkA","key_ops":["encrypt","decrypt"],"kty":"oct"}

I suppose you will have a different key, or you already have your own keys. But sticking to the example above, what you are looking for is the value from k, so tvece8xvegKJEIn_JWVqkA.
This is a base64url encoded value. It might not be practical but first I had to convert this to a base64 string. For this I used the code snippet from Marco Cantù's blog:

function Base64URLToBase64(ABase64URL: string): string;
var
  strFixup: string;
begin
  strFixup := ABase64URL + StringOfChar ('=', (4 - Length(ABase64URL) mod 4) mod 4);
  strFixup := StringReplace (strFixup, '-', '+', [rfReplaceAll]);
  Result := StringReplace (strFixup, '_', '/', [rfReplaceAll]);
end;

The next step is to get the raw value from the base64 encoded string and pass that to TAESEncryption.Key in VCL to decode the encoded value.
In the example you sent you set the IV to user defined but I had to set that back to random, With everything combined I have the following in VCL:

procedure TForm5.Panel2Click(Sender: TObject);
var
  b64: TBase64Encoding;
  b: TBytes;
begin
  //...

  b64 := TBase64Encoding.Create(10, '');
  try
    b := b64.DecodeStringToBytes(Base64URLToBase64('tvece8xvegKJEIn_JWVqkA'));  //add your key as a string
    AES.Key := Conv.TBytesToString(b);
    //AES.Key := Edit1.Text;
    AES.PaddingMode := TPaddingMode.PKCS7;
    AES.IVMode := TIVMode.rand;
    AES.Unicode := yesUni;

    Memo1.Text := AES.Decrypt(Memo2.Text);
    Refresh();
    sleep(30);
    Memo1.Color := clWindow; 
  finally
    b64.Free;
  end;

And what to decode:

The VCL demo uses HEX encoded strings, you'll want to encode the ADataAsArrayBuffer: TJSArrayBuffer result first:

function TForm1.Hex(ABuffer: TJSArrayBuffer): string;
var
  dv: TJSDataView;
  I: Integer;
  value: LongWord;
begin
  Result := '';
  dv := TJSDataView.new(ABuffer);
  for I := 0 to (dv.byteLength div 4) - 1 do
  begin
    value := dv.getUint32(I * 4);
    Result := Result + IntToHex(value, 8);
  end;
end;

procedure TForm1.MessageEncrypted(ADataAsArrayBuffer: TJSArrayBuffer);
begin
  result.text := Hex(ADataAsArrayBuffer);
end;
1 Like