Hashing and Cryptography in WEB Core

Hello..

Are there any plans to bring the TMS Cryptography pack over to WEB Core?

I'm desperately trying to get hashing functionality into a WEB Core application, and I'm really hoping I don't have to re-invent the wheel.

There is no plan for this.
We have exposed the Web Crypto API though.
Did you check if there are useful methods there?

I'm looking for a simple SHA256 implementation that I could use primarily for text hashing. File hashing with SHA256 would also be great to use in combination with TWebFileUpload.

Unfortunately, there's nothing in the Web Crypto API that would be applicable to what I need.

We will investigate but as an immediate solution, you could perhaps call the JS function from:

Thank you for the link, but unfortunately it doesn't really help me because I don't have any experience with JavaScript. That's why I'm using TMS WEB Core, so that I don't need to worry about JavaScript.

The code you provided from GitHub looks relatively simple, but I have no idea how to implement it inside my TMS WEB application from Delphi.

It should be simple to use this from TMS WEB Core.

  1. include sha256.js in your project.html file

<script type="text/javascript" src="sha256.js"></script>

  1. in a Pascal method, write
function GetSha256(s: string): string;
var
   res: string;
begin
   asm
      res = sha256(s);
   end;
  Result := res;
end;

Bruno,

Thank you very much for showing an example for someone who doesn't know JavaScript.

I created a test project and followed your instructions, and I believe I'm very close to a solution, but maybe I missed something because the result is always "[object Promise]" instead of an actual hash value.

Please see the attached project.HashTest.zip (6.6 KB)

Hi Steve,

Hopefully the code below will get you started. You won't need the file/library that Bruno linked, the code should work in a new project right away.

Don't forget to add WEBLib.Crypto to the uses list.

procedure TForm1.WebButton2Click(Sender: TObject);
var
  arr: TJSUint8Array;
begin
  arr := GetMessageEncoding('text_to_be_hashed');
  window.crypto.subtle.digest('SHA-256', arr)._then(function (aHash: JSValue): JSValue
  begin
    //Here you can access the result as an ArrayBuffer.
    //We need to run it through Hex():
    WebLabel1.Caption := Hex(TJSArrayBuffer(aHash));
  end);
end;

//And Hex() can be implemented as:
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;

If you want to make sure that the browser supports the internally used TextEncoder API, you can check that via the IsTextEncoderSupported function, which is also available in the WEBLib.Crypto unit.

1 Like

Hi Tünde,

Thank you - the code you provided works perfectly!

I even tested it against the SHA256 test vectors and the hashed output is exactly what it should be. This will save me a tremendous amount of time.

I was beginning to think that the only option available was to begin a manual conversion of DCPCrypt (which is full of pointers) from its original source into a non-pointer version that would be compatible with JavaScript.

I have just one final question: how did the following line not produce a compiler error?

"window.crypto.subtle.digest('SHA-256', arr)._then(function (aHash: JSValue): JSValue"

This looks to me like JavaScript syntax, and yet I was still able to compile it without using an ASM block.

The compiler used here, pas2js, supports this. You can consider this an anonymous method.

The function call appears to be asynchronous. Is there a way to wait for the result?

var hash : String;

arr := GetMessageEncoding('test');
window.crypto.subtle.digest('SHA-256', arr)._then(function (aHash: JSValue): JSValue
begin
  //Here you can access the result as an ArrayBuffer.
  //We need to run it through Hex():
  hash := Hex(TJSArrayBuffer(aHash));
end);
//something like await??
ShowMessage(hash); //hash is empty

Yes, it can be awaited:

//Mark your function as async
[async]
procedure WebButton1Click(Sender: TObject);

...

//Implementation
procedure TForm1.WebButton1Click(Sender: TObject);
var
  arr: TJSUint8Array;
  buf: TJSArrayBuffer;
  hash: string;
begin
  arr := GetMessageEncoding('text_to_be_hashed');
  buf := await(TJSArrayBuffer, window.crypto.subtle.digest('SHA-256', arr));
  hash := Hex(buf);
  ShowMessage(hash);
end;
1 Like

If I wrap the await code in a function, e.g.:

function SHA256(const s: string): string;
var
  arr: TJSUint8Array;
  buf: TJSArrayBuffer;
begin
  arr := GetMessageEncoding(s);

  // -- window.crypto.subtle.digest returns a Promise.
  buf := await(TJSArrayBuffer, window.crypto.subtle.digest('SHA-256', arr));
  result := Hex(buf);
end;

then I get:

[Error] UnitMain.pas(93): Incompatible types: got "TJSPromise" expected "String"

I've trawled the pas2js docs but can't see what I'm getting wrong.

Thanks, Bob

Hi,

This comes down to how JavaScript behaves. When you mark a function as async the return value will be a promise. See the relevant MDN docs here.

So even though you set the return value to a string, it is still going to return a promise. With this out of the way, consider the following:

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  const result = await resolveAfter2Seconds();
  console.log(result);
}

console.log('hello');
asyncCall();
console.log('world');

==== output:
hello
world
resolved

Try to understand what the code above does. You can only await inside an async function and when you call an async function it will execute asynchronously from the context it is in.

Now with this in your mind, you can use SHA256 hashing but only in async functions (or as a promise and rely on the then method).

You could wrap the code above to return a promise but we already did that by introducing TWebSHAHash.

[async] 
procedure WebButton1Click(Sender: TObject);

...

procedure TForm1.WebButton1Click(Sender: TObject);
var
  sha: TWebSHAHash;
  s: string;
begin
  sha := TWebSHAHash.Create(ehSHA256);
  s := Await(string, sha.Hash('hello world'));
  console.log(s);
  sha.Free;
end;

You can maintain a single TWebSHAHash instance instead of creating one each time if that fits your needs better.

3 Likes

Thank you - "You can only await inside an async function and when you call an async function it will execute asynchronously from the context it is in" - this is what I suspected but wasn't sure about. Good explanation. I'll check out TWebSHAHash.

Cheers, Bob