module RUP.Crypto open System open System.Net open System.IO open System.Security.Cryptography open System.Text open System.Web let keySize = 128 // [bit]. type Key = string let randBytes (nb : int) : byte array = let result = Array.zeroCreate nb use generator = new RNGCryptoServiceProvider () generator.GetBytes result result let generateKey () : Key = let bytes = randBytes (keySize / 8) Convert.ToBase64String bytes |> WebUtility.UrlEncode // TODO: return a Result let encrypt (key : Key) (name : string) : string = let keyAsBytes = WebUtility.UrlDecode key |> Convert.FromBase64String let iv = randBytes (keySize / 8) use aes = new AesCryptoServiceProvider (KeySize = keySize) // Default mode is CBC. use stream = new MemoryStream () use cryptoStream = new CryptoStream (stream, aes.CreateEncryptor (keyAsBytes, iv), CryptoStreamMode.Write) let nameAsBytes = Encoding.UTF8.GetBytes name use sha = SHA256.Create () let hash = sha.ComputeHash nameAsBytes cryptoStream.Write (nameAsBytes, 0, nameAsBytes.Length) cryptoStream.FlushFinalBlock () stream.Position <- 0L let result = Array.zeroCreate (iv.Length + hash.Length + int stream.Length) Array.blit iv 0 result 0 iv.Length Array.blit hash 0 result iv.Length hash.Length stream.Read (result, iv.Length + hash.Length, int stream.Length) |> ignore "1" + Convert.ToBase64String result |> WebUtility.UrlEncode // TODO: return a Result let decrypt (key : Key) (cipher : string) (urlDecode : bool) : string = let version = cipher.Substring (0, 1) |> int if version <> 1 then failwithf "Version not supported: %i" version let keyAsBytes = WebUtility.UrlDecode key |> Convert.FromBase64String let bytes = cipher.Substring 1 |> (if urlDecode then WebUtility.UrlDecode else id) |> Convert.FromBase64String let iv = Array.zeroCreate (keySize / 8) Array.blit bytes 0 iv 0 iv.Length let hash = Array.zeroCreate 32 Array.blit bytes iv.Length hash 0 hash.Length use aes = new AesCryptoServiceProvider (KeySize = keySize) use stream = new MemoryStream (bytes, iv.Length + hash.Length, bytes.Length - iv.Length - hash.Length) use cryptoStream = new CryptoStream (stream, aes.CreateDecryptor (keyAsBytes, iv), CryptoStreamMode.Read) use streamResult = new MemoryStream () cryptoStream.CopyTo streamResult streamResult.Position <- 0L let result = Array.zeroCreate (int streamResult.Length) streamResult.Read (result, 0, result.Length) |> ignore use sha = SHA256.Create () let computedHash = sha.ComputeHash result if hash <> computedHash then failwith "hash mismatch" Encoding.UTF8.GetString result