Jak osiągnąć bezpieczne kodowanie adresu URL Base64 w C #?

105

Chcę osiągnąć bezpieczne kodowanie adresu URL Base64 w C #. W Javie mamy wspólną Codecbibliotekę, która daje mi bezpieczny zakodowany łańcuch. Jak mogę osiągnąć to samo za pomocą C #?

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

Powyższy kod konwertuje go na Base64, ale wypełnia ==. Czy istnieje sposób na zapewnienie bezpiecznego kodowania adresów URL?

Vishvesh Phadnis
źródło
Nie możesz po prostu użyć Url.Encodena sznurku BASE64?
W której klasie Url przestrzeni nazw jest obecna w języku C #?
Vishvesh Phadnis,
Spójrz: msdn.microsoft.com/en-us/library/… Musisz odwołać się do System.Webzestawu.
Konwertuje = na% 3D. Nie chcę tego.
Vishvesh Phadnis
3
Więc co masz na myśli url safe? %3Djest bezpieczny dla adresu URL.

Odpowiedzi:

182

Zwykle po prostu zamienia się alfabet na potrzeby adresów URL, dzięki czemu nie jest konieczne kodowanie%; tylko 3 z 65 znaków są problematyczne - +, /i =. Najczęstszym zamienniki są -na miejscu +i _na miejscu /. Co do wyściółki: wystarczy ją usunąć (the =); można wywnioskować ilość potrzebnej wyściółki. Z drugiej strony: po prostu odwróć proces:

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

z:

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

i odwrócić:

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);

Jednak interesujące pytanie brzmi: czy jest to to samo podejście, którego używa „biblioteka wspólnych kodeków”? Z pewnością rozsądnie byłoby najpierw przetestować - to dość powszechne podejście.

Marc Gravell
źródło
1
W Common Codec używają znaku [0-9a-zA-Z_-] dla trybu bezpiecznego adresu URL.
Vishvesh Phadnis
jest to również wspomniane na stronie wiki dla Base64 w aplikacjach URL. en.wikipedia.org/wiki/Base64
wonster
2
Masz również tę funkcję „ stackoverflow.com/questions/1886686/… ”, która wykonuje całą ciężką pracę za Ciebie.
toy4fun
1
Dlaczego nie potrzebujemy: przypadek 1: przychodzące + = "==="; przerwa; ?
alex da franca
5
@alexdafranca, ponieważ nigdy nie będzie miał długości mod 4 z 1. 3x8 bitów stanie się 4x6 bitów (a każde 6 bitów to jeden z 64 znaków w wybranym alfabecie), 0x8 bitów jest kodowane jako 0x6 bitów bez wypełnienia, 1x8 bitów jest kodowane jako 2x6 bitów z ==dopełnieniem, 2x8 jest kodowane jako 3x6 z =dopełnieniem, 3x8 jest kodowane jako 4x6 bez dopełnienia, a następnie jest wyrównane, aby się powtarzało. Nic nie jest nigdy kodowane do bitów 1x6, więc nigdy nie potrzebujesz ===dopełniania.
George Helyar
83

Możesz użyć klasy Base64UrlEncoderz przestrzeni nazw 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!");
Jerome
źródło
1
Jest to o wiele czystsze niż zaakceptowana odpowiedź. Jakieś wady?
taktak004
18
Tylko uwaga: Microsoft.IdentityModel.Tokens to pakiet NuGet, który należy pobrać.
Uber Schnoz
Zakładam, że nie jest to platforma wieloplatformowa. Czy tak jest?
Brandon S.
8

Opierając się na odpowiedziach tutaj z pewnymi ulepszeniami wydajności, opublikowaliśmy bardzo łatwą w użyciu implementację base64 z bezpiecznym adresem URL w NuGet z kodem źródłowym dostępnym w GitHub (na licencji MIT).

Użycie jest tak proste, jak

var bytes = Encoding.UTF8.GetBytes("Foo");
var encoded = UrlBase64.Encode(bytes);
var decoded = UrlBase64.Decode(encoded);
Mahmoud Al-Qudsi
źródło
Niesamowite dziękuję. W interesie, dlaczego zdecydowałeś się na "string".Replaceencodemetodę, ale pętla z ręcznym zastąpieniem decode?
ᴍᴀᴛᴛ ʙᴀᴋᴇʀ
@ ᴍᴀᴛᴛʙᴀᴋᴇʀ Muszę wrócić do tego i przeprowadzić testy porównawcze, ale to dlatego, że dodajemy do tego ostatniego, więc jest reprezentowany przez rosnącą listę znaków zamiast niezmiennego ciągu.
Mahmoud Al-Qudsi,
Inna klasa z return type = string zamiast: github.com/vndevpro/architecture-common/blob/master/…
hazjack,
8

Aby uzyskać bezpieczne kodowanie w formacie base64, ale nie „base64url” zgodnie z RFC4648, użyj System.Web.HttpServerUtility.UrlTokenEncode(bytes)do kodowania i System.Web.HttpServerUtility.UrlTokenDecode(bytes)dekodowania.

Karanvir Kang
źródło
6
Nie zapewnia to zgodnego ze standardami kodowania Base64 bezpiecznego dla adresów URL zgodnie z RFC4648. Zobacz także te pytania i odpowiedzi . Używaj ostrożnie.
Artjom B.
1

Najprostsze rozwiązanie: (bez wypełnienia)

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
  }

Kredyt trafia do: Tholle

Ashi
źródło
0

Oto inna metoda dekodowania bezpiecznego adresu URL base64, który został zakodowany w ten sam sposób z Markiem. Po prostu nie rozumiem, dlaczego4-length%4 zadziałało (działa).

Jak poniżej, tylko długość bitu początku jest wspólną wielokrotnością 6 i 8, base64 nie dodaje „=” do wyniku.

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
                "=="            "="

Możemy więc zrobić to na odwrót, jeśli długość bitu wyniku nie może być podzielna przez 8, została dodana:

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
źródło
0

Korzystanie z aparatu kryptograficznego firmy Microsoft na platformie UWP.

uint length = 32;

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

return base64Str;
wisk
źródło
0

Odpowiedź Karanvira Kanga jest dobra i głosowałem za nią. Jednak na końcu ciągu pozostawia nieparzysty znak (wskazujący liczbę usuniętych znaków wypełniających). Oto moje rozwiązanie.

var bytesToEncode = System.Text.Encoding.UTF8.GetBytes("StringToEncode"); 
var bytesEncodedPadded = HttpServerUtility.UrlTokenEncode(bytesToEncode);
var objectIdBase64 = bytesEncodedPadded.Substring(0, bytesEncodedPadded.Length - 1);
Joshcodes
źródło