InstaUser-Basic (formerly "DAuth"), as the core portion of InstaUser, is an open-source salted password hash authentication library for D. It provides a simple, yet flexible API. With it, your software can easily incorporate user accounts with reliable, upgradable security.
You can have as much or as little control as you need. This makes InstaUser-Basic suitable for both new projects and interfacing with any existing hashed-password store.
By default, InstaUser-Basic uses known-good hashing and randomization algorithms (currently SHA-512 and Hash_DRBG), but it accepts any Phobos-compatible hash digest or random number generator.
InstaUser-Basic's main interface is makeHash and isSameHash:
-
makeHash(Password)
: Generates a salted hash for a password. -
isSameHash(Password, Hash)
: Validates a password against an existing hash. The hashes are compared using a "length-constant" time algorithm to thwart timing-based attacks.
import instauser.basic;
//...
char[] input = ...;
Password pass = toPassword(input); // Ref counted with automatic memory zeroing
// makeHash: Salt is crypto-secure randomized
string hash1 = makeHash(pass).toString(); // Ex: [SHA512]d93Tp...ULle$my7MSJu...NDtd5RG
string hash2 = makeHash(pass).toCryptString(); // Ex: $6$d93Tp...ULle$my7MSJu...NDtd5RG
// isSameHash: Compared using "length-constant" time
bool ok1 = isSameHash(pass, parseHash("[SHA512]d93Tp...ULle$my7MSJu...NDtd5RG"));
bool ok2 = isSameHash(pass, parseHash("$6$d93Tp...ULle$my7MSJu...NDtd5RG"));
The library provides a forward-compatible string-based hash format for easy
storage and retrieval using any hash digest type. It also has native support
for Unix crypt(3)-style hash
strings for MD5, SHA-256 and SHA-512. To avoid accidental usage of
low-security, hash digests which InstaUser-Basic knows to provide inferior
secury (such as MD5) require a clearly-named compiler flag to be used:
-version=InstaUser_AllowWeakSecurity
.
Additionally, there is a
instauser.basic.random
module with functions for randomly generating
salts,
passwords and
single-use tokens:
// All parameters are optional: Desired length, random number generator,
// token strength, and chars permitted in the password:
Password pass = randomPassword();
ubyte[] salt = randomSalt();
string singleUse = randomToken();
Password pass2 = randomPassword!DefaultCryptoRand(20, defaultPasswordChars);
ubyte[] salt2 = randomSalt!DefaultCryptoRand(32);
string singleUse2 = randomToken!DefaultCryptoRand(defaultTokenStrength);
See also: API Reference
import instauser.basic;
// Your code to save/load from a database or other storage:
void saveUserPassword(string user, string passhash) {...}
string loadUserPassword(string user) {...}
void setPassword(string user, char[] pass)
{
string hashString = makeHash(toPassword(pass)).toString();
saveUserPassword(user, hashString);
}
bool validateUser(string user, char[] pass)
{
string hashString = loadUserPassword(user);
return isSameHash(toPassword(pass), parseHash(hashString));
}
In that example:
setPassword()
uses InstaUser-Basic to store randomly-salted password
hashes, using the default hashing digest (currently SHA-512), in a
forward-compatible ASCII-safe text format. The format is mostly a form of
Base64, and similar to crypt(3)
but more readable and flexible. The hash digest (ex: "SHA512") is stored as
part of the hashString
, so if you upgrade to a different hashing digest,
any existing accounts using the old digest will automatically remain accessible.
validateUser()
is automatically compatible with all supported
InstaUser-style and crypt(3)-style string formats...not just whatever
format and digest setPassword
happens to be using. If you wish to
restrict the accepted formats and encodings, you can easily do that too.
You may have noticed the passwords are mutable character arrays, not strings. This is for a reason:
InstaUser stores passwords in a type named
Password
.
This is a reference-counted struct that automatically zero's out the
password data in memory before replacing the data or deallocating it.
A dupPassword(string)
is provided if you really need it, but this
is not recommended (because a string's memory buffer is immutable and
usually garbage-collected, and therefore can't be reliably zero'd out).
Ultimately, this helps you decrease the likelihood of raw passwords sticking
around in memory longer than necessary. Thus, with proper care when reading
the password from your user, your user's passwords may be less likely to
be exposed in the event of a memory-sniffing attack on your program.
To ensure compatibility with both existing infrastructure and future cryptographic developments, nearly any aspect of the authentication system can be customized:
-
Passwords can be hashed using any Phobos-compatible digest (See std.digest.digest).
-
Salts can be provided manually, or have a user-defined length.
-
Hashes and salts can be stored in any way or format desired. This is because the Hash struct returned by
makeHash()
andparseHash()
provides easy access to the hash, the salt, and the digest used. -
The method of combining the salt and raw password can be user-defined (via the optional
salter
parameter ofmakeHash()
andisSameHash()
). -
Hash!T.toString()
supports OutputRange sinks, to avoid unnecessary allocations. -
Passwords, salts, and randomized tokens (for one-use URLs) can all be automatically generated, optionally driven by custom Phobos-compatible random number generators.
Here's a more customized usage example:
import std.digest.md;
import std.exception;
import std.random;
import instauser.basic;
// Your code to save/load from a database or other storage:
void saveUserInfo(string user, string digest, string passhash, ubyte[] salt) {...}
string loadUserPassword(string user) {...}
ubyte[] loadUserSalt(string user) {...}
string loadUserDigest(string user) {...}
void setPassword(string user, char[] pass)
{
// InstaUser-Basic knows that MinstdRand and MD5 do NOT provide crypto-grade
// security, so it won't allow the following to compile unless you
// include the compiler flag: -version=InstaUser_AllowWeakSecurity
// Note: This randomizer is not actually suitable for crypto purposes.
static MinstdRand rand;
auto salt = randomSalt(rand, 64);
// Warning! MD5 should never be used for real passwords.
auto myHash = makeHash!MD5(pass, salt);
saveUserInfo(user, "MD5", myHash.hash, myHash.salt);
}
bool validateUser(string user, char[] pass)
{
string hash = loadUserPassword(user);
ubyte[] salt = loadUserSalt(user);
ensure(loadUserDigest(user) == "MD5");
return isSameHash!MD5(pass, hash, salt);
}
InstaUser prefers to avoid directly including crypto-related algorithms, instead relying on other libraries for these. But InstaUser will provide an implementation of well-known and establisted algorithms when necessary.
Currently, the only crypto-related algorthm directly implemented by InstaUser is InstaUser's default CSRNG, Hash_DRBG.
Previously, InstaUser (back when it was known as "DAuth") had included its own implementation of SHA2, but that implementaion has since been folded into Phobos and is no longer part of InstaUser.
For a good background on authentication, see "Salted Password Hashing - Doing it Right"