Skip to content

Instantly share code, notes, and snippets.

@sharpjs
Created September 17, 2013 15:08
Show Gist options
  • Save sharpjs/6595630 to your computer and use it in GitHub Desktop.
Save sharpjs/6595630 to your computer and use it in GitHub Desktop.
Convert objects to/from encrypted URL tokens.
using System.IO;
namespace SharpJS.Web
{
public interface IUrlToken
{
void WriteTo (BinaryWriter writer);
void ReadFrom (BinaryReader reader);
}
}
using System;
using System.Configuration;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace SharpJS.Web
{
// TODO: Make encryption key configurable
public static class UrlToken
{
public static string Encrypt(IUrlToken obj)
{
using (var crypto = CryptoFactory.CreateAlgorithm())
using (var memory = new MemoryStream(crypto.BlockSize * 2))
using (var stream = new CryptoStream(memory, crypto.CreateEncryptor(), CryptoStreamMode.Write))
using (var writer = new BinaryWriter(stream, Utf8))
{
obj.WriteTo(writer);
stream.FlushFinalBlock();
return HttpServerUtility.UrlTokenEncode(memory.ToArray());
}
}
public static bool TryDecrypt(string text, IUrlToken obj)
{
byte[] buffer;
return !string.IsNullOrWhiteSpace(text)
&& TryDecode(text, out buffer)
&& TryDecryptCore(buffer, obj);
}
private static bool TryDecode(string text, out byte[] buffer)
{
// HACK: UrlTokenDecode's edge case behavior is not documented.
// As of .NET 4.0 it will either throw OR return null on bad input.
try
{
buffer = HttpServerUtility.UrlTokenDecode(text);
return buffer != null;
}
catch (FormatException)
{
buffer = null;
return false;
}
}
private static bool TryDecryptCore(byte[] buffer, IUrlToken obj)
{
try
{
using (var crypto = CryptoFactory.CreateAlgorithm())
using (var memory = new MemoryStream(buffer, writable: false))
using (var stream = new CryptoStream(memory, crypto.CreateDecryptor(), CryptoStreamMode.Read))
using (var reader = new BinaryReader(stream, Utf8))
obj.ReadFrom(reader);
}
catch (ArgumentNullException e)
{
// HACK: Bug in CryptoStream sometimes throws this exception instead of
// CryptographicException when input data is invalid.
// http://connect.microsoft.com/visualstudio/feedback/details/759833/cryptostream
//
if (e.ParamName == "inputBuffer")
return false;
throw;
}
catch (CryptographicException)
{
// Invalid data being decrypted
return false;
}
catch (DecoderFallbackException)
{
// Invalid UTF8
return false;
}
catch (IOException)
{
// Somethign else IO-related went wrong
return false;
}
return true;
}
private sealed class AesAlgorithmFactory
{
private static readonly byte[] Salt =
{
0xCF, 0x83, 0x19, 0xF8, 0x10, 0x27, 0x06, 0x38,
0x5C, 0x40, 0x1D, 0x72, 0xF0, 0xA9, 0x7C, 0x64
};
public const int
DefaultKeySize = 128,
StandardBlockSize = 128;
private readonly int keySize;
private readonly byte[] key;
private readonly byte[] iv;
public AesAlgorithmFactory(int keySize = DefaultKeySize)
{
var secret = ConfigurationManager
.AppSettings["UrlTokenSecret"] ?? string.Empty;
using (var bytes = new Rfc2898DeriveBytes(secret, Salt))
{
this.keySize = keySize;
this.key = bytes.GetBytes(keySize / 8);
this.iv = bytes.GetBytes(StandardBlockSize / 8);
}
}
public SymmetricAlgorithm CreateAlgorithm()
{
var algorithm = new AesManaged();
try
{
algorithm.KeySize = keySize;
algorithm.Key = key;
algorithm.IV = iv;
return algorithm;
}
catch
{
algorithm.Dispose();
throw;
}
}
}
private static readonly AesAlgorithmFactory
CryptoFactory = new AesAlgorithmFactory();
private static readonly Encoding Utf8 = new UTF8Encoding
(
encoderShouldEmitUTF8Identifier: false,
throwOnInvalidBytes: true
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment