From 3579135886b85a80569501f1abb5ae7a56f6865e Mon Sep 17 00:00:00 2001
From: Greg Burri <greg.burri@gmail.com>
Date: Fri, 12 Jul 2019 22:46:19 +0200
Subject: [PATCH] Add the F# implementation.

---
 WebSharper/Crypto.fs | 77 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 77 insertions(+)
 create mode 100644 WebSharper/Crypto.fs

diff --git a/WebSharper/Crypto.fs b/WebSharper/Crypto.fs
new file mode 100644
index 0000000..5ad3d99
--- /dev/null
+++ b/WebSharper/Crypto.fs
@@ -0,0 +1,77 @@
+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
+
+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
+
+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
\ No newline at end of file
-- 
2.49.0