163

I want to achieve Base64 URL safe encoding in C#. In Java, we have the common Codec library which gives me an URL safe encoded string. How can I achieve the same using C#?

byte[] toEncodeAsBytes = System.Text.ASCIIEncoding.ASCII.GetBytes("StringToEncode");
string returnValue = System.Convert.ToBase64String(toEncodeAsBytes);

The above code converts it to Base64, but it pads ==. Is there is way to achieve URL safe encoding?

dakab
  • 5,379
  • 9
  • 43
  • 67
Vishvesh Phadnis
  • 2,448
  • 5
  • 19
  • 35

10 Answers10

250

It is common to simply swap alphabet for use in urls, so that no %-encoding is necessary; only 3 of the 65 characters are problematic - +, / and =. the most common replacements are - in place of + and _ in place of /. As for the padding: just remove it (the =); you can infer the amount of padding needed. At the other end: just reverse the process:

string returnValue = System.Convert.ToBase64String(toEncodeAsBytes)
        .TrimEnd(padding).Replace('+', '-').Replace('/', '_');

with:

static readonly char[] padding = { '=' };

and to reverse:

string incoming = returnValue
    .Replace('_', '/').Replace('-', '+');
switch(returnValue.Length % 4) {
    case 2: incoming += "=="; break;
    case 3: incoming += "="; break;
}
byte[] bytes = Convert.FromBase64String(incoming);
string originalText = Encoding.ASCII.GetString(bytes);

The interesting question, however, is: is this the same approach that the "common codec library" uses? It would certainly be a reasonable first thing to test - this is a pretty common approach.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    In Common Codec they are using [0-9a-zA-Z_-] Character for url safe mode. – Vishvesh Phadnis Oct 14 '14 at 08:37
  • this is also mentioned in the wiki page for Base64 under URL applications. http://en.wikipedia.org/wiki/Base64 – wonster Apr 16 '15 at 16:37
  • 2
    You also have this function 'http://stackoverflow.com/questions/1886686/c-sharp-byte-to-url-friendly-string' which does all the hard work for you. – toy4fun Jan 12 '17 at 15:54
  • Thanks a lot Marc; I was trying to decode a string created by Java's `encodeBase64URLSafeString` and in the end the issue was the missing padding on the end of the string that you solved with the `switch` statement! – Mike Perrenoud Jun 20 '17 at 08:16
  • Thanks for noting these characters. When interfacing with different languages some functions do not return same results when encoding with encodeBASE64URL. I am using PC Soft's WinDev v22 and it handles the problematic chars but the vendor I am working with what ever they are using does not... so you our HMAC_SHA_256 signatures where not matching and I found the issue in the encoding! – tcsgeek Apr 05 '18 at 16:04
  • Just for search-findability... this is a great solution for decoding those pesky Google Gmail API bodies. – DPH Jan 16 '19 at 13:57
  • 2
    Why do we not need: case 1: incoming += "==="; break; ? – alex da franca Feb 08 '19 at 14:53
  • 9
    @alexdafranca because it will never have a length mod 4 of 1. 3x8 bits become 4x6 bits (and each 6 bits is one of 64 characters in the chosen alphabet), 0x8 bits is encoded as 0x6 bits without padding, 1x8 bits is encoded as 2x6 bits with `==` padding, 2x8 is encoded as 3x6 with `=` padding, 3x8 is encoded as 4x6 without padding and then it is aligned so it repeats. Nothing is ever encoded to 1x6 bits, so you never need `===` padding. – George Helyar Jul 24 '19 at 15:14
148

You can use class Base64UrlEncoder from namespace Microsoft.IdentityModel.Tokens.

const string StringToEncode = "He=llo+Wo/rld";

var encodedStr = Base64UrlEncoder.Encode(StringToEncode);
var decodedStr = Base64UrlEncoder.Decode(encodedStr);

if (decodedStr == StringToEncode)
    Console.WriteLine("It works!");
else
    Console.WriteLine("Dangit!");

Microsoft.IdentityModel.Tokens is a NuGet package that has to be downloaded.

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
Jerome
  • 2,059
  • 1
  • 16
  • 19
50

Another option, if you are using ASP.NET Core, would be to use Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode.

If you are not using ASP.NET Core, the WebEncoders source is available under the Apache 2.0 License.

Kevinoid
  • 4,180
  • 40
  • 25
12

Based off the answers here with some performance improvements, we've published a very easy to use url-safe base64 implementation to NuGet with the source code available on GitHub (MIT licensed).

Usage is as easy as

var bytes = Encoding.UTF8.GetBytes("Foo");
var encoded = UrlBase64.Encode(bytes);
var decoded = UrlBase64.Decode(encoded);
Mahmoud Al-Qudsi
  • 28,357
  • 12
  • 85
  • 125
  • Amazing thank you. Out of interest why have you opted for `"string".Replace` for the `encode` method, but a loop with manual replaces for the `decode`? – ᴍᴀᴛᴛ ʙᴀᴋᴇʀ Sep 03 '18 at 09:10
  • @ᴍᴀᴛᴛʙᴀᴋᴇʀ I need to revisit it and run some benchmarks, but it's because we add to the latter so it's represented by a growable list of characters instead of an immutable string. – Mahmoud Al-Qudsi Sep 07 '18 at 00:22
  • 1
    Another class with return type = string instead : https://github.com/vndevpro/architecture-common/blob/master/GdNet.Common/Services/Base64SafeEncoder.cs – hazjack Dec 13 '18 at 08:05
9

To get an URL-safe base64-like encoding, but not "base64url" according to RFC4648, use System.Web.HttpServerUtility.UrlTokenEncode(bytes) to encode, and System.Web.HttpServerUtility.UrlTokenDecode(bytes) to decode.

Florian Winter
  • 4,750
  • 1
  • 44
  • 69
Karanvir Kang
  • 2,179
  • 19
  • 16
  • 8
    This doesn't provide a standards conform URL-safe Base64 encoding according to RFC4648. See also [this Q&A](http://stackoverflow.com/questions/26732145/c-base64url-according-to-rfc4648). **Use with caution.** – Artjom B. Oct 28 '15 at 11:43
2

Simplest solution: (with no padding)

private static string Base64UrlEncode(string input) {
    var inputBytes = System.Text.Encoding.UTF8.GetBytes(input);
    // Special "url-safe" base64 encode.
    return Convert.ToBase64String(inputBytes)
      .Replace('+', '-') // replace URL unsafe characters with safe ones
      .Replace('/', '_') // replace URL unsafe characters with safe ones
      .Replace("=", ""); // no padding
  }

Credit goes to: Tholle

Ashi
  • 806
  • 1
  • 11
  • 22
2
    public string Decode(string str)
    {
       byte[] decbuff = Convert.FromBase64String(str.Replace(",", "=").Replace("-", "+").Replace("/", "_"));
       return System.Text.Encoding.UTF8.GetString(decbuff);
    }
    
    public string Encode(string input)
    {
        byte[] encbuff = Encoding.UTF8.GetBytes(input ?? "");
        return Convert.ToBase64String(encbuff).Replace("=", ",").Replace("+", "-").Replace("_", "/");
    }

This is the way to do it to align with JavaScript!

Shiroy
  • 1,648
  • 1
  • 15
  • 22
  • Just based on the other answers, have you got that last `Replace` right? The other answers have `.Replace("/", "_")` – jasonscript Mar 08 '22 at 09:35
0

Here is another method to decode an url-safe base64 was encode by same way with Marc. I just don't get why 4-length%4 worked(it does).

As follows, only the origin's bit length are common multiple of 6 and 8, base64 do not append "=" to result.

1 2 3 4 5 6 7 8|1 2 3 4 5 6 7 8|1 2 3 4 5 6 7 8 
1 2 3 4 5 6|1 2 3 4 5 6|1 2 3 4 5 6|1 2 3 4 5 6
                "=="            "="

So we can do it conversely, if result's bit length can't divisible by 8, it has been appended:

base64String = base64String.Replace("-", "+").Replace("_", "/");
var base64 = Encoding.ASCII.GetBytes(base64String);
var padding = base64.Length * 3 % 4;//(base64.Length*6 % 8)/2
if (padding != 0)
{
    base64String = base64String.PadRight(base64String.Length + padding, '=');
}
return Convert.FromBase64String(base64String);
joyjy
  • 29
  • 4
0

Karanvir Kang's answer is a good one and I voted for it. However, it does leave an odd character on the end of the string (indicating the number of padding characters removed). Here is my solution.

var bytesToEncode = System.Text.Encoding.UTF8.GetBytes("StringToEncode"); 
var bytesEncodedPadded = HttpServerUtility.UrlTokenEncode(bytesToEncode);
var objectIdBase64 = bytesEncodedPadded.Substring(0, bytesEncodedPadded.Length - 1);
Joshcodes
  • 8,513
  • 5
  • 40
  • 47
-1

Using Microsoft cryptographic engine in UWP.

uint length = 32;

IBuffer buffer = CryptographicBuffer.GenerateRandom(length);
string base64Str = CryptographicBuffer.EncodeToBase64String(buffer)
                   // ensure url safe
                   .TrimEnd('=').Replace('+', '-').Replace('/', '_');

return base64Str;
visc
  • 4,794
  • 6
  • 32
  • 58