Wolfgang Bures
Wolfgang Bures

Reputation: 717

Base64 decode fails with "No mapping for the Unicode character exists in the target multi-byte code page."

I am trying to create the signature string needed for Azure Storage Tables as described here:

https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key#encoding-the-signature

The Access key is "p1qPD2lBiMlDtl/Vb/T/Xt5ysd/ZXNg5AGrwJvtrUbyqiAQxGabWkR9NmKWb8Jhd6ZIyBd9HA3kCRHr6eveKaA=="

The failing code is:

property AccessKey: string read FAccessKey write SetAccessKey;

function TAzureStorageAPI.GetAuthHeader(StringToSign: UTF8String): UTF8String;
var
  AccessKey64: string;
begin
  AccessKey64 := URLEncoding.Base64.Decode(AccessKey);
  Result := URLEncoding.Base64.Encode(CalculateHMACSHA256(StringtoSign,AccessKey64));
end;

What can I do to get this working?

I have checked that AccessKey decodes on https://www.base64decode.org/ and it converts to strange data, but it is what I am supposed to do.... :-/

Upvotes: 1

Views: 1166

Answers (1)

AmigoJack
AmigoJack

Reputation: 6109

It looks like CalculateHMACSHA256() comes from blindly copying it and just fiddling everything to make the compiler happy. But there are still logical flaws all over. Without testing I'd write it this way:

function GetAuthHeader
( StringToSign: UTF8String  // Expecting an UTF-8 encoded text
; AzureAccountKey: String  // Base64 is ASCII, so text encoding is almost irrelevant
): String;  // Result is ASCII, too, not UTF-8
var
  HMAC: TIdHMACSHA256;
begin
  HMAC:= TIdHMACSHA256.Create;
  try
    // Your key must be treated as pure binary - don't confuse it with
    // a human-readable text-like password. This corresponds to the part
    // "Base64.decode(<your_azure_storage_account_shared_key>)"
    HMAC.Key:= URLEncoding.Base64.DecodeStringToBytes( AzureAccessKey );

    result:= URLEncoding.Base64.EncodeBytesToString  // The part "Signature=Base64(...)"
    ( HMAC.HashValue  // The part "HMAC-SHA256(...,...)"
      ( IndyTextEncoding_UTF8.GetBytes( StringToSign )  // The part "UTF8(StringToSign)"
      )
    );
  finally
    HMAC.Free;
  end;
end;

No guarantee as I have no chance to compile it myself.

Using ToHex() anywhere makes no sense if you encode it all in Base64 anyway. And stuffing everything into UTF8String makes no sense either then. Hashing always implies working with bytes, not text - never mix up binary with String.

Upvotes: 2

Related Questions