Browse Source

encrypt pass example but with aes -- todo with rsa

221V 1 week ago
parent
commit
4dc8c1e428

+ 3 - 0
README.md

@@ -50,6 +50,9 @@ sudo apt-get install memcached
 // https://github.com/dlang-community/toml/blob/master/src/toml/toml.d
 // https://github.com/dlang-community/toml/blob/master/src/toml/toml.d
 
 
 
 
+// "secured": "~>3.0.0", // patched - https://github.com/221V/SecureD
+
+
 $ cd <FOLDER_NAME>
 $ cd <FOLDER_NAME>
 $ make c
 $ make c
 
 

+ 86 - 0
vtest/priv/login_test.dtl

@@ -16,11 +16,97 @@
 
 
 <p>{{result1}}</p>
 <p>{{result1}}</p>
 
 
+<br><br>
+<input id="login1" type="text" value="test1" placeholder="nickname"><br>
+<input id="pass1" type="password" value="123" placeholder="password"><br>
+<button id="send_pass1" onclick="do_log_in();">Log In</button>
+
+<script>
+async function encryptAES256CBC(key, iv, plaintext){
+  const enc = new TextEncoder();
+  const cryptoKey = await window.crypto.subtle.importKey(
+    "raw", key, { name: "AES-CBC" }, false, ["encrypt"]
+  );
+  const ciphertext = await window.crypto.subtle.encrypt({ name: "AES-CBC", iv }, cryptoKey, enc.encode(plaintext));
+  return new Uint8Array(ciphertext);
+}
+
+async function decryptAES256CBC(key, iv, ciphertext){
+  const cryptoKey = await window.crypto.subtle.importKey(
+    "raw", key, { name: "AES-CBC" }, false, ["decrypt"]
+  );
+  const decrypted = await window.crypto.subtle.decrypt({ name: "AES-CBC", iv }, cryptoKey, ciphertext);
+  return new TextDecoder().decode(decrypted);
+}
+
+/*
+
+var txt = "12345678";
+var txt2 = "12345678testтест";
+
+var key = new Uint8Array([34, 74, 12, 214, 126, 234, 101, 147, 13, 32, 244, 185, 45, 217, 142, 33, 213, 116, 63, 179, 84, 23, 138, 187, 134, 130, 234, 54, 48, 66, 20, 152]);
+var iv = new Uint8Array([62, 133, 213, 219, 194, 200, 76, 142, 202, 16, 12, 237, 163, 147, 65, 93]);
+
+(async () => {
+  const cipherText = await encryptAES256CBC(key, iv, txt);
+  console.log("Encrypted (Uint8Array):", cipherText);
+})();
+// Uint8Array(16) [223, 86, 210, 55, 192, 240, 144, 50, 159, 4, 238, 182, 171, 185, 80, 48]
+
+(async () => {
+  const cipherText = await encryptAES256CBC(key, iv, txt2);
+  console.log("Encrypted (Uint8Array):", cipherText);
+})();
+// Uint8Array(32) [90, 85, 212, 32, 94, 33, 182, 43, 20, 183, 121, 59, 232, 45, 180, 158, 153, 9, 54, 45, 244, 32, 85, 24, 162, 206, 56, 235, 107, 194, 143, 192]
+
+
+var encrypted = new Uint8Array([223, 86, 210, 55, 192, 240, 144, 50, 159, 4, 238, 182, 171, 185, 80, 48]);
+var encrypted2 = new Uint8Array([90, 85, 212, 32, 94, 33, 182, 43, 20, 183, 121, 59, 232, 45, 180, 158, 153, 9, 54, 45, 244, 32, 85, 24, 162, 206, 56, 235, 107, 194, 143, 192]);
+
+(async () => {
+  const decrypted = await decryptAES256CBC(key, iv, encrypted);
+  console.log("decrypted:", decrypted);
+})();
+// "12345678"
+
+(async () => {
+  const decrypted = await decryptAES256CBC(key, iv, encrypted2);
+  console.log("decrypted:", decrypted);
+})();
+// "12345678testтест"
+
+*/
+</script>
+
 <script src="/js/BigInteger.min.js" defer></script>
 <script src="/js/BigInteger.min.js" defer></script>
 <script src="/js/ieee754.js" defer></script>
 <script src="/js/ieee754.js" defer></script>
 <script src="/js/bert.js" defer></script>
 <script src="/js/bert.js" defer></script>
 <script src="/js/ws_conn.js" defer></script>
 <script src="/js/ws_conn.js" defer></script>
 <script>
 <script>
+function uint8ArrayToStr(arr){
+  return '[' + Array.from(arr).join(',') + ']';
+}
+
+function do_log_in(){
+  if(!(window.key && window.iv && window.uid)){ // was not init yet
+    ws.send(enc(tuple( number(1) )));
+    return;
+  } // else - already was init
+  
+  console.log('window.key = ', window.key);
+  console.log('window.iv = ', window.iv);
+  
+  var login = qi('login1').value.trim();
+  var pass = qi('pass1').value.trim();
+  if((pass == '') || (login == '') ){ alert('Err! empty login/pass!'); return; }
+  
+  (async () => {
+    var enc_pass = await encryptAES256CBC(window.key, window.iv, pass);
+    ws.send(enc(tuple( number(2), bin(window.uid), bin(login), bin(uint8ArrayToStr(enc_pass)) )));
+  })();
+}
+
+
 window.addEventListener('load', function(){
 window.addEventListener('load', function(){
   ws_start();
   ws_start();
 });
 });

+ 1 - 0
vtest/public/js/ws_conn.js

@@ -53,6 +53,7 @@ function ws_start(){
     //for(var i = 0;i < protos.length; i++){ p = protos[i]; if(p.on(evt, p.do).status == "ok") return; }
     //for(var i = 0;i < protos.length; i++){ p = protos[i]; if(p.on(evt, p.do).status == "ok") return; }
     //if($bert.on(evt, $bert.do).status == "ok") return;
     //if($bert.on(evt, $bert.do).status == "ok") return;
     try{
     try{
+      console.log('evt.data: ', evt.data);
       eval(evt.data);
       eval(evt.data);
     }catch(e){
     }catch(e){
       console.error("Eval failed: \n", e);
       console.error("Eval failed: \n", e);

+ 360 - 0
vtest/source/secured/ecc.d

@@ -0,0 +1,360 @@
+module secured.ecc;
+
+import std.stdio;
+import std.string;
+
+import deimos.openssl.evp;
+import deimos.openssl.rand;
+import deimos.openssl.pem;
+import deimos.openssl.bio;
+
+import secured.hash;
+import secured.kdf;
+import secured.random;
+import secured.util;
+
+public enum EccCurve
+{
+    P256,
+    P384,
+    P521,
+}
+
+@trusted:
+
+public class EllipticCurve
+{
+    private EVP_PKEY_CTX* paramsctx;
+    private EVP_PKEY* params;
+    private EVP_PKEY_CTX* keyctx;
+    private EVP_PKEY* key;
+
+    private bool _hasPrivateKey;
+    public @property bool hasPrivateKey() { return _hasPrivateKey; }
+
+    public this(EccCurve curve = EccCurve.P384)
+    {
+        //Reseed the OpenSSL RNG every time we create a new ECC Key to ensure that the result is truely random in threading/forking scenarios.
+        ubyte[] seedbuf = random(32);
+        RAND_seed(seedbuf.ptr, cast(int)seedbuf.length);
+
+        //Generate the key parameters
+        paramsctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, null);
+        if (paramsctx is null) {
+            throw new CryptographicException("Cannot get an OpenSSL public key context.");
+        }
+        if (EVP_PKEY_paramgen_init(paramsctx) < 1) {
+            throw new CryptographicException("Cannot initialize the OpenSSL public key context.");
+        }
+        if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(paramsctx, getOpenSSLCurveId(curve)) < 1) {
+            throw new CryptographicException("Cannot set the requested curve.");
+        }
+        if (EVP_PKEY_paramgen(paramsctx, &params) < 1) {
+            throw new CryptographicException("Unable to generate the key parameters.");
+        }
+
+        //Generate the public and private keys
+        keyctx = EVP_PKEY_CTX_new(params, null);
+        if (keyctx is null) {
+            throw new CryptographicException("Cannot get an OpenSSL private key context.");
+        }
+        if (EVP_PKEY_keygen_init(keyctx) < 1) {
+            throw new CryptographicException("Cannot initialize the OpenSSL private key context.");
+        }
+        if (EVP_PKEY_keygen(keyctx, &key) < 1) {
+            throw new CryptographicException("Unable to generate the private key.");
+        }
+
+        _hasPrivateKey = true;
+    }
+
+    public this(string privateKey, string password)
+    {
+        //Reseed the OpenSSL RNG every time we load an existing ECC Key to ensure that the result is truely random in threading/forking scenarios.
+        ubyte[] seedbuf = random(32);
+        RAND_seed(seedbuf.ptr, cast(int)seedbuf.length);
+
+        _hasPrivateKey = true;
+        ubyte[] pk = cast(ubyte[])privateKey;
+
+        BIO* bio = BIO_new_mem_buf(pk.ptr, cast(int)pk.length);
+        if (password is null) {
+            key = PEM_read_bio_PrivateKey(bio, null, null, null);
+        } else {
+            ubyte[] pwd = cast(ubyte[])password;
+            pwd = pwd ~ '\0';
+
+            key = PEM_read_bio_PrivateKey(bio, null, null, pwd.ptr);
+        }
+        BIO_free_all(bio);
+    }
+
+    public this(string publicKey)
+    {
+        //Reseed the OpenSSL RNG every time we load an existing ECC Key to ensure that the result is truely random in threading/forking scenarios.
+        ubyte[] seedbuf = random(32);
+        RAND_seed(seedbuf.ptr, cast(int)seedbuf.length);
+
+        _hasPrivateKey = false;
+        ubyte[] pk = cast(ubyte[])publicKey;
+
+        BIO* bio = BIO_new_mem_buf(pk.ptr, cast(int)pk.length);
+        key = PEM_read_bio_PUBKEY(bio, null, null, null);
+        BIO_free_all(bio);
+    }
+
+    public ~this()
+    {
+        if (key !is null) {
+            EVP_PKEY_free(key);
+        }
+        if (keyctx !is null) {
+            EVP_PKEY_CTX_free(keyctx);
+        }
+        if (params !is null) {
+            EVP_PKEY_free(params);
+        }
+        if (paramsctx !is null) {
+            EVP_PKEY_CTX_free(paramsctx);
+        }
+    }
+
+    public ubyte[] derive(string peerKey)
+    {
+        ubyte[] pk = cast(ubyte[])peerKey;
+        BIO* bio = BIO_new_mem_buf(pk.ptr, cast(int)pk.length);
+        EVP_PKEY* peer = PEM_read_bio_PUBKEY(bio, null, null, null);
+        BIO_free_all(bio);
+
+        //Initialize the key derivation context.
+        EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(key, null);
+        if (ctx is null) {
+            throw new CryptographicException("Unable to create the key derivation context.");
+        }
+        if (EVP_PKEY_derive_init(ctx) <= 0) {
+            throw new CryptographicException("Unable to initialize the key derivation context.");
+        }
+        if (EVP_PKEY_derive_set_peer(ctx, peer) <= 0) {
+            throw new CryptographicException("Unable to set the peer key.");
+        }
+
+        //Derive the key
+        size_t dklen = 0;
+        if (EVP_PKEY_derive(ctx, null, &dklen) <= 0) {
+            throw new CryptographicException("Unable to determine the length of the derived key.");
+        }
+        ubyte[] derivedKey = new ubyte[dklen];
+        if (EVP_PKEY_derive(ctx, derivedKey.ptr, &dklen) <= 0) {
+            throw new CryptographicException("Unable to determine the length of the derived key.");
+        }
+
+        return derivedKey;
+    }
+
+    public ubyte[] sign(ubyte[] data, bool useSha256 = false)
+    {
+        EVP_PKEY_CTX* pkeyctx = null;
+
+        pkeyctx = EVP_PKEY_CTX_new(key, null);
+        if (pkeyctx is null) {
+            throw new CryptographicException("Unable to create the key signing context.");
+        }
+        scope(exit) {
+            if (pkeyctx !is null) {
+                EVP_PKEY_CTX_free(pkeyctx);
+            }
+        }
+
+        if (EVP_PKEY_sign_init(pkeyctx) <= 0) {
+            throw new CryptographicException("Unable to initialize the signing digest.");
+        }
+
+        if (EVP_PKEY_CTX_set_signature_md(pkeyctx, cast(void*)(!useSha256 ? EVP_sha384() : EVP_sha256())) <= 0) {
+            throw new CryptographicException("Unable to set the signing digest.");
+        }
+
+        size_t signlen = 0;
+        if (EVP_PKEY_sign(pkeyctx, null, &signlen, data.ptr, data.length) <= 0) {
+            throw new CryptographicException("Unable to calculate signature length.");
+        }
+
+        ubyte[] sign = new ubyte[signlen];
+        if (EVP_PKEY_sign(pkeyctx, sign.ptr, &signlen, data.ptr, data.length) <= 0) {
+            throw new CryptographicException("Unable to calculate signature.");
+        }
+
+        return sign;
+    }
+
+    public bool verify(ubyte[] data, ubyte[] signature, bool useSha256 = false)
+    {
+        EVP_PKEY_CTX* pkeyctx = null;
+
+        pkeyctx = EVP_PKEY_CTX_new(key, null);
+        if (pkeyctx is null) {
+            throw new CryptographicException("Unable to create the key signing context.");
+        }
+        scope(exit) {
+            if (pkeyctx !is null) {
+                EVP_PKEY_CTX_free(pkeyctx);
+            }
+        }
+
+        if (EVP_PKEY_verify_init(pkeyctx) <= 0) {
+            throw new CryptographicException("Unable to initialize the signing digest.");
+        }
+
+        if (EVP_PKEY_CTX_set_signature_md(pkeyctx, cast(void*)(!useSha256 ? EVP_sha384() : EVP_sha256())) <= 0) {
+            throw new CryptographicException("Unable to set the signing digest.");
+        }
+
+        int ret = EVP_PKEY_verify(pkeyctx, data.ptr, cast(long)data.length, signature.ptr, cast(long)signature.length);
+
+        return ret != 1;
+    }
+
+    public string getPublicKey()
+    {
+        BIO* bio = BIO_new(BIO_s_mem());
+
+        PEM_write_bio_PUBKEY(bio, key);
+
+        ubyte[] buffer = new ubyte[BIO_ctrl_pending(bio)];
+        BIO_read(bio, buffer.ptr, cast(int)buffer.length);
+        BIO_free_all(bio);
+
+        return cast(string)buffer;
+    }
+
+    public string getPrivateKey(string password, bool use3Des = false)
+    {
+        if (!_hasPrivateKey) {
+            return null;
+        }
+
+        BIO* bio = BIO_new(BIO_s_mem());
+
+        if (password is null) {
+            PEM_write_bio_PKCS8PrivateKey(bio, key, null, null, 0, null, null);
+        } else {
+            ubyte[] pwd = cast(ubyte[])password;
+            pwd = pwd ~ '\0';
+
+            PEM_write_bio_PKCS8PrivateKey(
+                bio,
+                key,
+                !use3Des ? EVP_aes_256_cbc() : EVP_des_ede3_cbc(),
+                null,
+                0,
+                null,
+                pwd.ptr);
+        }
+
+        if(BIO_ctrl_pending(bio) == 0) {
+            throw new CryptographicException("No private key written.");
+        }
+
+        ubyte[] buffer = new ubyte[BIO_ctrl_pending(bio)];
+        BIO_read(bio, buffer.ptr, cast(int)buffer.length);
+        BIO_free_all(bio);
+
+        return cast(string)buffer;
+    }
+}
+
+unittest
+{
+    import std.digest;
+
+    writeln("Testing EllipticCurve Private Key Extraction/Recreation:");
+
+    EllipticCurve eckey = new EllipticCurve();
+    string pub = eckey.getPublicKey();
+
+    writeln("Extracting No Password");
+    string pkNoPwd = eckey.getPrivateKey(null);
+    writeln("Extracting With Password");
+    string pkPwd = eckey.getPrivateKey("Test Password");
+
+    writeln("Private Key Without Password: ");
+    writeln(pkNoPwd);
+    writeln("Private Key With Password:");
+    writeln(pkPwd);
+
+    assert(pkNoPwd !is null);
+    assert(pkPwd !is null);
+
+    EllipticCurve eckeyr1 = new EllipticCurve(pkNoPwd, null);
+    EllipticCurve eckeyr2 = new EllipticCurve(pkPwd, "Test Password");
+
+    string pkRecPwd = eckeyr2.getPrivateKey("Test Password");
+    string pkRecNoPwd = eckeyr1.getPrivateKey(null);
+
+    writeln("Recreated Private Key Without Password: ");
+    writeln(pkRecNoPwd);
+    writeln("Recreated Private Key With Password:");
+    writeln(pkRecPwd);
+
+    assert(pkNoPwd == pkRecNoPwd);
+}
+
+unittest
+{
+    import std.digest;
+
+    writeln("Testing EllipticCurve Key Derivation:");
+
+    EllipticCurve eckey1 = new EllipticCurve();
+    writeln("Created Key 1");
+    EllipticCurve eckey2 = new EllipticCurve();
+    writeln("Created Key 2");
+
+    string privKey1 = eckey1.getPrivateKey(null);
+    writeln("Retrieved Private Key 1");
+
+
+    string pubKey1 = eckey1.getPublicKey();
+    writeln("Retrieved Public Key 1");
+    string pubKey2 = eckey2.getPublicKey();
+    writeln("Retrieved Public Key 2");
+    ubyte[] key1 = eckey1.derive(pubKey2);
+    writeln("Derived Key 1");
+    ubyte[] key2 = eckey2.derive(pubKey1);
+    writeln("Derived Key 2");
+
+    writeln("Derived Key 1: ", toHexString!(LetterCase.lower)(key1));
+    writeln("Derived Key 2: ", toHexString!(LetterCase.lower)(key2));
+
+    assert(key1 !is null);
+    assert(key2 !is null);
+    assert(constantTimeEquality(key1, key2));
+}
+
+unittest
+{
+    import std.digest;
+
+    writeln("Testing EllipticCurve Signing/Verification:");
+
+    EllipticCurve eckey = new EllipticCurve();
+    ubyte[48] data = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    ubyte[] sig = eckey.sign(data);
+    writeln("Signature: ", toHexString!(LetterCase.lower)(sig));
+    assert(eckey.verify(data, sig));
+}
+
+private int getOpenSSLCurveId(EccCurve curve) {
+    import std.conv;
+    import std.format;
+
+    switch (curve) {
+        case EccCurve.P256: return NID_secp256k1;
+        case EccCurve.P384: return NID_secp384r1;
+        case EccCurve.P521: return NID_secp521r1;
+        default:
+            throw new CryptographicException(format("ECC Curve '%s' not supported.", to!string(curve)));
+    }
+}

+ 242 - 0
vtest/source/secured/hash.d

@@ -0,0 +1,242 @@
+module secured.hash;
+
+import std.stdio;
+
+import secured.openssl;
+import deimos.openssl.evp;
+
+import secured.util;
+
+public enum HashAlgorithm : ubyte {
+    None,
+    SHA2_256,
+    SHA2_384,
+    SHA2_512,
+    SHA2_512_224,
+    SHA2_512_256,
+    SHA3_224,
+    SHA3_256,
+    SHA3_384,
+    SHA3_512,
+	Default = SHA2_384,
+}
+
+@safe public ubyte[] hash(const ubyte[] data) {
+    return hash_ex(data, HashAlgorithm.Default);
+}
+
+@safe public bool hash_verify(ubyte[] test, ubyte[] data) {
+    ubyte[] hash = hash_ex(data, HashAlgorithm.Default);
+    return constantTimeEquality(hash, test);
+}
+
+@trusted public ubyte[] hash_ex(const ubyte[] data, HashAlgorithm func)
+{
+    //Create the OpenSSL context
+    EVP_MD_CTX *mdctx;
+    if ((mdctx = EVP_MD_CTX_new()) == null) {
+        throw new CryptographicException("Unable to create OpenSSL context.");
+    }
+    scope(exit) {
+        if(mdctx !is null) {
+            EVP_MD_CTX_free(mdctx);
+        }
+    }
+
+    //Initialize the hash algorithm
+    if (EVP_DigestInit_ex(mdctx, getOpenSSLHashAlgorithm(func), null) < 0) {
+        throw new CryptographicException("Unable to create hash context.");
+    }
+
+    //Run the provided data through the digest algorithm
+    if (EVP_DigestUpdate(mdctx, data.ptr, data.length) < 0) {
+        throw new CryptographicException("Error while updating digest.");
+    }
+
+    //Copy the OpenSSL digest to our D buffer.
+    uint digestlen;
+    ubyte[] digest = new ubyte[getHashLength(func)];
+    if (EVP_DigestFinal_ex(mdctx, digest.ptr, &digestlen) < 0) {
+        throw new CryptographicException("Error while retrieving the digest.");
+    }
+
+    return digest;
+}
+
+@safe public bool hash_verify_ex(const ubyte[] test, const ubyte[] data, HashAlgorithm func) {
+    ubyte[] hash = hash_ex(data, func);
+    return constantTimeEquality(hash, test);
+}
+
+unittest {
+    import std.digest;
+
+    writeln("Testing SHA2 Byte Array Hash:");
+
+    ubyte[] vec1 = hash_ex(cast(ubyte[])"", HashAlgorithm.SHA2_384);
+    ubyte[] vec2 = hash_ex(cast(ubyte[])"abc", HashAlgorithm.SHA2_384);
+    ubyte[] vec3 = hash_ex(cast(ubyte[])"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", HashAlgorithm.SHA2_384);
+    ubyte[] vec4 = hash_ex(cast(ubyte[])"The quick brown fox jumps over the lazy dog.", HashAlgorithm.SHA2_384);
+
+    writeln(toHexString!(LetterCase.lower)(vec1));
+    writeln(toHexString!(LetterCase.lower)(vec2));
+    writeln(toHexString!(LetterCase.lower)(vec3));
+    writeln(toHexString!(LetterCase.lower)(vec4));
+
+    assert(toHexString!(LetterCase.lower)(vec1) == "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b");
+    assert(toHexString!(LetterCase.lower)(vec2) == "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7");
+    assert(toHexString!(LetterCase.lower)(vec3) == "3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b");
+    assert(toHexString!(LetterCase.lower)(vec4) == "ed892481d8272ca6df370bf706e4d7bc1b5739fa2177aae6c50e946678718fc67a7af2819a021c2fc34e91bdb63409d7");
+}
+
+unittest {
+    import std.digest;
+
+    writeln("Testing SHA3 Byte Array Hash:");
+
+    ubyte[] vec1 = hash_ex(cast(ubyte[])"", HashAlgorithm.SHA3_384);
+    ubyte[] vec2 = hash_ex(cast(ubyte[])"abc", HashAlgorithm.SHA3_384);
+    ubyte[] vec3 = hash_ex(cast(ubyte[])"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", HashAlgorithm.SHA3_384);
+    ubyte[] vec4 = hash_ex(cast(ubyte[])"The quick brown fox jumps over the lazy dog.", HashAlgorithm.SHA3_384);
+
+    writeln(toHexString!(LetterCase.lower)(vec1));
+    writeln(toHexString!(LetterCase.lower)(vec2));
+    writeln(toHexString!(LetterCase.lower)(vec3));
+    writeln(toHexString!(LetterCase.lower)(vec4));
+
+    assert(toHexString!(LetterCase.lower)(vec1) == "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004");
+    assert(toHexString!(LetterCase.lower)(vec2) == "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25");
+    assert(toHexString!(LetterCase.lower)(vec3) == "991c665755eb3a4b6bbdfb75c78a492e8c56a22c5c4d7e429bfdbc32b9d4ad5aa04a1f076e62fea19eef51acd0657c22");
+    assert(toHexString!(LetterCase.lower)(vec4) == "1a34d81695b622df178bc74df7124fe12fac0f64ba5250b78b99c1273d4b080168e10652894ecad5f1f4d5b965437fb9");
+}
+
+@safe public ubyte[] hash(string path) {
+    return hash_ex(path, HashAlgorithm.Default);
+}
+
+@safe public bool hash_verify(string path, ubyte[] test) {
+    ubyte[] hash = hash_ex(path, HashAlgorithm.Default);
+    return constantTimeEquality(hash, test);
+}
+
+@trusted public ubyte[] hash_ex(string path, HashAlgorithm func)
+{
+    //Open the file for reading
+    auto fsfile = File(path, "rb");
+    scope(exit) {
+        if(fsfile.isOpen()) {
+            fsfile.close();
+        }
+    }
+
+    //Create the OpenSSL context
+    EVP_MD_CTX *mdctx;
+    if ((mdctx = EVP_MD_CTX_new()) == null) {
+        throw new CryptographicException("Unable to create OpenSSL context.");
+    }
+    scope(exit) {
+        if(mdctx !is null) {
+            EVP_MD_CTX_free(mdctx);
+        }
+    }
+
+    //Initialize the hash algorithm
+    if (EVP_DigestInit_ex(mdctx, getOpenSSLHashAlgorithm(func), null) < 0) {
+        throw new CryptographicException("Unable to create hash context.");
+    }
+
+    //Read the file in chunks and update the Digest
+    foreach(ubyte[] data; fsfile.byChunk(FILE_BUFFER_SIZE)) {
+        if (EVP_DigestUpdate(mdctx, data.ptr, data.length) < 0) {
+            throw new CryptographicException("Error while updating digest.");
+        }
+    }
+
+    //Copy the OpenSSL digest to our D buffer.
+    uint digestlen;
+    ubyte[] digest = new ubyte[getHashLength(func)];
+    if (EVP_DigestFinal_ex(mdctx, digest.ptr, &digestlen) < 0) {
+        throw new CryptographicException("Error while retrieving the digest.");
+    }
+
+    return digest;
+}
+
+@safe public bool hash_verify_ex(string path, HashAlgorithm func, ubyte[] test) {
+    ubyte[] hash = hash_ex(path, func);
+    return constantTimeEquality(hash, test);
+}
+
+unittest {
+    import std.digest;
+
+    writeln("Testing File Hash:");
+
+    auto f = File("hashtest.txt", "wb");
+    f.rawWrite("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
+    f.close();
+
+    ubyte[] vec = hash_ex("hashtest.txt", HashAlgorithm.SHA2_384);
+    writeln(toHexString!(LetterCase.lower)(vec));
+    assert(toHexString!(LetterCase.lower)(vec) == "3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b");
+
+    remove("hashtest.txt");
+}
+
+@trusted package const(EVP_MD)* getOpenSSLHashAlgorithm(HashAlgorithm func) {
+    import std.conv;
+    import std.format;
+
+    switch (func) {
+        case HashAlgorithm.SHA2_256: return EVP_sha256();
+        case HashAlgorithm.SHA2_384: return EVP_sha384();
+        case HashAlgorithm.SHA2_512: return EVP_sha512();
+        case HashAlgorithm.SHA2_512_224: return EVP_sha512_224();
+        case HashAlgorithm.SHA2_512_256: return EVP_sha512_256();
+        case HashAlgorithm.SHA3_224: return EVP_sha3_224();
+        case HashAlgorithm.SHA3_256: return EVP_sha3_256();
+        case HashAlgorithm.SHA3_384: return EVP_sha3_384();
+        case HashAlgorithm.SHA3_512: return EVP_sha3_512();
+        default:
+            throw new CryptographicException(format("Hash Function '%s' is not supported by OpenSSL.", to!string(func)));
+    }
+}
+
+@trusted package string getOpenSSLHashAlgorithmString(HashAlgorithm func) {
+    import std.conv;
+    import std.format;
+
+    switch (func) {
+        case HashAlgorithm.SHA2_256: return "sha256";
+        case HashAlgorithm.SHA2_384: return "sha384";
+        case HashAlgorithm.SHA2_512: return "sha512";
+        case HashAlgorithm.SHA2_512_224: return "sha512-224";
+        case HashAlgorithm.SHA2_512_256: return "sha512-256";
+        case HashAlgorithm.SHA3_224: return "sha3-224";
+        case HashAlgorithm.SHA3_256: return "sha3-256";
+        case HashAlgorithm.SHA3_384: return "sha3-384";
+        case HashAlgorithm.SHA3_512: return "sha3-512";
+        default:
+            throw new CryptographicException(format("Hash Function '%s' is not supported by OpenSSL.", to!string(func)));
+    }
+}
+
+@safe package uint getHashLength(HashAlgorithm func) {
+    import std.conv;
+    import std.format;
+
+    switch (func) {
+        case HashAlgorithm.None: return 0;
+        case HashAlgorithm.SHA2_256: return 32;
+        case HashAlgorithm.SHA2_384: return 48;
+        case HashAlgorithm.SHA2_512: return 64;
+        case HashAlgorithm.SHA2_512_224: return 24;
+        case HashAlgorithm.SHA2_512_256: return 32;
+        case HashAlgorithm.SHA3_224: return 24;
+        case HashAlgorithm.SHA3_256: return 32;
+        case HashAlgorithm.SHA3_384: return 48;
+        case HashAlgorithm.SHA3_512: return 64;
+        default:
+            throw new CryptographicException(format("Hash Function '%s' is not supported.", to!string(func)));
+    }
+}

+ 447 - 0
vtest/source/secured/kdf.d

@@ -0,0 +1,447 @@
+module secured.kdf;
+
+import std.base64;
+import std.conv;
+import std.typecons;
+import std.format;
+import std.string;
+
+import deimos.openssl.evp;
+import deimos.openssl.kdf;
+import secured.openssl;
+
+import secured.hash;
+import secured.random;
+import secured.symmetric;
+import secured.util;
+
+public enum uint defaultKdfIterations = 1_048_576;
+public enum ushort defaultSCryptR = 8;
+public enum ushort defaultSCryptP = 1;
+public enum ulong maxSCryptMemory = 1_074_790_400;
+
+public enum KdfAlgorithm : ubyte {
+    None = 0,
+    PBKDF2 = 1,
+    HKDF = 2,
+    SCrypt = 3,
+    Argon2 = 4,
+    Default = SCrypt,
+}
+
+public enum VerifyPasswordResult
+{
+    /// <summary>
+    /// The password verification was successful.
+    /// </summary>
+    Success,
+    /// <summary>
+    /// The password verification failed.
+    /// </summary>
+    Failure,
+    /// <summary>
+    /// The password was successfully verified, but needs to be rehashed to use updated hashing parameters.
+    /// </summary>
+    Rehash,
+}
+
+@safe public struct HashedPassword
+{
+    /// <summary>
+    /// The hashing algorithm used to secure the password.
+    /// </summary>
+    public KdfAlgorithm algorithm;
+    /// <summary>
+    /// The version of the hash parameters used to secure the password.
+    /// </summary>
+    public short parameterVersion;
+    /// <summary>
+    /// The salt used by the hashing function.
+    /// </summary>
+    public ubyte[] salt;
+    /// <summary>
+    /// The hashed password
+    /// </summary>
+    public ubyte[] derived;
+
+    /// <summary>
+    /// Constructs a HashedPassword object from the provided hashing parameters.
+    /// </summary>
+    /// <param name="derived">The hashed password.</param>
+    /// <param name="salt">The salt used by the hashing function.</param>
+    /// <param name="algorithm">The hashing algorithm used to secure the password.</param>
+    /// <param name="paramVersion">The version of the hash parameters used to secure the password.</param>
+    package this(ubyte[] derived, ubyte[] salt, KdfAlgorithm algorithm, ushort paramVersion)
+    {
+        this.algorithm = algorithm;
+        this.parameterVersion = paramVersion;
+        this.salt = salt;
+        this.derived = derived;
+    }
+
+    /// <summary>
+    /// Constructs a HashedPassword from an encoded string.
+    /// </summary>
+    /// <param name="encoded">The encoded string.</param>
+    /// <returns>A HashedPassword object containing the decoded string values.</returns>
+    /// <exception cref="ArgumentOutOfRangeException">The provided string is invalid.</exception>
+    public this(string encoded) {
+        auto parts = encoded.split(".");
+        if (parts.length != 4) throw new CryptographicException("Invalid password string provided.");
+
+        this.algorithm = to!KdfAlgorithm(to!int(parts[0]));
+        this.parameterVersion = to!ushort(parts[1]);
+        this.salt = Base64.decode(parts[2]);
+        this.derived = Base64.decode(parts[3]);
+    }
+
+    /// <summary>
+    /// Creates string containing the encoded password from the HashedPassword.
+    /// </summary>
+    /// <returns>The encoded password string</returns>
+    public string toString() {
+        return to!string(join([to!string(to!int(algorithm)), to!string(parameterVersion), Base64.encode(salt), Base64.encode(derived)], "."));
+    }
+}
+
+@safe public HashedPassword securePassword(string password, const ubyte[] pepper, KdfAlgorithm algorithm = KdfAlgorithm.Default) {
+    if (algorithm == KdfAlgorithm.HKDF) throw new CryptographicException("KdfAlgorithm.HKDF is not supported for password security.");
+
+    if (algorithm == KdfAlgorithm.PBKDF2) {
+        ubyte[] salt = random(32);
+        return HashedPassword(pbkdf2_ex(password, salt ~ pepper, HashAlgorithm.Default, 64, defaultKdfIterations), salt, algorithm, 1);
+    }
+
+    if (algorithm == KdfAlgorithm.SCrypt) {
+        ubyte[] salt = random(32);
+        return HashedPassword(scrypt_ex(password, salt ~ pepper, 64), salt, algorithm, 1);
+    }
+
+    if (algorithm == KdfAlgorithm.Argon2) throw new CryptographicException("Argon2 is not supported.");
+
+    throw new CryptographicException("KdfAlgorithm.None is not supported for password security.");
+}
+
+@safe public VerifyPasswordResult verifyPassword(string suppliedPassword, HashedPassword storedPassword, const ubyte[] pepper) {
+    if (storedPassword.algorithm == KdfAlgorithm.HKDF) throw new CryptographicException("KdfAlgorithm.HKDF is not supported for password security.");
+
+    if (storedPassword.algorithm == KdfAlgorithm.PBKDF2 && storedPassword.parameterVersion == 1) {
+        if (pbkdf2_verify_ex(storedPassword.derived, suppliedPassword, storedPassword.salt ~ pepper, HashAlgorithm.Default, 64, defaultKdfIterations)) return VerifyPasswordResult.Success;
+    }
+    else if (storedPassword.algorithm == KdfAlgorithm.PBKDF2 && storedPassword.parameterVersion == 0) {
+        if (pbkdf2_verify_ex(storedPassword.derived, suppliedPassword, storedPassword.salt ~ pepper, HashAlgorithm.SHA2_512, to!uint(storedPassword.derived.length), 100000)) return VerifyPasswordResult.Rehash;
+    }
+
+    if (storedPassword.algorithm == KdfAlgorithm.SCrypt && storedPassword.parameterVersion == 1) {
+        ubyte[] supplied = scrypt_ex(suppliedPassword, storedPassword.salt ~ pepper, 64);
+        if (supplied.constantTimeEquality(storedPassword.derived)) return VerifyPasswordResult.Success;
+    }
+
+    if (storedPassword.algorithm == KdfAlgorithm.Argon2) throw new CryptographicException("Argon2 is not supported.");
+
+    return VerifyPasswordResult.Failure;
+}
+
+unittest {
+    import std.digest;
+    import std.stdio;
+
+    ubyte[48] salt = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    writeln("Successful Password Test");
+    HashedPassword successTest = securePassword("TestPassword!@#$%", salt);
+    writeln("Encoded: ", successTest.toString());
+    auto verifyResult = verifyPassword("TestPassword!@#$%", successTest, salt);
+    assert (verifyResult == VerifyPasswordResult.Success);
+
+    writeln("Failure Password Test");
+    HashedPassword failTest = securePassword("TestPassword!@#$%", salt);
+    writeln("Encoded: ", failTest.toString());
+    verifyResult = verifyPassword("TestPassword!@#$", failTest, salt);
+    assert (verifyResult == VerifyPasswordResult.Failure);
+
+    writeln("PBKDF2 Password Test");
+    HashedPassword pbkdf2Test = securePassword("TestPassword!@#$%", salt, KdfAlgorithm.PBKDF2);
+    writeln("Encoded: ", pbkdf2Test.toString());
+    verifyResult = verifyPassword("TestPassword!@#$%", pbkdf2Test, salt);
+    assert (verifyResult == VerifyPasswordResult.Success);
+}
+
+public struct KdfResult {
+    public ubyte[] salt;
+    public ubyte[] key;
+}
+
+@safe public KdfResult pbkdf2(string password, uint iterations = defaultKdfIterations) {
+    KdfResult result;
+    result.salt = random(getHashLength(HashAlgorithm.Default));
+    result.key = pbkdf2_ex(password, result.salt, HashAlgorithm.Default, getHashLength(HashAlgorithm.Default), iterations);
+    return result;
+}
+
+@safe public bool pbkdf2_verify(const ubyte[] key, const ubyte[] salt, string password, uint iterations = defaultKdfIterations) {
+    ubyte[] test = pbkdf2_ex(password, salt, HashAlgorithm.Default, getHashLength(HashAlgorithm.Default), iterations);
+    return constantTimeEquality(key, test);
+}
+
+@trusted public ubyte[] pbkdf2_ex(string password, const ubyte[] salt, HashAlgorithm func, uint outputLen, uint iterations) {
+    ubyte[] output = new ubyte[outputLen];
+    if(PKCS5_PBKDF2_HMAC(password.ptr, cast(int)password.length, salt.ptr, cast(int)salt.length, iterations, getOpenSSLHashAlgorithm(func), outputLen, output.ptr) == 0) {
+        throw new CryptographicException("Unable to execute PBKDF2 hash function.");
+    }
+    return output;
+}
+
+@safe public bool pbkdf2_verify_ex(const ubyte[] test, string password, const ubyte[] salt, HashAlgorithm func, uint outputLen, uint iterations) {
+    ubyte[] key = pbkdf2_ex(password, salt, func, outputLen, iterations);
+    return constantTimeEquality(test, key);
+}
+
+unittest
+{
+    import std.datetime.stopwatch;
+    import std.digest;
+    import std.stdio;
+
+    writeln("Testing PBKDF2 Basic Methods:");
+
+    //Test basic methods
+    auto sw = StopWatch(AutoStart.no);
+    sw.start();
+    auto result = pbkdf2("password");
+    sw.stop();
+    writefln("PBKDF2 took %sms for 1,000,000 iterations", sw.peek.total!"msecs");
+
+    assert(result.key.length == 48);
+    assert(pbkdf2_verify(result.key, result.salt, "password"));
+    writeln(toHexString!(LetterCase.lower)(result.key));
+
+    //Test extended methods
+    ubyte[32] salt = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    ubyte[] key = pbkdf2_ex("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", salt, HashAlgorithm.SHA2_384, 64, 100000);
+    assert(pbkdf2_verify_ex(key, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", salt, HashAlgorithm.SHA2_384, 64, 100000));
+    writeln(toHexString!(LetterCase.lower)(key));
+}
+
+unittest
+{
+    import std.digest;
+    import std.stdio;
+
+    writeln("Testing PBKDF2 Extended with Defaults:");
+
+    ubyte[48] key = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                      0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                      0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    ubyte[] vec1 = pbkdf2_ex("", key, HashAlgorithm.SHA2_384, 48, 25000);
+    ubyte[] vec2 = pbkdf2_ex("abc", key, HashAlgorithm.SHA2_384, 48, 25000);
+    ubyte[] vec3 = pbkdf2_ex("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", key, HashAlgorithm.SHA2_384, 48, 25000);
+
+    writeln(toHexString!(LetterCase.lower)(vec1));
+    writeln(toHexString!(LetterCase.lower)(vec2));
+    writeln(toHexString!(LetterCase.lower)(vec3));
+
+    assert(toHexString!(LetterCase.lower)(vec1) == "b0ddf56b90903d638ec8d07a4205ba2bcfa944955d553e1ef3f91cba84e8e3bde9db7c8ccf14df26f8305fc8634572f9");
+    assert(toHexString!(LetterCase.lower)(vec2) == "b0a5e09a38bee3eb2b84d477d5259ef7bebf0e48d9512178f7e26cc330278ff45417d47d84db06a12b8ea49377a7c7cb");
+    assert(toHexString!(LetterCase.lower)(vec3) == "d1aacafea3a9fdf3ee6236b1b45527974ea01539b4a7cc493bba56e15e14d520b2834d7bf22b83bb5c21c4bccb423be2");
+}
+
+unittest
+{
+    import std.digest;
+    import std.stdio;
+
+    writeln("Testing PBKDF2 Extended with Custom Iterations:");
+
+    ubyte[48] key = [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                     0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                     0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    ubyte[] vec1 = pbkdf2_ex("", key, HashAlgorithm.SHA2_384, 48, 150000);
+    ubyte[] vec2 = pbkdf2_ex("abc", key, HashAlgorithm.SHA2_384, 48, 150000);
+    ubyte[] vec3 = pbkdf2_ex("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", key, HashAlgorithm.SHA2_384, 48, 150000);
+
+    writeln(toHexString!(LetterCase.lower)(vec1));
+    writeln(toHexString!(LetterCase.lower)(vec2));
+    writeln(toHexString!(LetterCase.lower)(vec3));
+
+    assert(toHexString!(LetterCase.lower)(vec1) == "babdcbbf4ff89367ed223d2edd06ef5473ac9cdc827783ed0b4b5eafd9e4097beb2ef66d6fc92d24dbf4b86aa51b4a0f");
+    assert(toHexString!(LetterCase.lower)(vec2) == "8894348ccea06d79f80382ae7d4434c0f2ef41f871d936604f426518ab23bde4410fddce6dad943c95de75dbece9b54a");
+    assert(toHexString!(LetterCase.lower)(vec3) == "fba55e91818c35b1e4cc753fbd01a6cd138c49da472b58b2d7c4860ba39a3dd9032f8f641aadcd74a819361ed27c9a0f");
+}
+
+unittest
+{
+    import std.digest;
+    import std.stdio;
+
+    writeln("Testing PBKDF2 Extended with Custom Output Length:");
+
+    ubyte[48] key = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                      0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                      0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    ubyte[] vec1 = pbkdf2_ex("", key, HashAlgorithm.SHA2_384, 32, 25000);
+    ubyte[] vec2 = pbkdf2_ex("abc", key, HashAlgorithm.SHA2_384, 32, 25000);
+    ubyte[] vec3 = pbkdf2_ex("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", key, HashAlgorithm.SHA2_384, 32, 25000);
+
+    writeln(toHexString!(LetterCase.lower)(vec1));
+    writeln(toHexString!(LetterCase.lower)(vec2));
+    writeln(toHexString!(LetterCase.lower)(vec3));
+
+    assert(toHexString!(LetterCase.lower)(vec1) == "b0ddf56b90903d638ec8d07a4205ba2bcfa944955d553e1ef3f91cba84e8e3bd");
+    assert(toHexString!(LetterCase.lower)(vec2) == "b0a5e09a38bee3eb2b84d477d5259ef7bebf0e48d9512178f7e26cc330278ff4");
+    assert(toHexString!(LetterCase.lower)(vec3) == "d1aacafea3a9fdf3ee6236b1b45527974ea01539b4a7cc493bba56e15e14d520");
+}
+
+@safe public KdfResult hkdf(const SymmetricKey key) {
+    return hkdf(key, getCipherKeyLength(key.algorithm));
+}
+
+@safe public KdfResult hkdf(const SymmetricKey key, size_t outputLen) {
+    KdfResult result;
+    result.salt = random(getHashLength(HashAlgorithm.Default));
+    result.key = hkdf_ex(key.value, result.salt, string.init, outputLen, HashAlgorithm.Default);
+    return result;
+}
+
+@trusted public ubyte[] hkdf_ex(const ubyte[] key, const ubyte[] salt, string info, size_t outputLen, HashAlgorithm func) {
+    if (key.length == 0) {
+        throw new CryptographicException("HKDF key cannot be an empty array.");
+    }
+
+    EVP_KDF *kdf;
+    EVP_KDF_CTX *kctx = null;
+    ubyte[] derived = new ubyte[outputLen];
+    ossl_param_st[5] params;
+
+    /* Find and allocate a context for the HKDF algorithm */
+    if ((kdf = EVP_KDF_fetch(null, "hkdf", null)) == null) {
+        throw new CryptographicException("Unable to create HKDF function.");
+    }
+    kctx = EVP_KDF_CTX_new(kdf);
+    scope(exit) {
+        if (kctx !is null) {
+            EVP_KDF_CTX_free(kctx);
+        }
+    }
+
+    /* Build up the parameters for the derivation */
+    string hashName = getOpenSSLHashAlgorithmString(func);
+    params[0] = OSSL_PARAM_construct_utf8_string("digest".toStringz(), cast(char*)hashName.toStringz(), hashName.length+1);
+    params[1] = OSSL_PARAM_construct_octet_string("salt".toStringz(), cast(void*)salt, salt.length);
+    params[2] = OSSL_PARAM_construct_octet_string("key".toStringz(), cast(void*)key, key.length);
+    params[3] = OSSL_PARAM_construct_octet_string("info".toStringz(), cast(void*)info, info.length);
+    params[4] = OSSL_PARAM_construct_end();
+    if (EVP_KDF_CTX_set_params(kctx, params.ptr) <= 0) {
+        throw new CryptographicException("Unable to set the HKDF parameters.");
+    }
+
+    /* Do the derivation */
+    if (EVP_KDF_derive(kctx, derived.ptr, outputLen, null) <= 0) {
+        throw new CryptographicException("Unable to generate the requested key material.");
+    }
+
+    return derived;
+}
+
+unittest
+{
+    import std.digest;
+    import std.stdio;
+
+    writeln("Testing HKDF Extended with Defaults:");
+
+    ubyte[48] salt = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    ubyte[] vec2 = hkdf_ex(cast(ubyte[])"abc", salt, "", 64, HashAlgorithm.SHA2_384);
+    ubyte[] vec3 = hkdf_ex(cast(ubyte[])"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", salt, "test", 64, HashAlgorithm.SHA2_384);
+
+    writeln(toHexString!(LetterCase.lower)(vec2));
+    writeln(toHexString!(LetterCase.lower)(vec3));
+
+    assert(toHexString!(LetterCase.lower)(vec2) == "65e464a5d7026678a3af78bf0282592472f85ccd7d1040e2dea5cea9218276a960367d418154a1e95019182a3c857286860aa0711955829e896b5bcdb1224794");
+    assert(toHexString!(LetterCase.lower)(vec3) == "12a82466f85ead03f50bb502475b47ec50e7224a90f0219955bf09846ed72791206f6e713a529a0082bf7229093f2b4e6c6b467119518a2579a5b091ebe8ba12");
+}
+
+unittest
+{
+    import std.digest;
+    import std.stdio;
+
+    writeln("Testing HKDF Extended with SHA3_384:");
+
+    ubyte[48] salt = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    ubyte[] vec2 = hkdf_ex(cast(ubyte[])"abc", salt, "", 64, HashAlgorithm.SHA3_384);
+    ubyte[] vec3 = hkdf_ex(cast(ubyte[])"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", salt, "test", 64, HashAlgorithm.SHA3_384);
+
+    writeln(toHexString!(LetterCase.lower)(vec2));
+    writeln(toHexString!(LetterCase.lower)(vec3));
+
+    assert(toHexString!(LetterCase.lower)(vec2) == "41999e49a273f7f1367c7b3c7bd80d56fa27307cdfdf0274c022a0185080ddaa36410a93098f325785e5c27c406df535c91cc47096dc846d5c1dea671a40f944");
+    assert(toHexString!(LetterCase.lower)(vec3) == "15addd263fdab613056a7a82804c1d1c158ea901424d277c25407c15be4b7aa8cad52251de18b3151145035e94c8f360517bda7912d2249f80c9662c1a1cd345");
+}
+
+@safe public KdfResult scrypt(string password) {
+    KdfResult result;
+    result.salt = random(32);
+    result.key = scrypt_ex(password, result.salt, defaultSCryptR, defaultSCryptR, defaultSCryptP, maxSCryptMemory, 64);
+    return result;
+}
+
+@safe public KdfResult scrypt(const ubyte[] password) {
+    KdfResult result;
+    result.salt = random(32);
+    result.key = scrypt_ex(password, result.salt, defaultKdfIterations, defaultSCryptR, defaultSCryptP, maxSCryptMemory, 64);
+    return result;
+}
+
+@trusted public ubyte[] scrypt_ex(string password, const ubyte[] salt, size_t length) {
+    return scrypt_ex(cast(ubyte[])password, salt, defaultKdfIterations, defaultSCryptR, defaultSCryptP, maxSCryptMemory, length);
+}
+
+@trusted public ubyte[] scrypt_ex(string password, const ubyte[] salt, ulong n, ulong r, ulong p, ulong maxMemory, size_t length) {
+    import std.string;
+    return scrypt_ex(cast(ubyte[])password.representation, salt, n, r, p, maxMemory, length);
+}
+
+@trusted public ubyte[] scrypt_ex(const ubyte[] password, const ubyte[] salt, ulong n, ulong r, ulong p, ulong maxMemory, size_t length) {
+    ubyte[] hash = new ubyte[length];
+
+    if (EVP_PBE_scrypt((cast(char[])password).ptr, password.length, salt.ptr, salt.length, n, r, p, maxMemory, hash.ptr, length) <= 0) {
+        throw new CryptographicException("Unable to calculate SCrypt hash.");
+    }
+
+    return hash;
+}
+
+unittest
+{
+    import std.digest;
+    import std.stdio;
+
+    writeln("Testing SCrypt Extended with Defaults:");
+
+    ubyte[48] salt = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    ubyte[] vec2 = scrypt_ex("abc", salt, 1_048_576, 8, 1, 1_074_790_400, 64);
+    ubyte[] vec3 = scrypt_ex("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", salt, 1_048_576, 8, 1, 1_074_790_400, 64);
+
+    writeln(toHexString!(LetterCase.lower)(vec2));
+    writeln(toHexString!(LetterCase.lower)(vec3));
+
+    assert(toHexString!(LetterCase.lower)(vec2) == "134fca5087e04c2a79e0ea2c793660f19d466db74a069e1f2e4da2b177d51402501bd39ffc592b9419ec0280cc17dca7af8df54f836179d69a4b9e9f6b9467fd");
+    assert(toHexString!(LetterCase.lower)(vec3) == "45397ec370eb31f3155ad162d83ec165ff8e363bc4e03c1c61c5a31ad17d0dac51d9e8911f32e9b588adf284a9de24561483dbaf0ea519b6a29ecae77eab5b90");
+}

+ 110 - 0
vtest/source/secured/mac.d

@@ -0,0 +1,110 @@
+module secured.mac;
+
+import std.stdio;
+import std.format;
+
+import secured.openssl;
+import deimos.openssl.evp;
+import secured.hash;
+import secured.util;
+
+
+@safe public ubyte[] hmac(const ubyte[] key, const ubyte[] data) {
+    return hmac_ex(key, data, HashAlgorithm.Default);
+}
+
+@safe public bool hmac_verify(const ubyte[] test, const ubyte[] key, const ubyte[] data) {
+    ubyte[] hash = hmac_ex(key, data, HashAlgorithm.Default);
+    return constantTimeEquality(test, hash);
+}
+
+@trusted public ubyte[] hmac_ex(const ubyte[] key, const ubyte[] data, HashAlgorithm func)
+{
+    if (key.length > getHashLength(func)) {
+        throw new CryptographicException(format("HMAC key must be less than or equal to %s bytes in length.", getHashLength(func)));
+    }
+
+    //Create the OpenSSL context
+    EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
+    if (mdctx == null) {
+        throw new CryptographicException("Unable to create OpenSSL context.");
+    }
+    scope(exit) {
+        if(mdctx !is null) {
+            EVP_MD_CTX_free(mdctx);
+        }
+    }
+
+    //Initialize the hash algorithm
+    auto md = getOpenSSLHashAlgorithm(func);
+    if (EVP_DigestInit_ex(mdctx, md, null) != 1) {
+        throw new CryptographicException("Unable to create hash context.");
+    }
+
+    //Create the HMAC key context
+    auto pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, null, key.ptr, cast(int)key.length);
+    scope(exit) {
+        if(pkey !is null) {
+            EVP_PKEY_free(pkey);
+        }
+    }
+    if (EVP_DigestSignInit(mdctx, null, md, null, pkey) != 1) {
+        throw new CryptographicException("Unable to create HMAC key context.");
+    }
+
+    //Run the provided data through the digest algorithm
+    if (EVP_DigestSignUpdate(mdctx, data.ptr, data.length) != 1) {
+        throw new CryptographicException("Error while updating digest.");
+    }
+
+    //Copy the OpenSSL digest to our D buffer.
+    size_t digestlen = getHashLength(func);
+    ubyte[] digest = new ubyte[getHashLength(func)];
+    if (EVP_DigestSignFinal(mdctx, digest.ptr, &digestlen) < 0) {
+        throw new CryptographicException("Error while retrieving the digest.");
+    }
+
+    return digest;
+}
+
+@safe public bool hmac_verify_ex(const ubyte[] test, const ubyte[] key, const ubyte[] data, HashAlgorithm func){
+    ubyte[] hash = hmac_ex(key, data, func);
+    return constantTimeEquality(test, hash);
+}
+
+unittest {
+    import std.digest;
+
+    ubyte[48] key = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                      0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                      0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    writeln("Testing HMAC Basic:");
+
+    ubyte[] verify_basic_hash = hmac(key, cast(ubyte[])"");
+    assert(hmac_verify(verify_basic_hash, key, cast(ubyte[])""));
+
+    writeln(toHexString!(LetterCase.lower)(verify_basic_hash));
+
+    writeln("Testing HMAC Extended:");
+
+    ubyte[] vec1 = hmac_ex(key, cast(ubyte[])"", HashAlgorithm.SHA2_384);
+    ubyte[] vec2 = hmac_ex(key, cast(ubyte[])"abc", HashAlgorithm.SHA2_384);
+    ubyte[] vec3 = hmac_ex(key, cast(ubyte[])"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", HashAlgorithm.SHA2_384);
+
+    writeln(toHexString!(LetterCase.lower)(vec1));
+    writeln(toHexString!(LetterCase.lower)(vec2));
+    writeln(toHexString!(LetterCase.lower)(vec3));
+
+    assert(toHexString!(LetterCase.lower)(vec1) == "440b0d5f59c32cbee090c3d9f524b81a9b9708e9b65a46bbc189842b0ab0759d3bf118acca58eda0813fd346e8ccfde4");
+    assert(toHexString!(LetterCase.lower)(vec2) == "cb5da1048feb76fd75752dc1b699caba124090feac21adb5b4c0f6600e7b626e08d7415660aa0ee79ca5b83e56669a60");
+    assert(toHexString!(LetterCase.lower)(vec3) == "460b59c0bd8ae48133431185a4583376738be3116cafce47aff7696bd19501b0cf1f1850c3e5fa2992882997493d1c99");
+
+    ubyte[32] keyshort = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                      0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    ubyte[] verify_hash = hmac_ex(keyshort, cast(ubyte[])"", HashAlgorithm.SHA2_256);
+    assert(hmac_verify_ex(verify_hash, keyshort, cast(ubyte[])"", HashAlgorithm.SHA2_256));
+
+    writeln(toHexString!(LetterCase.lower)(verify_hash));
+}

+ 80 - 0
vtest/source/secured/openssl.d

@@ -0,0 +1,80 @@
+module secured.openssl;
+
+import deimos.openssl.evp;
+import core.stdc.stdint;
+
+private:
+
+enum int EVP_PKEY_ALG_CTRL = 0x1000;
+enum int EVP_PKEY_CTRL_HKDF_MD = (EVP_PKEY_ALG_CTRL + 3);
+enum int EVP_PKEY_CTRL_HKDF_SALT = (EVP_PKEY_ALG_CTRL + 4);
+enum int EVP_PKEY_CTRL_HKDF_KEY = (EVP_PKEY_ALG_CTRL + 5);
+enum int EVP_PKEY_CTRL_HKDF_INFO = (EVP_PKEY_ALG_CTRL + 6);
+enum int EVP_PKEY_CTRL_HKDF_MODE = (EVP_PKEY_ALG_CTRL + 7);
+enum int EVP_PKEY_CTRL_PASS = (EVP_PKEY_ALG_CTRL + 8);
+enum int EVP_PKEY_CTRL_SCRYPT_SALT = (EVP_PKEY_ALG_CTRL + 9);
+enum int EVP_PKEY_CTRL_SCRYPT_N = (EVP_PKEY_ALG_CTRL + 10);
+enum int EVP_PKEY_CTRL_SCRYPT_R = (EVP_PKEY_ALG_CTRL + 11);
+enum int EVP_PKEY_CTRL_SCRYPT_P = (EVP_PKEY_ALG_CTRL + 12);
+enum int EVP_PKEY_CTRL_SCRYPT_MAXMEM_BYTES = (EVP_PKEY_ALG_CTRL + 13);
+
+extern (C):
+nothrow:
+public:
+
+ulong ERR_get_error();
+ulong ERR_peek_error();
+void ERR_error_string_n(ulong e, char *buf, size_t len);
+
+void EVP_MD_CIPHER_free(EVP_CIPHER_CTX* free);
+
+struct ossl_param_st {
+    char *key;                  /* the name of the parameter */
+    uint data_type;             /* declare what kind of content is in buffer */
+    void *data;                 /* value being passed in or out */
+    size_t data_size;           /* data size */
+    size_t return_size;         /* returned content size */
+};
+
+ossl_param_st OSSL_PARAM_construct_utf8_string(const char *key, char *buf, size_t bsize);
+ossl_param_st OSSL_PARAM_construct_octet_string(const char *key, void *buf, size_t bsize);
+ossl_param_st OSSL_PARAM_construct_utf8_ptr(const char *key, char **buf, size_t bsize);
+ossl_param_st OSSL_PARAM_construct_octet_ptr(const char *key, void **buf, size_t bsize);
+ossl_param_st OSSL_PARAM_construct_end();
+
+int EVP_KDF_CTX_set_params(EVP_KDF_CTX* ctx, const(ossl_param_st)* params);
+
+const(EVP_MD)* EVP_sha512_224();
+const(EVP_MD)* EVP_sha512_256();
+const(EVP_MD)* EVP_sha3_224();
+const(EVP_MD)* EVP_sha3_256();
+const(EVP_MD)* EVP_sha3_384();
+const(EVP_MD)* EVP_sha3_512();
+
+extern(D):
+
+enum int EVP_PKEY_HKDF = 1036;
+enum int EVP_PKEY_SCRYPT = 973;
+enum int EVP_CTRL_AEAD_SET_IVLEN = 0x9;
+enum int EVP_CTRL_AEAD_GET_TAG = 0x10;
+enum int EVP_CTRL_AEAD_SET_TAG = 0x11;
+
+int EVP_PKEY_CTX_set1_pbe_pass(EVP_PKEY_CTX *pctx, const ubyte[] password) {
+    return EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, EVP_PKEY_CTRL_PASS, cast(int)password.length, cast(void *)(password));
+}
+
+int EVP_PKEY_CTX_set1_scrypt_salt(EVP_PKEY_CTX *pctx, const ubyte[] salt) {
+    return EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, EVP_PKEY_CTRL_SCRYPT_SALT, cast(int)salt.length, cast(void *)(salt));
+}
+
+int EVP_PKEY_CTX_set_scrypt_N(EVP_PKEY_CTX *pctx, ulong n) {
+    return EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, EVP_PKEY_CTRL_SCRYPT_N, 0, cast(void*)n);
+}
+
+int EVP_PKEY_CTX_set_scrypt_r(EVP_PKEY_CTX *pctx, ulong r) {
+    return EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, EVP_PKEY_CTRL_SCRYPT_R, 0, cast(void*)r);
+}
+
+int EVP_PKEY_CTX_set_scrypt_p(EVP_PKEY_CTX *pctx, ulong p) {
+    return EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, EVP_PKEY_CTRL_SCRYPT_P, 0, cast(void*)p);
+}

+ 10 - 0
vtest/source/secured/package.d

@@ -0,0 +1,10 @@
+module secured;
+
+public import secured.ecc;
+public import secured.hash;
+public import secured.kdf;
+public import secured.mac;
+public import secured.random;
+public import secured.rsa;
+public import secured.symmetric;
+public import secured.util;

+ 154 - 0
vtest/source/secured/random.d

@@ -0,0 +1,154 @@
+module secured.random;
+
+import secured.util;
+
+version(CRuntime_Bionic)
+    version = SecureARC4Random;//ChaCha20
+else version(OSX)
+    version = SecureARC4Random;//AES
+else version(OpenBSD)
+    version = SecureARC4Random;//ChaCha20
+else version(NetBSD)
+    version = SecureARC4Random;//ChaCha20
+// Can uncomment following two lines if Solaris versions prior to 11.3 are unsupported:
+//else version (Solaris)
+//    version = SecureARC4Random;
+
+version(SecureARC4Random)
+extern(C) @nogc nothrow private @system
+{
+    void arc4random_buf(scope void* buf, size_t nbytes);
+}
+
+@trusted public ubyte[] random(uint bytes)
+{
+    if (bytes == 0) {
+        throw new CryptographicException("The number of requested bytes must be greater than zero.");
+    }
+    ubyte[] buffer = new ubyte[bytes];
+
+    version(SecureARC4Random)
+    {
+        arc4random_buf(buffer.ptr, bytes);
+    }
+    else version(Posix)
+    {
+        import std.exception;
+        import std.format;
+        import std.stdio;
+
+        try {
+            //Initialize the system random file buffer
+            File urandom = File("/dev/urandom", "rb");
+            urandom.setvbuf(null, _IONBF);
+            scope(exit) urandom.close();
+
+            //Read into the buffer
+            try {
+                buffer = urandom.rawRead(buffer);
+            }
+            catch(ErrnoException ex) {
+                throw new CryptographicException(format("Cannot get the next random bytes. Error ID: %d, Message: %s", ex.errno, ex.msg));
+            }
+            catch(Exception ex) {
+                throw new CryptographicException(format("Cannot get the next random bytes. Message: %s", ex.msg));
+            }
+        }
+        catch(ErrnoException ex) {
+            throw new CryptographicException(format("Cannot initialize the system RNG. Error ID: %d, Message: %s", ex.errno, ex.msg));
+        }
+        catch(Exception ex) {
+            throw new CryptographicException(format("Cannot initialize the system RNG. Message: %s", ex.msg));
+        }
+    }
+    else version(Windows)
+    {
+        import core.sys.windows.windows;
+		import core.sys.windows.wincrypt;
+        import std.format;
+		
+        HCRYPTPROV hCryptProv;
+
+        //Get the cryptographic context from Windows
+        if (!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
+            throw new CryptographicException("Unable to acquire Cryptographic Context.");
+        }
+        //Release the context when finished
+        scope(exit) CryptReleaseContext(hCryptProv, 0);
+
+        //Generate the random bytes
+        if (!CryptGenRandom(hCryptProv, cast(DWORD)buffer.length, buffer.ptr)) {
+            throw new CryptographicException(format("Cannot get the next random bytes. Error ID: %d", GetLastError()));
+        }
+    }
+    else
+    {
+        static assert(0, "SecureD does not support this OS.");
+    }
+
+    return buffer;
+}
+
+unittest
+{
+    import std.digest;
+    import std.stdio;
+
+    writeln("Testing Random Number Generator with 32/64/512/2048 bytes:");
+
+    //Test 32 bytes
+    ubyte[] rnd1 = random(32);
+    writeln("32 Bytes:");
+    writeln(toHexString!(LetterCase.lower)(rnd1));
+    assert(rnd1.length == 32);
+
+    //Test 128 bytes
+    ubyte[] rnd2 = random(128);
+    writeln("128 Bytes:");
+    writeln(toHexString!(LetterCase.lower)(rnd2));
+    assert(rnd2.length == 128);
+
+    //Test 512 bytes
+    ubyte[] rnd3 = random(512);
+    writeln("512 Bytes:");
+    writeln(toHexString!(LetterCase.lower)(rnd3));
+    assert(rnd3.length == 512);
+
+    //Test 2048 bytes
+    ubyte[] rnd4 = random(2048);
+    writeln("2048 Bytes:");
+    writeln(toHexString!(LetterCase.lower)(rnd4));
+    assert(rnd4.length == 2048);
+}
+
+unittest
+{
+    import std.digest;
+    import std.stdio;
+
+    writeln("Testing Random Number Generator for Equality:");
+
+    //Test 32 bytes
+    ubyte[] rnd1 = random(32);
+    ubyte[] rnd2 = random(32);
+    writeln("Testing with 32 Bytes");
+    assert(!constantTimeEquality(rnd1, rnd2));
+
+    //Test 128 bytes
+    rnd1 = random(128);
+    rnd2 = random(128);
+    writeln("Testing with 128 Bytes");
+    assert(!constantTimeEquality(rnd1, rnd2));
+
+    //Test 512 bytes
+    rnd1 = random(512);
+    rnd2 = random(512);
+    writeln("Testing with 512 Bytes");
+    assert(!constantTimeEquality(rnd1, rnd2));
+
+    //Test 2048 bytes
+    rnd1 = random(2048);
+    rnd2 = random(2048);
+    writeln("Testing with 2048 Bytes");
+    assert(!constantTimeEquality(rnd1, rnd2));
+}

+ 653 - 0
vtest/source/secured/rsa.d

@@ -0,0 +1,653 @@
+module secured.rsa;
+
+import core.memory;
+
+import secured.openssl;
+import deimos.openssl.evp;
+import deimos.openssl.rand;
+import deimos.openssl.pem;
+import deimos.openssl.bio;
+import deimos.openssl.rsa;
+import deimos.openssl.engine;
+
+import secured.random;
+import secured.symmetric;
+import secured.util;
+
+// ----------------------------------------------------------
+
+@trusted:
+
+public class RSA
+{
+    private bool _hasPrivateKey;
+    public @property bool hasPrivateKey() { return _hasPrivateKey; }
+
+    static EVP_PKEY *keypair;
+
+    public this(const int RSA_KEYLEN = 4096)
+    {
+        // Reseed the OpenSSL RNG every time we create a new RSA Key to ensure that the result is truely random in threading/forking scenarios.
+        ubyte[] seedbuf = random(32);
+        RAND_seed(seedbuf.ptr, cast(int)seedbuf.length);
+
+        EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, null);
+        if (ctx is null) {
+            throw new CryptographicException("EVP_PKEY_CTX_new_id failed.");
+        }
+        scope(exit) {
+            if (ctx !is null)
+                EVP_PKEY_CTX_free(ctx);
+        }
+
+        if(EVP_PKEY_keygen_init(ctx) <= 0) {
+            throw new CryptographicException("EVP_PKEY_keygen_init failed.");
+        }
+
+        if(EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, RSA_KEYLEN) <= 0) {
+            throw new CryptographicException("EVP_PKEY_CTX_set_rsa_keygen_bits failed.");
+        }
+
+        if(EVP_PKEY_keygen(ctx, &keypair) <= 0) {
+            throw new CryptographicException("EVP_PKEY_keygen failed.");
+        }
+
+        _hasPrivateKey = true;
+    }
+
+    public this(ubyte[] privateKey, ubyte[] password)
+    {
+        // Reseed the OpenSSL RNG every time we load an existing RSA key to ensure that the result is truely random in threading/forking scenarios.
+        ubyte[] seedbuf = random(32);
+        RAND_seed(seedbuf.ptr, cast(int)seedbuf.length);
+
+        _hasPrivateKey = true;
+        ubyte[] pk = cast(ubyte[])privateKey;
+
+        BIO* bio = BIO_new_mem_buf(pk.ptr, cast(int)pk.length);
+        if (password is null) {
+            keypair = PEM_read_bio_PrivateKey(bio, null, null, null);
+        } else {
+            ubyte[] pwd = cast(ubyte[])password;
+            pwd = pwd ~ '\0';
+
+            keypair = PEM_read_bio_PrivateKey(bio, null, null, pwd.ptr);
+        }
+        BIO_free_all(bio);
+    }
+
+    public this(ubyte[] publicKey)
+    {
+        // Reseed the OpenSSL RNG every time we load an existing RSA key to ensure that the result is truely random in threading/forking scenarios.
+        ubyte[] seedbuf = random(32);
+        RAND_seed(seedbuf.ptr, cast(int)seedbuf.length);
+
+        _hasPrivateKey = false;
+        ubyte[] pk = cast(ubyte[])publicKey;
+
+        BIO* bio = BIO_new_mem_buf(pk.ptr, cast(int)pk.length);
+        keypair = PEM_read_bio_PUBKEY(bio, null, null, null);
+        BIO_free_all(bio);
+    }
+
+    public ~this()
+    {
+    }
+
+    ubyte[] seal(const ubyte[] plaintext)
+    {
+        return this.seal(plaintext, SymmetricAlgorithm.AES256_CTR);
+    }
+
+    ubyte[] seal(const ubyte[] plaintext, SymmetricAlgorithm algorithm)
+    {
+        ubyte* _encMsg;
+        ubyte* _ek;
+        size_t _ekl;
+        ubyte* _iv;
+        size_t _ivl;
+
+        ubyte** encMsg    = &_encMsg;
+        ubyte** ek        = &_ek;
+        size_t* ekl        = &_ekl;
+        ubyte** iv        = &_iv;
+        size_t* ivl        = &_ivl;
+
+        // The header, symmetric encryption key ek and initialisation vector iv are prefixed the encrypted message
+        // Having four length bytes in header imposes a 4 GB limit on the plaintext
+
+        const ubyte* msg    = plaintext.ptr;
+        size_t msgLen         = plaintext.length;
+
+        static if(size_t.sizeof == 8) {
+            size_t maxHeaderL    = 2 + 2 + 4; // 2 bytes for actual ekl, 2 bytes for actual ivl and 4 bytes for actual length
+            size_t maxEKL        = EVP_PKEY_get_size(keypair);
+            size_t maxIVL        = EVP_MAX_IV_LENGTH;
+            size_t maxEncMsgLen    = msgLen + EVP_MAX_IV_LENGTH;
+            size_t maxTotalSize    = maxHeaderL + maxEKL + maxIVL + maxEncMsgLen;
+
+            size_t encMsgLen = 0;
+            size_t blockLen  = 0;
+
+            *ivl = EVP_MAX_IV_LENGTH;
+
+            ubyte* buffer = cast(ubyte*)GC.malloc(maxTotalSize);
+            if(buffer == null)
+                throw new CryptographicException("Malloc failed.");
+
+            *ek = buffer + maxHeaderL;
+            *iv = buffer + maxHeaderL + maxEKL;
+            *encMsg = buffer + maxHeaderL + maxEKL + maxIVL;
+version(OpenSSL10) {
+            EVP_CIPHER_CTX *rsaEncryptCtx = cast(EVP_CIPHER_CTX*)GC.malloc(EVP_CIPHER_CTX.sizeof);
+            if(rsaEncryptCtx == null)
+                throw new CryptographicException("Malloc failed.");
+            EVP_CIPHER_CTX_init(rsaEncryptCtx);
+} else {
+            EVP_CIPHER_CTX *rsaEncryptCtx = EVP_CIPHER_CTX_new();
+}
+            scope(exit) {
+                if (rsaEncryptCtx !is null) {
+version(OpenSSL10) {
+                    EVP_CIPHER_CTX_cleanup(rsaEncryptCtx);
+} else {
+                    EVP_CIPHER_CTX_free(rsaEncryptCtx);
+}
+                }
+            }
+
+            if(!EVP_SealInit(rsaEncryptCtx, getOpenSslCipher(algorithm), ek, cast(int*)ekl, *iv, &keypair, 1))
+                throw new CryptographicException("CEVP_SealInit failed.");
+
+            if(!EVP_SealUpdate(rsaEncryptCtx, *encMsg + encMsgLen, cast(int*)&blockLen, cast(const ubyte*)msg, cast(int)msgLen))
+                throw new CryptographicException("EVP_SealUpdate failed.");
+            encMsgLen += blockLen;
+
+            if(!EVP_SealFinal(rsaEncryptCtx, *encMsg + encMsgLen, cast(int*)&blockLen))
+                throw new CryptographicException("EVP_SealFinal failed.");
+            encMsgLen += blockLen;
+
+            buffer[0 .. 2] = (cast(ubyte*)ekl)[0..2];
+            buffer[2 .. 4] = (cast(ubyte*)ivl)[0..2];
+
+            ubyte* encMsgLenTemp = cast(ubyte*)(&encMsgLen);
+            buffer[4..8] = encMsgLenTemp[0..4];
+
+            assert(*ekl == maxEKL);
+            assert(*ivl == maxIVL);
+
+            return buffer[0 .. maxHeaderL + maxEKL + maxIVL + encMsgLen];
+        }
+        else
+            assert(0);
+    }
+
+    ubyte[] open(ubyte[] encMessage)
+    {
+        return this.open(encMessage, SymmetricAlgorithm.AES256_CTR);
+    }
+
+    ubyte[] open(ubyte[] encMessage, SymmetricAlgorithm algorithm)
+    {
+        assert(encMessage.length > 8); // Encrypted message must be larger than header = ekl + ivl + messageLength
+        static if(size_t.sizeof == 8) {
+            // Header: 2 bytes for actual ekl, 2 bytes for actual ivl and 4 bytes for actual length
+            size_t maxHeaderL    = 2 + 2 + 4;
+            size_t maxEKL        = EVP_PKEY_get_size(keypair);
+            size_t maxIVL        = EVP_MAX_IV_LENGTH;
+
+            ubyte* ek            = encMessage.ptr + maxHeaderL;
+            ubyte[8] temp        = 0;
+            temp[0..2]            = encMessage[0..2];
+            int ekl                = (cast(int[])temp)[0];
+
+            ubyte* iv            = encMessage.ptr + maxHeaderL + maxEKL;
+            temp                = 0;
+            temp[0..2]            = encMessage[2..4];
+            size_t ivl            = (cast(int[])temp)[0];
+
+            ubyte* encMsg        = encMessage.ptr + maxHeaderL + maxEKL + maxIVL;
+            temp                = 0;
+            temp[0..4]            = encMessage[4..8];
+            size_t encMsgLen    = (cast(size_t[])temp)[0];
+
+            size_t decLen   = 0;
+            size_t blockLen = 0;
+            EVP_PKEY *key;
+            ubyte* _decMsg;
+            auto decMsg = &_decMsg;
+            *decMsg = cast(ubyte*)GC.malloc(encMsgLen + ivl);
+            if(decMsg == null) {
+                throw new CryptographicException("Malloc failed.");
+            }
+
+version(OpenSSL10) {
+            EVP_CIPHER_CTX *rsaDecryptCtx = cast(EVP_CIPHER_CTX*)GC.malloc(EVP_CIPHER_CTX.sizeof);
+            if(rsaDecryptCtx == null) {
+                throw new CryptographicException("Malloc failed.");
+            }
+            EVP_CIPHER_CTX_init(rsaDecryptCtx);
+} else {
+            EVP_CIPHER_CTX *rsaDecryptCtx = EVP_CIPHER_CTX_new();
+}
+            scope(exit) {
+                if (rsaDecryptCtx !is null) {
+version(OpenSSL10) {
+                    EVP_CIPHER_CTX_cleanup(rsaDecryptCtx);
+} else {
+                    EVP_CIPHER_CTX_free(rsaDecryptCtx);
+}
+                }
+            }
+
+            if(!EVP_OpenInit(rsaDecryptCtx, getOpenSslCipher(algorithm), ek, ekl, iv, keypair))
+                throw new CryptographicException("EVP_OpenInit failed.");
+
+            if(!EVP_OpenUpdate(rsaDecryptCtx, cast(ubyte*)*decMsg + decLen, cast(int*)&blockLen, encMsg, cast(int)encMsgLen))
+                throw new CryptographicException("EVP_OpenUpdate failed.");
+            decLen += blockLen;
+
+            if(!EVP_OpenFinal(rsaDecryptCtx, cast(ubyte*)*decMsg + decLen, cast(int*)&blockLen))
+                throw new CryptographicException("EVP_OpenFinal failed.");
+            decLen += blockLen;
+
+            return (*decMsg)[0 .. decLen];
+        }
+        else
+            assert(0);
+    }
+
+    ubyte[] getPublicKey()
+    {
+        BIO* bio = BIO_new(BIO_s_mem());
+
+        PEM_write_bio_PUBKEY(bio, keypair);
+
+        ubyte[] buffer = new ubyte[BIO_ctrl_pending(bio)];
+        BIO_read(bio, buffer.ptr, cast(int)buffer.length);
+        BIO_free_all(bio);
+
+        return buffer;
+    }
+
+    public ubyte[] getPrivateKey(string password, int iterations = 25000, bool use3Des = false)
+    {
+        if (!_hasPrivateKey) {
+            return null;
+        }
+
+        BIO* bio = BIO_new(BIO_s_mem());
+
+        if (password is null) {
+            PEM_write_bio_PKCS8PrivateKey(bio, keypair, null, null, 0, null, null);
+        } else {
+            ubyte[] pwd = cast(ubyte[])password;
+            pwd = pwd ~ '\0';
+
+            PEM_write_bio_PKCS8PrivateKey(
+                bio,
+                keypair,
+                !use3Des ? EVP_aes_256_cbc() : EVP_des_ede3_cbc(),
+                null,
+                0,
+                null,
+                pwd.ptr);
+        }
+
+        if(BIO_ctrl_pending(bio) == 0) {
+            throw new CryptographicException("No private key written.");
+        }
+
+        ubyte[] buffer = new ubyte[BIO_ctrl_pending(bio)];
+        BIO_read(bio, buffer.ptr, cast(int)buffer.length);
+        BIO_free_all(bio);
+
+        return buffer;
+    }
+
+    ubyte[] encrypt(const ubyte[] inMessage)
+    in
+    {
+        import std.exception: enforce;
+        enforce(inMessage.length <= (EVP_PKEY_get_size(keypair) - 42), new CryptographicException("Plainttext length exceeds allowance")); // 42 being the padding overhead for OAEP padding using SHA-1
+    }
+    body
+    {
+        EVP_PKEY_CTX *ctx;
+        ENGINE *eng = null; // Use default RSA implementation
+        ubyte *out2;
+        const ubyte *in2 = inMessage.ptr;
+        size_t outlen;
+        size_t inlen = inMessage.length;
+
+        ctx = EVP_PKEY_CTX_new(keypair,eng);
+        if (!ctx)  {
+            throw new CryptographicException("EVP_PKEY_CTX_new.");
+        }
+        scope(exit) {
+            if (ctx !is null) {
+                EVP_PKEY_CTX_free(ctx);
+            }
+        }
+
+        if (EVP_PKEY_encrypt_init(ctx) <= 0) {
+            throw new CryptographicException("EVP_PKEY_encrypt_init failed.");
+        }
+
+        if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
+            throw new CryptographicException("EVP_PKEY_CTX_set_rsa_padding failed.");
+        }
+
+        if (EVP_PKEY_encrypt(ctx, null, &outlen, in2, inlen) <= 0) {
+            throw new CryptographicException("EVP_PKEY_encrypt failed.");
+        }
+
+        out2 = cast(ubyte*)GC.malloc(outlen);
+        if(out2 == null) {
+            throw new CryptographicException("Malloc failed.");
+        }
+
+        if (EVP_PKEY_encrypt(ctx, out2, &outlen, in2, inlen) <= 0) {
+            throw new CryptographicException("EVP_PKEY_encrypt failed.");
+        }
+
+        return (out2)[0 .. outlen];
+    }
+
+    ubyte[] decrypt(const ubyte[] inMessage)
+    in
+    {
+        assert(inMessage.length == EVP_PKEY_get_size(keypair));  // Should always hold as padding was added during encryption
+    }
+    body
+    {
+        EVP_PKEY_CTX *ctx;
+        ENGINE *eng = null; // Use default RSA implementation
+        ubyte *out2;
+        const ubyte *in2 = inMessage.ptr;
+        size_t outlen;
+        size_t inlen = inMessage.length;
+
+        ctx = EVP_PKEY_CTX_new(keypair,eng);
+        if (!ctx) {
+            throw new CryptographicException("EVP_PKEY_CTX_new failed");
+        }
+        scope(exit) {
+            if (ctx !is null) {
+                EVP_PKEY_CTX_free(ctx);
+            }
+        }
+
+        if (EVP_PKEY_decrypt_init(ctx) <= 0) {
+            throw new CryptographicException("EVP_PKEY_decrypt_init failed.");
+        }
+
+        if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
+            throw new CryptographicException("EVP_PKEY_CTX_set_rsa_padding failed.");
+        }
+
+        if (EVP_PKEY_decrypt(ctx, null, &outlen, in2, inlen) <= 0) {
+            throw new CryptographicException("EVP_PKEY_decrypt failed.");
+        }
+
+        out2 = cast(ubyte*)GC.malloc(outlen);
+        if(out2 == null) {
+            throw new CryptographicException("Malloc failed.");
+        }
+
+        if (EVP_PKEY_decrypt(ctx, out2, &outlen, in2, inlen) <= 0) {
+            throw new CryptographicException("EVP_PKEY_encrypt failed.");
+        }
+
+        return (out2)[0 .. outlen];
+    }
+
+    public ubyte[] sign(ubyte[] data, bool useSha256 = false)
+    out (signature)
+    {
+        assert(signature.length == EVP_PKEY_get_size(keypair));
+    }
+    body
+    {
+        EVP_MD_CTX *mdctx = null;
+
+        mdctx = EVP_MD_CTX_new();
+        if (mdctx is null) {
+            throw new CryptographicException("Unable to create the MD signing context.");
+        }
+        scope(exit) {
+            if (mdctx !is null) {
+                EVP_MD_CTX_free(mdctx);
+            }
+        }
+
+        auto alg = (!useSha256 ? EVP_sha384() : EVP_sha256());
+
+        if (EVP_DigestSignInit(mdctx, null, alg, null, keypair) != 1) {
+            throw new CryptographicException("Unable to initialize the signing digest.");
+        }
+
+        if (EVP_DigestSignUpdate(mdctx, data.ptr, data.length) != 1) {
+            throw new CryptographicException("Unable to set sign data.");
+        }
+
+        size_t signlen = 0;
+        if (EVP_DigestSignFinal(mdctx, null, &signlen) != 1) {
+            throw new CryptographicException("Unable to calculate signature length.");
+        }
+
+        ubyte[] sign = new ubyte[signlen];
+        if (EVP_DigestSignFinal(mdctx, sign.ptr, &signlen) != 1) {
+            throw new CryptographicException("Unable to finalize signature");
+        }
+
+
+        return sign[0..signlen];
+    }
+
+    public bool verify(ubyte[] data, ubyte[] signature, bool useSha256 = false)
+    in
+    {
+        assert(signature.length == EVP_PKEY_get_size(keypair));
+    }
+    body
+    {
+        EVP_MD_CTX *mdctx = null;
+
+        mdctx = EVP_MD_CTX_new();
+        if (mdctx is null) {
+            throw new CryptographicException("Unable to create the MD signing context.");
+        }
+        scope(exit) {
+            if (mdctx !is null) {
+                EVP_MD_CTX_free(mdctx);
+            }
+        }
+
+        auto alg = (!useSha256 ? EVP_sha384() : EVP_sha256());
+
+        if (EVP_DigestVerifyInit(mdctx, null, alg, null, keypair) != 1) {
+            throw new CryptographicException("Unable to initialize the verification digest.");
+        }
+
+        if (EVP_DigestVerifyUpdate(mdctx, data.ptr, data.length) != 1) {
+            throw new CryptographicException("Unable to set verify data.");
+        }
+
+        int ret = EVP_DigestVerifyFinal(mdctx, signature.ptr, signature.length);
+
+        return ret == 1;
+    } // verify()
+
+} // class RSA
+
+
+// ----------------------------------------------------------
+// UNITTESTING BELOW
+// ----------------------------------------------------------
+
+unittest
+{
+    import std.stdio;
+    writeln("Testing seal and open functions:");
+
+    auto keypair = new RSA();
+    scope(exit) keypair.destroy();
+
+       ubyte[] plaintext = cast(ubyte[])"This is a test This is a test This is a test This is a test";
+
+    ubyte[] encMessage = keypair.seal(plaintext);
+    ubyte[] decMessage = keypair.open(encMessage);
+
+    assert(plaintext.length    == decMessage.length);
+    assert(plaintext        == decMessage);
+}
+
+// ----------------------------------------------------------
+
+unittest
+{
+    import std.stdio;
+    writeln("Testing getXxxKey functions and constructors:");
+
+    auto keypairA = new RSA();
+    scope(exit) keypairA.destroy();
+
+    auto privateKeyA = keypairA.getPrivateKey(null);
+    auto publicKeyA  = keypairA.getPublicKey();
+
+       ubyte[] plaintext = cast(ubyte[])"This is a test This is a test This is a test This is a test";
+
+    // Creating key from public key only
+    auto keypairB = new RSA(publicKeyA);
+    scope(exit) keypairB.destroy();
+
+    auto privateKeyB = keypairB.getPrivateKey(null);
+    auto publicKeyB  = keypairB.getPublicKey();
+
+    assert(privateKeyA    != privateKeyB,    "Private keys A and B match - they should NOT do so");
+    assert(publicKeyA     == publicKeyB,    "Public  keys A and B does not match");
+
+    //  Creating key from private key only
+    auto keypairC = new RSA(privateKeyA, null);
+    scope(exit) keypairC.destroy();
+
+    auto publicKeyC     = keypairC.getPublicKey();
+    auto privateKeyC = keypairC.getPrivateKey(null);
+
+    assert(privateKeyA    == privateKeyC,    "Private keys A and C does not match");
+    assert(publicKeyA     == publicKeyC,    "Public  keys A and C does not match");
+}
+
+// ----------------------------------------------------------
+
+unittest
+{
+    import std.stdio;
+    writeln("Testing sealing and opening with keys, which have been constructed on getXxxKey output:");
+
+    auto keypairA = new RSA();
+    scope(exit)        keypairA.destroy();
+
+    auto privateKeyA = keypairA.getPrivateKey(null);
+    auto publicKeyA  = keypairA.getPublicKey();
+
+       ubyte[] plaintext = cast(ubyte[])"This is a test This is a test This is a test This is a test";
+
+    // Creating key from public key only
+    auto keypairB        =  new RSA(publicKeyA);
+    scope(exit)               keypairB.destroy();
+
+    auto publicKeyB        =  keypairB.getPublicKey();
+    assert(publicKeyA     == publicKeyB,    "Public  keys A and B does not match");
+
+    //  Creating key from private key only
+    auto keypairC        =  new RSA(privateKeyA, null);
+    scope(exit)               keypairC.destroy();
+
+    auto privateKeyC    =  keypairC.getPrivateKey(null);
+    assert(privateKeyA    == privateKeyC,    "Private keys A and C does not match");
+
+    // Sealing plaintext using public key
+    ubyte[] encMessage    = keypairB.seal(plaintext);
+    // Opening encrypted message using private key
+    ubyte[] decMessage    = keypairC.open(encMessage);
+
+    assert(plaintext.length    == decMessage.length);
+    assert(plaintext        == decMessage);
+}
+
+// ----------------------------------------------------------
+
+unittest
+{
+    import std.stdio;
+    writeln("Testing RSA only encrypt/decrypt functions:");
+
+    auto keypair = new RSA();
+    scope(exit) keypair.destroy();
+
+    ubyte[48] plaintext = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                            0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                            0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    ubyte[] encMessage = keypair.encrypt(plaintext);
+    ubyte[] decMessage = keypair.decrypt(encMessage);
+
+    assert(plaintext.length    == decMessage.length);
+    assert(plaintext        == decMessage);
+}
+
+// ----------------------------------------------------------
+
+unittest
+{
+    import std.stdio;
+    writeln("Testing RSA encrypt/decrypt limit:");
+
+    auto keypair = new RSA(2048);  // Only allows for (2048/8)-42 = 214 bytes to be asymmetrically RSA encrypted
+    scope(exit) keypair.destroy();
+
+    // This should work
+    ubyte[214] plaintext214 = 2; // 2 being an arbitrary value
+
+    ubyte[] encMessage214 = keypair.encrypt(plaintext214);
+    assert(encMessage214.length == 2048 / 8);
+
+    ubyte[] decMessage214 = keypair.decrypt(encMessage214);
+
+    assert(plaintext214.length    == decMessage214.length);
+    assert(plaintext214            == decMessage214);
+
+    // This should NOT work, as the plaintext is larger that allowed for this 2048 bit RSA keypair
+    ubyte[215] plaintext215 = 2; // 2 being an arbitrary value
+
+    import std.exception: assertThrown;
+    assertThrown!CryptographicException(keypair.encrypt(plaintext215));
+}
+
+// ----------------------------------------------------------
+
+unittest
+{
+    import std.stdio;
+    writeln("Testing RSA Signing/Verification:");
+
+    import std.digest;
+
+    auto keypair = new RSA();
+    scope(exit) keypair.destroy();
+
+    ubyte[48] data = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    ubyte[48] data2 = [ 0x1, 0x2, 0x3, 0x4, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                        0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                        0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+
+    ubyte[] sig = keypair.sign(data);
+    writeln("Signature: ", toHexString!(LetterCase.lower)(sig));
+    assert(keypair.verify(data, sig));
+    assert(!keypair.verify(data2, sig));
+}

+ 427 - 0
vtest/source/secured/symmetric.d

@@ -0,0 +1,427 @@
+module secured.symmetric;
+
+import std.base64;
+import std.conv;
+import std.stdio;
+import std.string;
+
+import deimos.openssl.evp;
+
+import secured.hash;
+import secured.mac;
+import secured.kdf;
+import secured.random;
+import secured.util;
+import secured.openssl;
+
+public enum SymmetricAlgorithm : ubyte {
+    AES128_GCM,
+    AES128_CTR,
+    AES128_CFB,
+    AES128_CBC,
+    AES192_GCM,
+    AES192_CTR,
+    AES192_CFB,
+    AES192_CBC,
+    AES256_GCM,
+    AES256_CTR,
+    AES256_CFB,
+    AES256_CBC,
+    ChaCha20,
+    ChaCha20_Poly1305,
+    Default = AES256_GCM,
+}
+
+public immutable struct EncryptedData {
+    public immutable ubyte[] iv;
+    public immutable ubyte[] cipherText;
+    public immutable ubyte[] authTag;
+
+    private immutable SymmetricAlgorithm algorithm;
+    private immutable HashAlgorithm hashAlgorithm;
+
+    @safe public this(const string encoded, SymmetricAlgorithm algorithm = SymmetricAlgorithm.Default, HashAlgorithm hashAlgorithm = HashAlgorithm.Default) {
+        this.algorithm = algorithm;
+        this.hashAlgorithm = hashAlgorithm;
+        this(Base64.decode(encoded), getCipherIVLength(algorithm), getAuthLength(algorithm, hashAlgorithm), algorithm, hashAlgorithm);
+    }
+
+    @trusted public this(const ubyte[] rawCiphertext, size_t ivLength, size_t authTagLength, SymmetricAlgorithm algorithm = SymmetricAlgorithm.Default, HashAlgorithm hashAlgorithm = HashAlgorithm.Default) {
+        if (rawCiphertext.length <= ivLength + authTagLength)
+            throw new CryptographicException("Incorrect ciphertext length");
+
+        this.algorithm = algorithm;
+        this.hashAlgorithm = hashAlgorithm;
+
+        this.iv         = cast(immutable) rawCiphertext[0 .. ivLength];
+        this.cipherText = cast(immutable) rawCiphertext[ivLength .. $-authTagLength];
+        this.authTag    = cast(immutable) rawCiphertext[$-authTagLength .. $];
+    }
+
+    @trusted public this(const ubyte[] cipherText, const ubyte[] iv, const ubyte[] authTag, SymmetricAlgorithm algorithm = SymmetricAlgorithm.Default, HashAlgorithm hashAlgorithm = HashAlgorithm.Default) {
+        this.iv            = cast(immutable) iv;
+        this.cipherText    = cast(immutable) cipherText;
+        this.authTag       = cast(immutable) authTag;
+        this.algorithm     = algorithm;
+        this.hashAlgorithm = hashAlgorithm;
+    }
+
+    public string toString() {
+        return to!string(Base64.encode(iv ~ cipherText ~ authTag));
+    }
+}
+
+public struct SymmetricKey {
+    package ubyte[] value;
+    package SymmetricAlgorithm algorithm;
+    @disable this();
+
+    public @property ubyte[] key() { return value; }
+
+    public string toString() {
+        return Base64.encode(value);
+    }
+}
+
+
+public struct SymmetricKeyIV{
+  ubyte[] key;
+  ubyte[] iv;
+  SymmetricAlgorithm algorithm;
+}
+
+@safe public SymmetricKeyIV generateSymmetricKeyIV(SymmetricAlgorithm algorithm = SymmetricAlgorithm.AES256_CBC){
+  SymmetricKeyIV KeyIV = { key : random(getCipherKeyLength(algorithm)), iv : random(getCipherIVLength(algorithm)), algorithm: algorithm };
+  return KeyIV;
+}
+
+@safe public SymmetricKey generateSymmetricKey(SymmetricAlgorithm algorithm = SymmetricAlgorithm.Default) {
+    SymmetricKey key = SymmetricKey.init;
+    key.value = random(getCipherKeyLength(algorithm));
+    key.algorithm = algorithm;
+    return key;
+}
+
+@safe public SymmetricKey generateSymmetricKey(const string password, SymmetricAlgorithm algorithm = SymmetricAlgorithm.Default, KdfAlgorithm kdf = KdfAlgorithm.Default) {
+    SymmetricKey key = SymmetricKey.init;
+    key.algorithm = algorithm;
+    if (kdf == KdfAlgorithm.SCrypt) {
+        key.value = scrypt_ex(password, null, getCipherKeyLength(algorithm));
+    } else if (kdf == KdfAlgorithm.PBKDF2) {
+        key.value = pbkdf2_ex(password, null, HashAlgorithm.Default, getCipherKeyLength(algorithm), defaultKdfIterations);
+    } else {
+        throw new CryptographicException("Specified KDF '" ~ to!string(kdf) ~ "' is not supported.");
+    }
+    return key;
+}
+
+@trusted public SymmetricKey initializeSymmetricKey(const ubyte[] bytes, SymmetricAlgorithm algorithm = SymmetricAlgorithm.Default) {
+    if (bytes.length != (getCipherKeyLength(algorithm))) {
+        throw new CryptographicException("Encryption Key must be " ~ to!string(getCipherKeyLength(algorithm)) ~ " bytes in length.");
+    }
+    SymmetricKey key = SymmetricKey.init;
+    key.value = cast(ubyte[])bytes;
+    key.algorithm = algorithm;
+    return key;
+}
+
+pragma(inline) @safe private ubyte[] deriveKey(const ubyte[] key, uint bytes, const ubyte[] salt, HashAlgorithm hash = HashAlgorithm.Default) {
+    return hkdf_ex(key, salt, string.init, bytes, hash);
+}
+
+@safe public EncryptedData encrypt(const ubyte[] key, const ubyte[] iv, const ubyte[] data, SymmetricAlgorithm algorithm){
+    ubyte[] authTag = [];
+    const ubyte[] associatedData = null;
+    ubyte[] result = encrypt_ex(data, associatedData, key, iv, authTag, algorithm);
+    return EncryptedData(result, iv, authTag, algorithm);
+}
+
+@safe public EncryptedData encrypt(const SymmetricKey key, const ubyte[] data, const ubyte[] associatedData = null) {
+    ubyte[] iv = random(getCipherIVLength(key.algorithm));
+    ubyte[] derived = deriveKey(key.value, getCipherKeyLength(key.algorithm), iv);
+    ubyte[] authTag;
+    ubyte[] result = encrypt_ex(data, associatedData, derived, iv, authTag, key.algorithm);
+    return EncryptedData(result, iv, authTag, key.algorithm);
+}
+
+@trusted public ubyte[] encrypt_ex(const ubyte[] data, const ubyte[] associatedData, const ubyte[] encryptionKey, const ubyte[] iv, out ubyte[] authTag, SymmetricAlgorithm algorithm) {
+    if (encryptionKey.length != getCipherKeyLength(algorithm)) {
+        throw new CryptographicException("Encryption Key must be " ~ to!string(getCipherKeyLength(algorithm)) ~ " bytes in length.");
+    }
+    if (iv.length != getCipherIVLength(algorithm)) {
+        throw new CryptographicException("IV must be " ~ to!string(getCipherIVLength(algorithm)) ~ " bytes in length.");
+    }
+
+    //Get the OpenSSL cipher context
+    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+    if (ctx is null) {
+        throw new CryptographicException("Cannot get an OpenSSL cipher context.");
+    }
+    scope(exit) {
+        if (ctx !is null) {
+            EVP_CIPHER_CTX_free(ctx);
+        }
+    }
+
+    //Initialize the cipher context
+    if (EVP_EncryptInit_ex(ctx, getOpenSslCipher(algorithm), null, encryptionKey.ptr, iv.ptr) != 1) {
+        throw new CryptographicException("Cannot initialize the OpenSSL cipher context.");
+    }
+
+    //Write the additional data to the cipher context, if any
+    if (associatedData !is null && isAeadCipher(algorithm)) {
+        int aadLen = 0;
+        if (EVP_EncryptUpdate(ctx, null, &aadLen, associatedData.ptr, cast(int)associatedData.length) != 1) {
+            throw new CryptographicException("Unable to write bytes to cipher context.");
+        }
+    }
+
+    //Write data to the cipher context
+    int written = 0;
+    int len = 0;
+    ubyte[] output = new ubyte[data.length + 32];
+    if (EVP_EncryptUpdate(ctx, &output[written], &len, data.ptr, cast(int)data.length) != 1) {
+        throw new CryptographicException("Unable to write bytes to cipher context.");
+    }
+    written += len;
+
+    //Extract the complete ciphertext
+    if (EVP_EncryptFinal_ex(ctx, &output[written], &len) != 1) {
+        throw new CryptographicException("Unable to extract the ciphertext from the cipher context.");
+    }
+
+    written += len;
+    ubyte[] result = output[0..written];
+
+    //Extract the auth tag
+    if (isAeadCipher(algorithm)) {
+        ubyte[] _auth = new ubyte[getAuthLength(algorithm)];
+        if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, getAuthLength(algorithm), _auth.ptr) != 1) {
+            throw new CryptographicException("Unable to extract the authentication tag from the cipher context.");
+        }
+        authTag = _auth;
+    } else {
+        authTag = hmac(iv, hash(result) ~ hash(associatedData));
+    }
+
+    return result;
+}
+
+@safe public ubyte[] decrypt(const ubyte[] key, const ubyte[] iv, const ubyte[] data, SymmetricAlgorithm algorithm){
+    const ubyte[] associatedData = [];
+    const ubyte[] authTag = [];
+    return decrypt_ex(data, associatedData, key, iv, authTag, algorithm);
+}
+
+@safe public ubyte[] decrypt(const SymmetricKey key, const EncryptedData data, const ubyte[] associatedData = null) {
+    if (data.algorithm != key.algorithm)
+        throw new CryptographicException("Key and data algorithms don't match");
+    ubyte[] derived = deriveKey(key.value, getCipherKeyLength(key.algorithm), data.iv);
+    return decrypt_ex(data.cipherText, associatedData, derived, data.iv, data.authTag, key.algorithm);
+}
+
+@trusted public ubyte[] decrypt_ex(const ubyte[] data, const ubyte[] associatedData, const ubyte[] encryptionKey, const ubyte[] iv, const ubyte[] authTag, SymmetricAlgorithm algorithm) {
+    if (encryptionKey.length != getCipherKeyLength(algorithm)) {
+        throw new CryptographicException("Encryption Key must be " ~ to!string(getCipherKeyLength(algorithm)) ~ " bytes in length.");
+    }
+    if (iv.length != getCipherIVLength(algorithm)) {
+        throw new CryptographicException("IV must be " ~ to!string(getCipherIVLength(algorithm)) ~ " bytes in length.");
+    }
+
+    if(!isAeadCipher(algorithm)){
+      // do not make HMAC check for not-AEAD (like AES-CBC)
+    }else{
+      if(!hmac_verify(authTag, iv, hash(data) ~ hash(associatedData))){
+        throw new CryptographicException("Failed to verify the authTag.");
+      }
+    }
+
+    //Get the OpenSSL cipher context
+    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+    if (ctx is null) {
+        throw new CryptographicException("Cannot get an OpenSSL cipher context.");
+    }
+    scope(exit) {
+        if (ctx !is null) {
+            EVP_CIPHER_CTX_free(ctx);
+        }
+    }
+
+    //Initialize the cipher context
+    if (!EVP_DecryptInit_ex(ctx, getOpenSslCipher(algorithm), null, encryptionKey.ptr, iv.ptr)) {
+        throw new CryptographicException("Cannot initialize the OpenSSL cipher context.");
+    }
+
+    //Write the additional data to the cipher context, if any
+    if (associatedData.length != 0 && isAeadCipher(algorithm)) {
+        int aadLen = 0;
+        if (!EVP_DecryptUpdate(ctx, null, &aadLen, associatedData.ptr, cast(int)associatedData.length)) {
+            throw new CryptographicException("Unable to write bytes to cipher context.");
+        }
+    }
+
+    //Use the supplied tag to verify the message
+    if (isAeadCipher(algorithm)) {
+        if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, cast(int)authTag.length, (cast(ubyte[])authTag).ptr)) {
+            throw new CryptographicException("Unable to set the authentication tag on the cipher context.");
+        }
+    }
+
+    //Write data to the cipher context
+    int written = 0;
+    int len = 0;
+    ubyte[] output = new ubyte[data.length + 32];
+    if (!EVP_DecryptUpdate(ctx, &output[written], &len, data.ptr, cast(int)data.length)) {
+        throw new CryptographicException("Unable to write bytes to cipher context.");
+    }
+    written += len;
+
+    //Extract the complete plaintext
+    if (EVP_DecryptFinal_ex(ctx, &output[written], &len) <= 0) {
+        throw new CryptographicException("Unable to extract the plaintext from the cipher context.");
+    }
+    written += len;
+
+    return output[0..written];
+}
+
+@trusted package const(EVP_CIPHER*) getOpenSslCipher(SymmetricAlgorithm algo) {
+    switch(algo) {
+        case SymmetricAlgorithm.AES128_GCM: return EVP_aes_128_gcm();
+        case SymmetricAlgorithm.AES192_GCM: return EVP_aes_192_gcm();
+        case SymmetricAlgorithm.AES256_GCM: return EVP_aes_256_gcm();
+        case SymmetricAlgorithm.AES128_CTR: return EVP_aes_128_ctr();
+        case SymmetricAlgorithm.AES192_CTR: return EVP_aes_192_ctr();
+        case SymmetricAlgorithm.AES256_CTR: return EVP_aes_256_ctr();
+        case SymmetricAlgorithm.AES128_CFB: return EVP_aes_128_cfb();
+        case SymmetricAlgorithm.AES192_CFB: return EVP_aes_192_cfb();
+        case SymmetricAlgorithm.AES256_CFB: return EVP_aes_256_cfb();
+        case SymmetricAlgorithm.AES128_CBC: return EVP_aes_128_cbc();
+        case SymmetricAlgorithm.AES192_CBC: return EVP_aes_192_cbc();
+        case SymmetricAlgorithm.AES256_CBC: return EVP_aes_256_cbc();
+        case SymmetricAlgorithm.ChaCha20: return EVP_chacha20();
+        case SymmetricAlgorithm.ChaCha20_Poly1305: return EVP_chacha20_poly1305();
+        default: return EVP_aes_256_gcm();
+    }
+}
+
+@safe package bool isAeadCipher(SymmetricAlgorithm algo) {
+    switch(algo) {
+        case SymmetricAlgorithm.AES128_GCM: return true;
+        case SymmetricAlgorithm.AES192_GCM: return true;
+        case SymmetricAlgorithm.AES256_GCM: return true;
+        case SymmetricAlgorithm.ChaCha20_Poly1305: return true;
+        default: return false;
+    }
+}
+
+@safe package uint getCipherKeyLength(SymmetricAlgorithm algo) {
+    switch(algo) {
+        case SymmetricAlgorithm.AES128_GCM: return 16;
+        case SymmetricAlgorithm.AES192_GCM: return 24;
+        case SymmetricAlgorithm.AES256_GCM: return 32;
+        case SymmetricAlgorithm.AES128_CTR: return 16;
+        case SymmetricAlgorithm.AES192_CTR: return 24;
+        case SymmetricAlgorithm.AES256_CTR: return 32;
+        case SymmetricAlgorithm.AES128_CFB: return 16;
+        case SymmetricAlgorithm.AES192_CFB: return 24;
+        case SymmetricAlgorithm.AES256_CFB: return 32;
+        case SymmetricAlgorithm.AES128_CBC: return 16;
+        case SymmetricAlgorithm.AES192_CBC: return 24;
+        case SymmetricAlgorithm.AES256_CBC: return 32;
+        case SymmetricAlgorithm.ChaCha20: return 32;
+        case SymmetricAlgorithm.ChaCha20_Poly1305: return 32;
+        default: return 16;
+    }
+}
+
+@safe package uint getCipherIVLength(SymmetricAlgorithm algo) {
+    switch(algo) {
+        case SymmetricAlgorithm.AES128_GCM: return 12;
+        case SymmetricAlgorithm.AES192_GCM: return 12;
+        case SymmetricAlgorithm.AES256_GCM: return 12;
+        case SymmetricAlgorithm.ChaCha20: return 12;
+        case SymmetricAlgorithm.ChaCha20_Poly1305: return 12;
+        default: return 16;
+    }
+}
+
+@safe package uint getAuthLength(SymmetricAlgorithm symmetric, HashAlgorithm hash = HashAlgorithm.Default) {
+    switch(symmetric) {
+        case SymmetricAlgorithm.AES128_GCM: return 16;
+        case SymmetricAlgorithm.AES192_GCM: return 16;
+        case SymmetricAlgorithm.AES256_GCM: return 16;
+        case SymmetricAlgorithm.ChaCha20_Poly1305: return 16;
+        default: return getHashLength(hash);
+    }
+}
+
+unittest
+{
+    import std.digest;
+    import std.stdio;
+
+    ubyte[] input = cast(ubyte[])"The quick brown fox jumps over the lazy dog.";
+    SymmetricKey key = initializeSymmetricKey([ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                                                0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ]);
+
+    SymmetricKey generateTest = generateSymmetricKey();
+	assert(generateTest.value.length == 32);
+    SymmetricKey passwordTest = generateSymmetricKey("Test Password");
+    writeln("Password Key: ", toHexString!(LetterCase.lower)(passwordTest.value));
+	assert(toHexString!(LetterCase.lower)(passwordTest.value) == "76ae6c580be5e707a5cef313d2161899cd596c8c635671c9904602f8312cca34");
+
+    writeln("Testing Encryption (No Additional Data)");
+    writeln("Encryption Input: ", cast(string)input);
+    EncryptedData enc = encrypt(key, input);
+    writeln("Encryption Output: ", toHexString!(LetterCase.lower)(enc.cipherText));
+    writeln("AuthTag: ", toHexString!(LetterCase.lower)(enc.authTag));
+
+    writeln("Testing Decryption (No Additional Data)");
+    writeln("Decryption Input:  ", toHexString!(LetterCase.lower)(enc.cipherText));
+    ubyte[] dec = decrypt(key, enc);
+    writeln("Decryption Output: ", cast(string)dec);
+
+    assert((cast(string)dec) == cast(string)input);
+
+    string encoded = enc.toString();
+    writeln("Base64 Encoded: ", encoded);
+    EncryptedData test = EncryptedData(encoded);
+    ubyte[] eddec = decrypt(key, test);
+    writeln("Decryption Output:  ", cast(string)eddec);
+    assert((cast(string)eddec) == cast(string)input);
+
+    ubyte[] ad = cast(ubyte[])"Associated Data";
+
+    writeln("Testing Encryption (With Additional Data)");
+    writeln("Encryption Input: ", cast(string)input);
+    writeln("Encryption AD: ", cast(string)ad);
+    EncryptedData enc2 = encrypt(key, input, ad);
+    writeln("Encryption Output: ", toHexString!(LetterCase.lower)(enc2.cipherText));
+    writeln("AuthTag: ", toHexString!(LetterCase.lower)(enc2.authTag));
+
+    writeln("Testing Decryption (With Additional Data)");
+    writeln("Decryption Input:  ", toHexString!(LetterCase.lower)(enc2.cipherText));
+    writeln("Decryption AD: ", cast(string)ad);
+    ubyte[] dec2 = decrypt(key, enc2, ad);
+    writeln("Decryption Output: ", cast(string)dec2);
+
+    assert((cast(string)dec2) == cast(string)input);
+
+    writeln("Testing Non-AEAD Encryption (With Additional Data)");
+    writeln("Encryption Input: ", cast(string)input);
+    writeln("Encryption AD: ", cast(string)ad);
+    SymmetricKey nonAeadKey = generateSymmetricKey(SymmetricAlgorithm.AES256_CBC);
+    EncryptedData enc3 = encrypt(nonAeadKey, input, ad);
+    writeln("Encryption Output: ", enc3);
+    writeln("IV: ", toHexString!(LetterCase.lower)(enc3.iv));
+    writeln("AuthTag: ", toHexString!(LetterCase.lower)(enc3.authTag));
+
+    writeln("Testing Non-AEAD Decryption (With Additional Data)");
+    writeln("Decryption Input:  ", enc3);
+    writeln("Decryption AD: ", cast(string)ad);
+    ubyte[] dec3 = decrypt(nonAeadKey, enc3, ad);
+    writeln("Decryption Output: ", cast(string)dec3);
+
+    assert((cast(string)dec3) == cast(string)input);
+}

+ 58 - 0
vtest/source/secured/util.d

@@ -0,0 +1,58 @@
+module secured.util;
+
+import std.stdio;
+
+import secured.openssl;
+
+public enum uint FILE_BUFFER_SIZE = 32768;
+
+@trusted public class CryptographicException : Exception
+{
+    this(string message)
+    {
+        super(message);
+        debug {
+        while(ERR_peek_error() != 0) {
+            char[] buf = new char[512];
+            ERR_error_string_n(ERR_get_error(), buf.ptr, 512);
+            writeln(buf);
+        }
+        }
+    }
+}
+
+@safe pure public bool constantTimeEquality(const ubyte[] a, const ubyte[] b)
+{
+    if(a.length != b.length)
+        return false;
+
+    int result = 0;
+    for(int i = 0; i < a.length; i++)
+        result |= a[i] ^ b[i];
+    return result == 0;
+}
+
+unittest
+{
+    import std.digest;
+    import std.stdio;
+    import secured.random;
+
+    writeln("Testing Constant Time Equality:");
+
+    //Test random data
+    ubyte[] rnd1 = random(32);
+    ubyte[] rnd2 = random(32);
+    writeln("Testing with Random Data");
+    assert(!constantTimeEquality(rnd1, rnd2));
+
+    //Test equal data
+    ubyte[48] key1 = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+    ubyte[48] key2 = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
+    writeln("Testing with Equal Data");
+    assert(constantTimeEquality(key1, key2));
+}

+ 46 - 2
vtest/source/session.d

@@ -3,8 +3,52 @@
 //   we have same user_id - but few cookies (devices or people on same account etc)
 //   we have same user_id - but few cookies (devices or people on same account etc)
 
 
 // sessions store:
 // sessions store:
-//   postgresql = cookie - user_id
-//   memcached  = cookie - user_id + user_id - some_cached_values
+//   postgresql = cookie -- user_id
+//   memcached  = cookie -- user_id ; user_id + key_part -- some_cached_values // todo think maybe store only - user_id + key_part -- used_id_cookies_list_csv_str
+
+
+import std.stdio : writeln;
+import std.array;
+import std.string;
+private{
+  import std.digest.sha;
+  import std.random : uniform;
+  import std.base64 : Base64;
+  import std.datetime : SysTime, Clock, dur;
+  import std.algorithm;
+  import std.typecons : Tuple, Nullable;
+}
+
+ubyte[][string] userSessions;
+
+//userSessions["user123"] = 42;
+//if("user123" in userSessions){
+//  int value = userSessions["user123"];
+//}
+//userSessions.remove("user123");
+//userSessions.remove("user123", null);
+
+
+
+ubyte idLength = 15; // we got 20 chars here // session ID length in Base64 like YouTube videos id
+ubyte validityDays = 255; // max value for ubyte too enaught // session validity in days
+
+string generateCookie(){ // Cookie as SessionId
+  ubyte[16] randomBytes;
+  foreach(ref b; randomBytes){
+    b = cast(ubyte)uniform(0, 256);
+  }
+  
+  auto hash = sha256Of(randomBytes);
+  ubyte[15] idBytes = hash[0..15].dup; // get 15 bytes - (120 bits) for 15-symbols Base64 // but got up to 20 symbols
+  
+  // Base64 - URL-safe coding
+  auto base64 = Base64.encode(idBytes)
+    .tr("+/", "-_")  // replace +/ to -_
+    .stripRight("="); // rm padding
+  
+  return base64.idup; // convert char[] into string
+}
 
 
 
 
 
 

+ 156 - 1
vtest/source/ws_bert_login.d

@@ -34,13 +34,46 @@ import std.conv : to;
 import bert; // https://github.com/221V/dlang_erl_bert  https://git.4dev.win/game1/dlang_erl_bert
 import bert; // https://github.com/221V/dlang_erl_bert  https://git.4dev.win/game1/dlang_erl_bert
 
 
 
 
+import secured.symmetric; // https://github.com/221V/SecureD   // https://github.com/221V/js_AES256CBC
+
+import session;
+
+
 import mustache;
 import mustache;
 alias MustacheEngine!(string) Mustache;
 alias MustacheEngine!(string) Mustache;
 
 
 
 
 
 
+string byte_arr_to_str(ubyte[] arr){
+  string[] str_arr;
+  foreach(elem; arr){
+    str_arr ~= to!string(elem);
+  }
+  return "[" ~ str_arr.join(",") ~ "]";
+}
+
+
+ubyte[] str_to_byte_arr(string str_arr){
+  str_arr = str_arr.replace("[", "").replace("]", "");
+  string[] parts = str_arr.split(",");
+  ubyte[] result;
+  result.reserve(parts.length);
+  foreach(part; parts){
+    result ~= to!ubyte(part.strip());
+  }
+  return result;
+}
+
+
 void ws_bert_handle(scope WebSocket sock){
 void ws_bert_handle(scope WebSocket sock){
+  //foreach(pair; req.headers.byKeyValue()){
+  //  writeln("Header: ", pair.key, " = ", pair.value);
+  //}
   // simple echo server + :)
   // simple echo server + :)
+  
+  //string client_id = req.attributes.get("client_id", "");
+  //writeln("96 client_id = ", client_id);
+  
   while(sock.connected){
   while(sock.connected){
     //auto msg = sock.receiveText();
     //auto msg = sock.receiveText();
     //sock.send(msg ~ " :)");
     //sock.send(msg ~ " :)");
@@ -55,14 +88,132 @@ void ws_bert_handle(scope WebSocket sock){
       msg_match(decoded, sock);
       msg_match(decoded, sock);
     }
     }
   }
   }
+  // todo delete data in userSessions here
 }
 }
 
 
+
+
 void msg_match(BertValue decoded, WebSocket sock){
 void msg_match(BertValue decoded, WebSocket sock){
   writeln("Decoded: ", decoded.toString());
   writeln("Decoded: ", decoded.toString());
   
   
+  ubyte[] key;
+  ubyte[] iv;
+  
   if(decoded.type_ == BertType.Tuple){
   if(decoded.type_ == BertType.Tuple){
     auto decoded1 = decoded.tupleValue;
     auto decoded1 = decoded.tupleValue;
-    if(decoded1.length == 3){
+    if(decoded1.length == 1){
+      if(auto num1 = cast(uint8)decoded1[0].intValue){ // we can use js AES for additional password encrypt for login-logout  // ws.send(enc(tuple( number(1) )));
+        if(num1 == 1){ // init login -- get key + iv for encrypt password and send to server
+          
+          /*
+          ubyte[] test_pass = cast(ubyte[])"12345678";
+          SymmetricKey key = generateSymmetricKey( SymmetricAlgorithm.AES256_CBC );
+          EncryptedData enc = encrypt(key, test_pass);
+          //auto iv_hex = toHexString!(LetterCase.lower)(enc.iv);
+          //auto encrypt = toHexString!(LetterCase.lower)(enc.cipherText);
+          auto iv_hex = enc.iv;
+          auto encrypt = enc.cipherText;
+          writeln("Key: ", key.key);
+          //writeln("Key: ", key.toString); // base64 encoded string
+          //writeln("IV: ", toHexString!(LetterCase.lower)(enc.iv));
+          writeln("IV: ", iv_hex);
+          //writeln("Encrypt: ", enc);
+          writeln("Encrypt: ", encrypt);
+          */
+          
+          
+          SymmetricKeyIV rand_key_iv = generateSymmetricKeyIV(); // default SymmetricAlgorithm.AES256_CBC
+          writeln("rand key: ", rand_key_iv.key);
+          writeln("rand iv: ", rand_key_iv.iv);
+          string client_id = generateCookie();
+          userSessions[client_id ~ "_key"] = rand_key_iv.key;
+          userSessions[client_id ~ "_iv"] = rand_key_iv.iv;
+          
+          /*
+          //auto test_pass = cast(ubyte[])"12345678";
+          auto test_pass = cast(ubyte[])"12345678testтест";
+          auto key = cast(ubyte[])[34, 74, 12, 214, 126, 234, 101, 147, 13, 32, 244, 185, 45, 217, 142, 33, 213, 116, 63, 179, 84, 23, 138, 187, 134, 130, 234, 54, 48, 66, 20, 152];
+          auto iv = cast(ubyte[])[62, 133, 213, 219, 194, 200, 76, 142, 202, 16, 12, 237, 163, 147, 65, 93];
+          
+          
+          auto encrypted = encrypt(key, iv, test_pass, SymmetricAlgorithm.AES256_CBC);
+          writeln("Encrypted: ", encrypted.cipherText); // [223, 86, 210, 55, 192, 240, 144, 50, 159, 4, 238, 182, 171, 185, 80, 48] // [90, 85, 212, 32, 94, 33, 182, 43, 20, 183, 121, 59, 232, 45, 180, 158, 153, 9, 54, 45, 244, 32, 85, 24, 162, 206, 56, 235, 107, 194, 143, 192]
+          
+          //auto encrypted_data = cast(ubyte[])[223, 86, 210, 55, 192, 240, 144, 50, 159, 4, 238, 182, 171, 185, 80, 48];
+          auto encrypted_data = cast(ubyte[])[90, 85, 212, 32, 94, 33, 182, 43, 20, 183, 121, 59, 232, 45, 180, 158, 153, 9, 54, 45, 244, 32, 85, 24, 162, 206, 56, 235, 107, 194, 143, 192];
+          
+          ubyte[] decrypted = decrypt(key, iv, encrypted_data, SymmetricAlgorithm.AES256_CBC);
+          writeln("Decrypted: ", decrypted); // [49, 50, 51, 52, 53, 54, 55, 56] // [49, 50, 51, 52, 53, 54, 55, 56, 116, 101, 115, 116, 209, 130, 208, 181, 209, 129, 209, 130]
+          writeln("Decrypted: ", cast(string)decrypted); // "12345678" // "12345678testтест"
+          */
+          
+          
+          sock.send("{window.key = new Uint8Array(" ~ byte_arr_to_str(rand_key_iv.key) ~ ");" ~
+            "window.iv = new Uint8Array(" ~ byte_arr_to_str(rand_key_iv.iv) ~ ");" ~
+            "window.uid = '" ~ client_id ~ "';" ~
+            "do_log_in();}");
+          
+          
+          /*
+          ubyte[] key = sock.context.get("aes_key", "");
+          ubyte[] iv = sock.context.get("aes_iv", "");
+          
+          if( key.empty || iv.empty ){}else{
+            sock.send("{window.key = new Uint8Array(" ~ byte_arr_to_str(key) ~ ");" ~
+              "window.iv = new Uint8Array(" ~ byte_arr_to_str(iv) ~ ");" ~
+              "do_log_in();}");
+          }
+          */
+          
+        } // else do nothing
+      } // else do nothing
+    
+    
+    }else if(decoded1.length == 4){ // {2, "uid", "login", "encrypted_pass"}
+      if(auto code2 = cast(uint8)decoded1[0].intValue){ // 2
+        if(code2 == 2){
+          
+          if(auto client_id = cast(string)decoded1[1].binaryValue){
+            writeln("client_id = ", client_id);
+          
+            if(auto login_str = cast(string)decoded1[2].binaryValue){
+              writeln("login_str = ", login_str);
+              
+              if(auto maybe_encrypted_pass = cast(string)decoded1[3].binaryValue){
+                writeln("maybe_encrypted_pass = ", maybe_encrypted_pass, " ", typeof(maybe_encrypted_pass).stringof); // Decoded: {2, <<116,101,115,116,49>>, <<91,49,50,53,44,50,51,54,44,50,50,48,44,50,53,53,44,49,50,48,44,49,54,57,44,49,56,51,44,49,48,50,44,50,49,49,44,51,53,44,50,52,54,44,50,49,55,44,55,49,44,50,54,44,50,49,50,44,56,56,93>>} // maybe_encoded_pass = [125,236,220,255,120,169,183,102,211,35,246,217,71,26,212,88] string
+                //sock.send("{console.log('" ~ str1 ~ " la-la-la" ~ "')}"); // 
+                
+                try{
+                  auto encrypted_pass = str_to_byte_arr(maybe_encrypted_pass);
+                  writeln("encoded_pass = ", encrypted_pass);
+                  
+                  if( (client_id ~ "_key") in userSessions){
+                    key = userSessions[client_id ~ "_key"];
+                  }
+                  if( (client_id ~ "_iv") in userSessions){
+                    iv = userSessions[client_id ~ "_iv"];
+                  }
+                  
+                  writeln("key = ", key);
+                  writeln("iv = ", iv);
+                  
+                  ubyte[] pass = decrypt(key, iv, encrypted_pass, SymmetricAlgorithm.AES256_CBC);
+                  writeln("Decrypted: ", pass); // byte array
+                  writeln("Decrypted: ", cast(string)pass); // string
+                  
+                  
+                }catch(Exception e){} // skip err, do nothing
+              
+              }
+            } // else do nothing
+            
+          } // else do nothing
+        } // else do nothing
+      } // else do nothing
+    
+    
+    
+    }else if(decoded1.length == 3){
       
       
       if(auto num1 = cast(uint8)decoded1[0].intValue){
       if(auto num1 = cast(uint8)decoded1[0].intValue){
         writeln("num1 = ", num1, " ", typeof(num1).stringof); // ws.send(enc(tuple( number(1), number(42), number(777) ))); // Decoded: {1, 42, 777} // num1 = 1 ubyte
         writeln("num1 = ", num1, " ", typeof(num1).stringof); // ws.send(enc(tuple( number(1), number(42), number(777) ))); // Decoded: {1, 42, 777} // num1 = 1 ubyte
@@ -76,6 +227,7 @@ void msg_match(BertValue decoded, WebSocket sock){
         
         
       } // else do nothing
       } // else do nothing
       
       
+      
       // var big_value = bigInt("61196067033413");
       // var big_value = bigInt("61196067033413");
       // ws.send(enc(tuple( number(1), bin('9'), bignum( big_value ) ))); // got as long for auto
       // ws.send(enc(tuple( number(1), bin('9'), bignum( big_value ) ))); // got as long for auto
       if(decoded1[2].type_ == BertType.Int){
       if(decoded1[2].type_ == BertType.Int){
@@ -111,6 +263,9 @@ void msg_match(BertValue decoded, WebSocket sock){
 }
 }
 
 
 void login_test(HTTPServerRequest req, HTTPServerResponse res){
 void login_test(HTTPServerRequest req, HTTPServerResponse res){
+  //string client_id = generateCookie();
+  //req.context["client_id"] = client_id;
+  
   Mustache mustache;
   Mustache mustache;
   auto context = new Mustache.Context;
   auto context = new Mustache.Context;
   mustache.path = "priv";
   mustache.path = "priv";