Search code examples
c#delphidelphi-11-alexandria

How to convert C# code to Delphi to perform a SHA256 hash and then Base64Url encoding


I am integrating Xero accounting with my Delphi app. Xero have a VS C# example program, but I've never used C#.

The Xero docs say: The “code challenge” is created by performing a SHA256 hash on the code verifier and then Base64url encoding the hash

The C# code is:

private void btnGenerateLink_Click(object sender, EventArgs e)
{
    //construct the link that the end user will need to visit in order to authorize the app
    var clientId = txtBoxClientID.Text;
    var scopes = Uri.EscapeUriString(txtBoxScopes.Text);
    var redirectUri = txtBoxRedirectURI.Text;
    var state = txtBoxState.Text;

    //generate the code challenge based on the verifier
    string codeChallenge;
    using (var sha256 = SHA256.Create())
    {
        var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(txtBoxCodeVerifier.Text));
        codeChallenge = Convert.ToBase64String(challengeBytes)
            .TrimEnd('=')
            .Replace('+', '-')
            .Replace('/', '_');
    }
    
    var authLink = $"{AuthorisationUrl}?response_type=code&client_id={clientId}&redirect_uri={redirectUri}&scope={scopes}&state={state}&code_challenge={codeChallenge}&code_challenge_method=S256";
    txtBoxAuthLink.Text = authLink;
    btnAuthorise.Enabled = true;
}

How do I re-write this in Delphi 11? I have tried the following:

var
  inputKey, hash : string;
  b64Encoded : string;
  b64url : TBase64UrlEncoding;
begin
  //create the code challenge
  inputKey := eCodeVerifier.Text;

  hash := THashSHA2.GetHashString(inputKey, SHA256);
  b64url := TBase64UrlEncoding.Create;
  b64Encoded := b64url.Encode(hash);

Solution

  • Not an exact translation of the presented C# code, but hopefully answering your underlying question: here is a function that, given a PKCE code verifier, returns the SHA256 code challenge:

    uses
      System.SysUtils, System.Hash, System.NetEncoding;
    
    function GetCodeChallenge(const ACodeVerifier: string): string;
    var
      LSHA256: string;
      LSHA256Bytes: TBytes;
    begin
      LSHA256Bytes := THashSHA2.GetHashBytes(ACodeVerifier, THashSHA2.TSHA2Version.SHA256);
      LSHA256 := TNetEncoding.Base64.EncodeBytesToString(LSHA256Bytes);
      // The following three lines are what makes
      // a "Base64 encoding" into a "Base 64 URL encoding" - no more magic than that
      Result := StringReplace(LSHA256, '=', '', [rfReplaceAll]);
      Result := StringReplace(Result, '+', '-', [rfReplaceAll]);
      Result := StringReplace(Result, '/', '_', [rfReplaceAll]);
    end;
    

    Also worth mentioning, perhaps, is that a PKCE code verifier must be a random string of length min 43 and max 128.