BCrypt unter Windows



  • Hallo zusammen.

    Ich möchte gern BCrypt dazu nutzen um Passwörter zu hashen. Dazu nutze ich folgende Lib von Github: https://github.com/trusch/libbcrypt

    Das Problem ist nun, dass in der Lib folgendes vorkommt:

    int bcrypt_gensalt(int factor, char salt[BCRYPT_HASHSIZE])
    {
    	int fd;
    	char input[RANDBYTES];
    	int workf;
    	char *aux;
    
    	fd = open("/dev/urandom", O_RDONLY); //Hier steckt das Problem
    	if (fd == -1) {
    		printf("test");
    		return 1;
    	}
    
    	if (try_read(fd, input, RANDBYTES) != 0) {
    		if (try_close(fd) != 0)
    			return 4;
    		return 2;
    	}
    
    	if (try_close(fd) != 0)
    		return 3;
    
    	/* Generate salt. */
    	workf = (factor < 4 || factor > 31)?12:factor;
    	aux = crypt_gensalt_rn("$2a$", workf, input, RANDBYTES,
    			       salt, BCRYPT_HASHSIZE);
    	return (aux == NULL)?5:0;
    }
    

    Es wird open("/dev/urandom", O_RDONLY) verwendet, was unter Windows nicht läuft. Ich habe gelesen, CryptGenRandom wäre eine Alternative für Windows. Leider habe ich noch nicht genug Erfahrung um einzuschätzen ob dies der richtige Weg ist. Ich habe auch nicht genügend Kenntnisse, dass selbst umzuschreiben. Zwar könnte ich Versuche wagen, allerdings ist mir das Thema Verschlüsselung zu heikel für Experimente.

    Meine Frage ist nun, ob ihr mir da vielleicht helfen könnt? Vielleicht geht der Gedanke mit CryptGenRandom auch in die falsche Richtung.



  • Nach einem CryptGenRandom Beispiel würde das ganze bei mir so aussehen:

    int bcrypt_gensalt(int factor, char salt[BCRYPT_HASHSIZE])
    {
    	int fd;
    	unsigned char input[RANDBYTES];
    	int workf;
    	char *aux;
    
    	HCRYPTPROV hCryptProv;
    
    	if (CryptAcquireContext(
    		&hCryptProv,
    		NULL,
    		(LPCSTR)"Microsoft Base Cryptographic Provider v1.0",
    		PROV_RSA_FULL,
    		CRYPT_VERIFYCONTEXT)) {
    		if (CryptGenRandom(
    			hCryptProv,
    			RANDBYTES,
    			input)) {
    
    			if (CryptReleaseContext(hCryptProv, 0)) {
    				return 0;
    			}
    			else {
    				printf("Error during CryptReleaseContext.\n");
    			return 4;
    			}
    		}
    		else {
    			if (CryptReleaseContext(hCryptProv, 0)) {
    				printf("Error during CryptGenRandom.\n");
    				return 2;
    			}
    			else {
    				printf("Error during CryptReleaseContext.\n");
    				return 3;
    			}
    		}
    	}
    	else {
    		printf("Error during CryptAcquireContext!\n");
    		return 1;
    	}
    
    	/* Generate salt. */
    	workf = (factor < 4 || factor > 31)?12:factor;
    	aux = crypt_gensalt_rn("$2a$", workf, input, RANDBYTES,
    			       salt, BCRYPT_HASHSIZE);
    	return (aux == NULL)?5:0;
    }
    


  • Wieso nicht Botan, Crypto++, OpenSSL oder libsodium? Muss es bcrypt sein, oder darf es auch scrypt, Argon2 oder PBKDF2 sein? (Crypto++ hat z.B. kein bcrypt soweit ich weiß). Man muss ungeheuerlich aufpassen bei Crypto Zeug hab ich gehört... Es gibt so viele Möglichkeiten ungewollt Schwachstellen einzubauen in der Implementierung... Lieber auf etwas bewährtes und weit verbreitetes setzen.

    Meine Frage ist nun, ob ihr mir da vielleicht helfen könnt? Vielleicht geht der Gedanke mit CryptGenRandom auch in die > falsche Richtung.

    Crypto++ verwendet unter Windows auch CryptGenRandom für ihren AutoSeededRandomPool. Ich denke so verkehrt wird das nicht sein, wenn man Microsoft da trauen kann. Sonst gibt es wohl auch auf neueren CPUs einen Hardware RNG, falls man dem trauen kann.

    Aber selbst wenn die Funktion CryptGenRandom an sich richtig funktioniert, kann man bei der Anwendung insgesamt bestimmt noch viele Fehler machen, wodurch andere Angriffsflächen entstehen.



  • Ich möchte Passwörter in einer Datenbank speichern und habe nach einer sicheren Möglichkeit gesucht. Die meisten Quellen empfohlen einen BCrypt Hash in Verbindung mit einem Salt. Daraufhin habe ich mehrere Libs ohne Erfolg ausprobiert (das Problem der meisten war, wie hier auch, dass sie für UNIX geschrieben sind). Ich suche halt nach einer "sicheren" Möglichkeit ein Passwort zu hashen. (BCrypt wurde auch deshalb oft genannt weil es länger dauert den Hash zu generieren und somit Brut Force verzögert)

    Ich habe auch nichts dagegen eine andere Methode zu verwenden, solange ich damit Nachts gut schlafen kann 😂



  • @zhavok Evtl. findest Du diesen Link interessant: https://crackstation.net/hashing-security.htm



  • Vielleicht ist es einfacher PBKDF2 dafür zu nutzen. Vielleicht könnte das ganze so aussehen:

    #include <openssl/evp.h>
    #include <iostream>
    #include <sstream>
    #include <windows.h>
    
    enum { KEYLENGTH = 16 };
    
    int main() {
    
    	/* Salt generieren */
    	HCRYPTPROV hProvider = 0;
    	if (!::CryptAcquireContextW(&hProvider, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
    		return 1;
    
    	unsigned char salt[8] = {};
    
    	if (!::CryptGenRandom(hProvider, 8, salt)) {
    		::CryptReleaseContext(hProvider, 0);
    		return 1;
    	}
    
    	for (DWORD i = 0; i < 8; ++i)
    		std::cout << std::hex << static_cast<unsigned int>(salt[i]);
    
    	if (!::CryptReleaseContext(hProvider, 0))
    		return 1;
    
    	/* Hash generieren */
    	char pass[] = "pass\0word";
    	int iter = 4096;
    	unsigned char result[KEYLENGTH];
    	int success = PKCS5_PBKDF2_HMAC(pass, sizeof(pass) - 1, salt, sizeof(salt) - 1, iter, EVP_sha1(), KEYLENGTH, result);
    
    
    	/* Ausgabe */
    	std::stringstream ss;
    	for (int i = 0; i< KEYLENGTH; ++i)
    		ss << std::hex << (int)result[i];
    	std::string mystr = ss.str();
    
    	std::cout << mystr;
    	return 0;
    }
    


  • Ich habe das jetzt alles nochmal überarbeitet und würde mich freuen wenn mir jemand sagen kann, ob ich das ganze so nutzen kann. Hilft vielleicht auch anderen Nutzern beim Passwort-Hashing weiter:

    //hash.hpp
    #ifndef HASH_HPP_INCLUDED
    #define HASH_HPP_INCLUDED
    #include <openssl/evp.h>
    #include <iostream>
    #include <sstream>
    #include <windows.h>
    #include <string>
    #include "hash.hpp"
    
    #define KEYLENGTH 16
    
    std::string GenerateSalt() {
    
    	HCRYPTPROV hProvider = 0;
    	if (!::CryptAcquireContextW(&hProvider, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
    		throw std::runtime_error("CryptAcquireContextW Error");
    
    	unsigned char salt[8] = {};
    
    	if (!::CryptGenRandom(hProvider, 8, salt)) {
    		::CryptReleaseContext(hProvider, 0);
    		throw std::runtime_error("CryptGenRandom Error");
    	}
    
    	for (DWORD i = 0; i < 8; ++i)
    		std::cout << std::hex << static_cast<unsigned int>(salt[i]);
    
    	if (!::CryptReleaseContext(hProvider, 0))
    		throw std::runtime_error("CryptReleaseContext Error");
    
    	std::string temp(salt, salt + sizeof salt / sizeof salt[0]);
    
    	return temp;
    }
    
    
    
    std::string GenerateHash(std::string strPass, std::string strSalt) {
    
    	const char* pass = strPass.c_str();
    	const unsigned char* salt = reinterpret_cast<const unsigned char *> (strPass.c_str());
    
    	int iter = 4096;
    	unsigned char result[KEYLENGTH];
    	int success = PKCS5_PBKDF2_HMAC(pass, sizeof(pass) - 1, salt, sizeof(salt) - 1, iter, EVP_sha1(), KEYLENGTH, result);
    
    	std::stringstream ss;
    	for (int i = 0; i < KEYLENGTH; ++i)
    		ss << std::hex << (int)result[i];
    	std::string hash = ss.str();
    
    	return hash;
    }
    
    #endif
    
    //main.cpp
    #include <iostream>
    #include <string>
    #include "hash.hpp"
    
    int main() {
    
    	try {
    		std::string pass = "password";
    		std::string salt = "salt";
    		std::string hash = GenerateHash(pass, salt);
    		std::cout << hash << std::endl;
    	}
    	catch (std::exception &exc) {
    		std::cout << exc.what() << std::endl;
    	}
    
    	return 0;
    }