Created
September 17, 2013 15:08
-
-
Save sharpjs/6595630 to your computer and use it in GitHub Desktop.
Convert objects to/from encrypted URL tokens.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.IO; | |
namespace SharpJS.Web | |
{ | |
public interface IUrlToken | |
{ | |
void WriteTo (BinaryWriter writer); | |
void ReadFrom (BinaryReader reader); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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