Add the F# implementation.
authorGreg Burri <greg.burri@gmail.com>
Fri, 12 Jul 2019 20:46:19 +0000 (22:46 +0200)
committerGreg Burri <greg.burri@gmail.com>
Fri, 12 Jul 2019 20:46:19 +0000 (22:46 +0200)
WebSharper/Crypto.fs [new file with mode: 0644]

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