Skip to content

Abscissa/InstaUser-preview

Repository files navigation

InstaUser-Basic - Salted Hashed Password Library for D

[ChangeLog] [API Reference]

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);

Typical Usage Examples

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() and parseHash() 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 of makeHash() and isSameHash()).

  • 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's Policy Toward Implementing Crypto-Related Algorithms

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.

See also

For a good background on authentication, see "Salted Password Hashing - Doing it Right"

About

Preview of DAuth's future: InstaUser

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages