0

I am writing a TCP socket server application to accept data from an Angular application. Within the server application I want to return a response header to a web-socket request, however in generating the base64 of an hash I get a value that is long (and incorrect), and therefore the handshaking fails.

I am using the TNetCoding class in the System.NetEncoding unit to do the encoding. An example is found at https://flixengineering.com/archives/270.

I'm getting something like:

YWRiYzRlYmJiMDkyZmM2MzNjMGJjMGZjNGY0YjQwOTllZjVhNWMxMw==

procedure TServerForm.SendHeader(key: string);
var
  hash, ret, encod: string;
const
  magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
begin
  hash := trim(key) + magic;
  ret := GenStrHashSHA1(hash);
  encod := TNetEncoding.Base64.Encode(ret);
  CliSocket.SendStr('HTTP/1.1 101 Switching Protocols' + #13#10 +
                   'Upgrade: websocket' + #13#10 +
                   'Connection: Upgrade' + #13#10 +
                   'Sec-WebSocket-Accept: ' + encod + #13#10 +  #13#10);
  Memo2.Lines.Add('Header was sent');
end;

/* Hash Function */
function TServerForm.GenStrHashSHA1(Str: String): String;
var
  HashSHA: THashSHA1;
begin
  HashSHA := THashSHA1.Create;
  HashSHA.GetHashString(Str);
  result := HashSHA.GetHashString(Str);
end;

For Example:

Incoming request:

GET / HTTP/1.1
Host: localhost:12345
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:4200
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Sec-WebSocket-Key: ky8at6EtBZLocDhJU7hMnw==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

Hash generated:

c289980eb715b3e19107152dc21f075c9f7bf539

Base64 string generated:

YzI4OTk4MGViNzE1YjNlMTkxMDcxNTJkYzIxZjA3NWM5ZjdiZjUzOQ==

Update: Here is another example:

WebSocket key received:

Nk1L3oS2Q7LHdoGP2Uyn7Q==

Decimal value displayed in debugging:

image

String generated:

F68FCEE74F044B57E9047395B975A31A6ABEBDD2

Tom Brunberg
  • 20,312
  • 8
  • 37
  • 54
Hugo
  • 11
  • 4
  • Can you provide a [mcve] that includes the input hash, the code to generate the base64, the generated base64, and what you believe is the correct base64. – David Heffernan May 09 '19 at 13:52
  • Why are you calling `HashSHA.GetHashString(Str)` twice? – Remy Lebeau May 09 '19 at 15:16
  • The length of the base64 is correct. The result of the SHA1 hash is 20 bytes, per the [WebSocket protocol spec](https://tools.ietf.org/html/rfc6455), and the base64 you showed decodes to 20 bytes. So that is not a problem. If you are getting the wrong hash result then you are hashing the wrong data to begin with. Make sure that the `key` you hash exactly matches the client's `Sec-WebSocket-Key` as-is (don't decode it, don't trim it, etc) – Remy Lebeau May 09 '19 at 15:22
  • The additional call to the hash function is an oversight. – Hugo May 09 '19 at 15:50
  • The resulting base64 is YWRiYzRlYmJiMDkyZmM2MzNjMGJjMGZjNGY0YjQwOTllZjVhNWMxMw== I am looking at the information found at https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers – Hugo May 09 '19 at 15:52
  • I removed the additional call to the hash and removed the trim. I am not decoding the Sec-WebSocket-Key. The problem persist. Examples of base64 Sec-WebSocket-Accept values are much shorter. – Hugo May 09 '19 at 16:14
  • Code to generate hash (SHA1) taken from http://www.queryadmin.com/1774/delphi-10-berlin-system-hash-md5-sha1-sha2-hash/ – Hugo May 09 '19 at 16:54
  • @Hugo you are base64 encoding the wrong data. See the answer I just posted. – Remy Lebeau May 09 '19 at 21:40

1 Answers1

0

The correct SHA1 hash for the concatenated string

ky8at6EtBZLocDhJU7hMnw==258EAFA5-E914-47DA-95CA-C5AB0DC85B11

is this (verified with multiple online SHA1 hash sites):

51D265858FE3CF00E77A63FBC2C2327D89579B07

Not this, like you showed:

c289980eb715b3e19107152dc21f075c9f7bf539

So, your WebSocket handshake fails due to your hash being wrong.

Also, because THashSHA1.GetHashString() returns a hex encoded string, which is what you are base64 encoding, but you need to base64 encode the raw hash bytes, not a hex string representation of those bytes.

Try this:

procedure TServerForm.SendHeader(key: string);
var
  encod, ret: TBytes;
const
  magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
begin
  ret := THashSHA1.GetHashBytes(key + magic);
  encod := TNetEncoding.Base64.Encode(ret);
  CliSocket.SendStr('HTTP/1.1 101 Switching Protocols' + #13#10 +
                    'Upgrade: websocket' + #13#10 +
                    'Connection: Upgrade' + #13#10 +
                    'Sec-WebSocket-Accept: ' + TEncoding.ASCII.GetString(encod) + #13#10 +
                    #13#10);
  Memo2.Lines.Add('Header was sent');
end;

The generated base64 should be:

UdJlhY/jzwDnemP7wsIyfYlXmwc=

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks for the help. I tried the code and I noticed that the base64 value is much shorter. The handshaking failed however.with the same message: - "Error during WebSocket handshake: net::ERR_INVALID_HTTP_RESPONSE". So I guess the problem is not just the base64. – Hugo May 10 '19 at 01:36
  • @Hugo I don't see anything else wrong with your HTTP response. Did you verify the correct hash is being computed after changing the code to work on bytes instead of a hex string? I have added the base64 that you should be getting. Also, just curious, is the request coming in over HTTPS? If so, is your `CliSocket` encrypting the response correctly? – Remy Lebeau May 10 '19 at 03:41
  • The request, I believe, is coming from a HTTP request. I'm using websocket as in "ws://localhost:12345" from within the Angular application. I am not sure how to verify the computed hash code. Looking at the hash during debugging I see: 124, 430, 449, ..........,146. How do I verify that? – Hugo May 10 '19 at 11:47
  • To visually display the hash, I use the method, TEncoding.ASCII.GetString(ret). But I get the result - 'd>w ]/+GijZE7V8RB'. How do I verify that? – Hugo May 10 '19 at 12:21
  • @Hugo where are you seeing those decimal values? You can't get values like 430 and 449 from individual bytes, the highest value you can get is 255. ASCII chars `'d>w ... B'` are decimal `100 62 119 ... 66`, hex `64 3E 77 42` – Remy Lebeau May 10 '19 at 14:46
  • Sorry, those were not the actual values. I set breakpoints in the application. During debugging, I'm able to see the result of the hash at that breakpont. The values I saw were decimal values. I was looking for a way to display the actual values. – Hugo May 10 '19 at 15:43
  • Those ARE the actual values, just in decimal instead of hex. But I would be seriously worried if a debugger were displaying values that are outside the allowed range of a given data type. `ret` is an array of bytes, and a byte CANNOT have a value outside of `0..255`, even if it were being extended to an `Integer`. I think maybe you are not using the debugger correctly. Can you provide a screenshot of what you see, including the actual value of `key`? – Remy Lebeau May 10 '19 at 15:52
  • See screenshot in post. Corresponding key is EMgh7gvdEHrFGqzNYVEOig== – Hugo May 10 '19 at 23:09
  • Screenshot was disallowed by peers. I believe my solution may be found here: https://stackoverflow.com/questions/4929650/sha1-hashing-in-delphi-xe/4941040#4941040 – Hugo May 12 '19 at 00:37
  • @Hugo there is something seriously wrong with your hashes. The correct SHA1 hash for key `EMgh7gvdEHrFGqzNYVEOig==` is `1f7e8706ade41117aa7a2edb4da9c45e14337a99`. The correct SHA1 hash for key `Nk1L3oS2Q7LHdoGP2Uyn7Q==` is `7b17df214f6b1cd16c8b69254d0d4db861c54781`. You need to find another SHA1 implementation, because the one you are using is not working. – Remy Lebeau May 12 '19 at 04:25
  • I found the problem. I changed the hash function as you suggest. Also, an the key had a hidden character at the end; Thanks Remy. – Hugo May 13 '19 at 14:33