6

I was working on creating a method that would generate a JWT token. Part of the method reads a value from my web.config that services as the "secret" used to generate the hash used to create the signature for the JWT token.

<add key="MySecret" value="j39djak49H893hsk297353jG73gs72HJ3tdM37Vk397" />

Initially I tried using the following to convert the "secret" value to a byte array.

byte[] key = Convert.FromBase64String(ConfigurationManager.AppSettings["MySecret"]);

However, an exception was thrown when this line was reached ...

The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters.

So I looked into the OAuth code and so another method being used to change a base64 string into a byte array

byte[] key = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["MySecret"]);

This method worked without issue. To me it looks like they are doing the same thing. Changing a Base64 text value into an array of bytes. However, I must be missing something. Why does Convert.FromBase64String fail and TextEncodings.Base64Url.Decode work?

webworm
  • 10,587
  • 33
  • 120
  • 217

1 Answers1

14

I came across the same thing when I migrated our authentication service to .NET Core. I had a look at the source code for the libraries we used in our previous implementation, and the difference is actually in the name itself.

The TextEncodings class has two types of text encoders, Base64TextEncoder and Base64UrlEncoder. The latter one modifies the string slightly so the base64 string can be used in an url.

My understanding is that it is quite common to replace + and / with - and _. As a matter of fact we have been doing the same with our handshake tokens. Additionally the padding character(s) at the end can also be removed. This leaves us with the following implementation (this is from the source code):

public class Base64UrlTextEncoder : ITextEncoder
{
    public string Encode(byte[] data)
    {
        if (data == null)
        {
            throw new ArgumentNullException("data");
        }

        return Convert.ToBase64String(data).TrimEnd('=').Replace('+', '-').Replace('/', '_');
    }

    public byte[] Decode(string text)
    {
        if (text == null)
        {
            throw new ArgumentNullException("text");
        }

        return Convert.FromBase64String(Pad(text.Replace('-', '+').Replace('_', '/')));
    }

    private static string Pad(string text)
    {
        var padding = 3 - ((text.Length + 3) % 4);
        if (padding == 0)
        {
            return text;
        }
        return text + new string('=', padding);
    }
}
Community
  • 1
  • 1
Iris Classon
  • 5,752
  • 3
  • 33
  • 52
  • Big thanks! I was printing out keys to the console and they were _slightly_ different. The initial key was created using `AesManaged()`. I needed to share the key, so my end goal was a jwk. So I had to cast that key into a `SymmetricSecurityKey()` and then convert it to a jwk using `JsonWebKeyConverter.ConvertFromSecurityKey()`. Printing the AesManaged key and the jwk to console showed different keys. This explains why. Url safe encoding. Thank you! – Troy Witthoeft Oct 19 '19 at 02:04