SHA1 Hash bzw. Salt



  • Hallo zusammen,
    gibt es für den C++ Builder ein brauchbares Tutorial wie man einen Passwort Salt analog RNGCryptoServiceProvider (ASP.NET) generieren kann? Die Datei die ich erzeugen muss, setzt ein in SHA1 gehashtes Passwort vorraus und dem Salt. Dies ist absolutes Neuland für mich und weiß nicht recht wo ich anfangen soll. Mit Indy verwende ich derzeit die TIdHashSHA1 Klasse um zumindest den Passwort String in SHA1 zu hashen.

    Mir ist bewusst, dass das Salt ein random generierter String ist, der entweder dem Passwort String verklebt oder vermischt wird, um in der Userdatenbank die Passwort Hashes unique zu halten trotz möglicher gleicher Passwörter und Prävention vor BruteForce.



  • Das kannst du direkt mit der WinAPI umsetzen. Guck dir mal die Funktionen CryptAcquireContext, CryptCreateHash, CryptHashData, und CryptGetHashParam an.



  • Vielen lieben Dank Doc, wow - das wirkt erstmal erschlagend wenn man den Anspruch erhebt, auch gleich alle Backgrounds logisch zu verstehen. Ich melde mich sobald ich Luft dafür habe



  • Hallo zusammen,
    ich habe nun in einem char buffer den SHA1 hash als raw bits mit Hilfe der Funktion CryptGetHashParam. Jetzt muss ich doch gewiss mit Hilfe einer for-Schleife durch den char Buffer iterieren und mit der Hilfe von Bitoperationen einen String daraus basteln. Ich möchte bitte nicht das mir jemand das Ergebnis vor die Füße wirft und würde nur gerne grob wissen, wie dies funktioniert.



  • Einfach in mit setw und hex einen ostringstream schreiben. Oder du bastelst dir sowas wie to_hex( unsigned char ch ) und befüllst den Ergebnisstring von Hand, wenn du den ostringstream Overhead nicht haben möchtest.

    #include <string>
    
    char nibble_to_hex( char val )
    {
       if( val > 9 ) return 'a' + val - 10;
       else          return '0' + val;
    }
    
    std::string buffer_to_hex_string( const char* buffer, std::size_t size )
    {
       std::string retval;
       retval.resize( size * 2 );
    
       for( std::size_t i = 0; i < size; ++i )
       {
          retval[2 * i     ] = nibble_to_hex( buffer[i] >> 4 );
          retval[2 * i +1] = nibble_to_hex( buffer[i] & 0x0f );
       }
       return retval;
    }
    


  • Hi Doc, nochmals danke. Ich habe folgendes bisher geschrieben:

    int __fastcall TForm1::GetSHA1asString(String &Password, String &Salt)
    {
    
    	  char Buff[256];
    	  DWORD BuffSize;
    
    	  HCRYPTPROV handle_cryptprov = 0;
    	  HCRYPTHASH handle_hash = 0;
    
    
    
    	  //----Handle zum Cryptoprovider
    	  if(!CryptAcquireContext(&handle_cryptprov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
    	  {
    		 //---CryptAcquireContext failed
    		 return 1;
    	  }
    
    
    
    	  if(!CryptCreateHash(handle_cryptprov, CALG_SHA1, 0, 0, &handle_hash))
    	  {
    		 //---CryptCreateHash failed
    		 CryptReleaseContext(handle_cryptprov, 0);
    		 return 2;
    	  }
    
    
    
    	  if(CryptHashData(handle_hash, (BYTE*)&Password, Password.Length(), 0))
    	  {
    
    		   ZeroMemory(&Buff, sizeof(Buff));
    		   BuffSize = sizeof(Buff);
    
    
    		   CryptGetHashParam(handle_hash, HP_HASHVAL, (BYTE*)&Buff, &BuffSize, 0);
    
    
    		   for(int i = 0; i < BuffSize; i++)
    		   {
    
    			   //--- Buff in Base64 String konvertieren
    		   }
    
    
    	  }
    
    
    
    
    
    
    	  return 0;
    
    }
    

    Mein Verständnis endet nun daran wie ich die Bits aus dem char Buffer zu einem Base64 String konvertieren kann. Hexadezimal wird nicht benötigt aber wäre trotzdem eine gute Übung. Der gezeigte Quelltext ist momentan natürlich weder feingeschliffen noch vollständig und bezieht sich noch nicht auf das Salt. Ich wäre schon sehr froh wenn der Passwort hash funktionieren würde.



  • Aua, da sind mehrere Böcke drin.

    1. Implementier´ die Hash Funktion als freie Funktion und nicht als member eines Formulars. Formulare zeigen Sachen an und sollten keine Hashes ausrechnen
    2. benutz´ keine C-Style casts, sondern C++ casts. Dann sollte dir dein Compiler auch sagen, dass (BYTE*)&Password in die Hosen gehen kann. String ist in Delphi ein UnicodeString, der 16bit pro Zeichen benutzt und mit Length die Anzahl der Zeichen im String zurückgibt, nicht die Menge des benötigten Speichers. Der CryptHashData Aufruf berücksichtigt also nur die erste Hälfte des Passworts und auch nur, weil die Stringdaten zufällig am Anfang des Stringklasse liegen.
    3. benutz´ die Standard C++ Datentypen und vergiss die Delphi-Datentypen. Iwann schraubt Embarcadero wieder dran rum und plötzlich funktionieren Sachen nicht mehr, die früher funktionierten. C++ bleibt abwärtskompatibel, was da ein mal funktioniert wird weiter funktionieren.
    4. In der WINAPI ruft man Funktionen, die einen Puffer befüllen, in der Regel zwei mal auf. Beim ersten Mal um die Puffergröße zu erfragen, beim zweiten Mal um den Puffer zu füllen.
    5. Gib die Ressourcen wieder frei. Was du mit CryptAcquireContext und CryptCreateHash anforderst musst du auch wieder freigeben.
    6. hab grade keinen C++ Base64 Converter zur Hand, musste mal googeln. Kann nicht schwierig sein.


  • Hallo Doc, du hast vollkommen recht. Ich sollte diese ganzen maroden Sitten wirklich mal ad acta legen. Meine Wissensdefizite sind in vielen Details wirklich prekär aber ich versuche immer wieder die Dinge aufzuarbeiten, ich möchte nichts beschönigen. Ich bin hauptberuflich kein Entwickler und beschäftige mich immer mal wieder beiläufig mit der Materie um auf der Arbeit Probleme schnell lösen zu können. Ich hoffe, dass dich dies nicht weiter abschreckt mir zu helfen. Ich habe mir eben ein Tutoral über die STD reingezogen und was der Unterschied zwischen C-Style Casts und C++ Casts sind. Letzteres macht natürlich mehr sind da wir nunmal C++ verwenden....Gott wie peinlich. Mir war dieser Unterschied wirklich nicht bewusst. In den C++ Cast Erklärungen war neben dem statisch oder dynamisch machen auch zu lesen, dass rein nur reinterpret casts in Frage kommen wenn Pointer im Spiel sind. Daher habe ich folgenden Stand auf die schnelle Entworfen. Deine Umrechnungsfunktionen für die Hexadezimalwerte habe ich in ganz groben Auszügen verstanden und würde dich gerne nochmals näher bohren, warum dies so funktioniert (Bitverschiebung).

    Irgendwo passt wohl ein Cast nicht oder die Längenübergabe des std::string password an den api call ist missglückt. Wenn ich password als c_string über cout Ausgebe, steht nur Müll geschrieben. Aber wo ist der Schnitzer?

    int SHA1_Class::GetSHA1asString(std::string &password, std::string &salt)
    {
    
                  char Buff[256];
    			  unsigned long BuffSize;
    
    			  HCRYPTPROV handle_cryptprov = 0;
    			  HCRYPTHASH handle_hash = 0;
    
    
    
    			  //----Handle zum Cryptoprovider
    			  if(!CryptAcquireContext(&handle_cryptprov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
    			  {
    				 //CryptAcquireContext failed
    				 //call GetLastError für Details
    				 return 1;
    			  }
    
    
    
    			  if(!CryptCreateHash(handle_cryptprov, CALG_SHA1, 0, 0, &handle_hash))
    			  {
    				 //CryptCreateHash failed
    				 //call GetLastError für Details
    				 CryptReleaseContext(handle_cryptprov, 0);
    				 return 2;
    			  }
    
    
    
    
    
    
    			  if(CryptHashData(handle_hash, reinterpret_cast <BYTE*>(&password), password.length(), 0))
    			  {
    
    				   ZeroMemory(&Buff, sizeof(Buff));
    				   BuffSize = sizeof(Buff);
    
    				   CryptGetHashParam(handle_hash, HP_HASHVAL, reinterpret_cast <BYTE*>(&Buff), &BuffSize, 0);
    
    				   password = buffer_to_hex_string(reinterpret_cast <unsigned char*> (&Buff), BuffSize);
    
    			  }
    			  else
    			  {
    			     //CryptHashData failed
    				 //call GetLastError für Details
    				 return 3;
    
    			  }
    
    
    
    
                  //---Ressourcen freigeben
    			  CryptDestroyHash(handle_hash);
    			  CryptReleaseContext(handle_cryptprov, 0);
    
    
    
    
    
    			  return 0;
    
    
    
    
    }
    //------------------------------------------------------------------------------
    char SHA1_Class::nibble_to_hex( char value )
    {
    	 if( value > 9 )
    	 return 'a' + value - 10;
    	 else
    	 return '0' + value;
    }
    //------------------------------------------------------------------------------
    std::string SHA1_Class::buffer_to_hex_string( const char* buffer, unsigned long size )
    {
    	std::string returnvalue;
    
    	returnvalue.resize( size * 2 );
    
    	for( unsigned long i = 0; i < size;  i++ )
    	{
    	  returnvalue[2 * i    ] = nibble_to_hex( buffer[i] >> 4 );
    	  returnvalue[2 * i + 1] = nibble_to_hex( buffer[i] & 0x0f );
        }
    
    	return returnvalue;
    }
    //------------------------------------------------------------------------------
    


  • Ob man C++ jetzt hauptberuflich oder hobbymäßig einsetzt ist total egal, ausschlaggebend ist die Bereitschaft Lernen zu wollen. Und leider hat C++ eine steile Lernkurve.

    Dass reinterpret_cast immer bei Zeigern eingesetzt werden muss ist schlichtweg falsch. reinterpret_cast wird eigentlich nur dann eingesetzt, wenn man es besser weiß als der Compiler, was nur selten der Fall sein sollte, zB bei Konvertierungen aus void*.

    Zu deinem Quelltext:
    Du brauchst keine SHA1 Class, das kann man alles in einer freien Funktion implementieren. Und du solltest GetLastError auch aufrufen und auswerten, nicht nur in den Kommentar schreiben 😉 Auf den ersten Blick springt mir nur reinterpret_cast<BYTE*>( &password ) in´s Auge, das ist nach wie vor falsch. Du möchtest in Inhalt des strings hashen, nicht das Stringobjekt selbst.
    Du gibst nicht in allen Programmpfaden die angeforderten Handles frei, wenn CryptHashData fehlschlägt gibst du einfach den Wert drei zurück. Überhaupt solltest du dir überlegen, wie deine Funktion aussehen sollte und was sie zuückgeben sollte. Wie übergibst du denn den Hash an den Aufrufer, wenn deine Funktion einen intals Rückgabetyp hat?



  • Hallo Doc,
    die Feinheiten mit GetLastError hätte ich direkt im Anschluss erledigt. Ich habe mir angewöhnt ersteinmal die Grundfunktionalität herzustellen und dann alles weitere auszuschmücken. Deinen Einwand der Objektfreigabe sehe ich und werde es umgehend beheben. Ich hatte bei den Casts auch Bauchweh beim schreiben zugegebenermaßen, aber das ist der Stand wie ich es erstmal interpretieren konnte. Also das was ich da fabriziere bezieht sich auf das Objekt selbst und nicht auf die Daten hinter dem Pointer? Ich hoffe das du mir mit einem Hinweis helfen kannst.

    Password und Salt wird ja via Referenz an diese Funktion übergeben und somit ergibt sich die Referenz an die Variablen des Aufrufers ja automatisch.

    Ich danke dir sehr, dass du bereit bis mich ans Händchen zu nehmen.



  • @zero01 sagte in SHA1 Hash bzw. Salt:

    Password und Salt wird ja via Referenz an diese Funktion übergeben und somit ergibt sich die Referenz an die Variablen des Aufrufers ja automatisch.

    Was soll denn nach dem Aufruf in diesen Variablen stehen (außer den übergebenen Werten)?
    M.E. sollten diese beiden Parameter const sein.

    Um an den Inhalt eines std::string als nullterminierte Zeichenkette zu kommen, gibt es die .c_str()-Methode.



  • Sorry, habe mich falsch ausgedrückt. Vor dem Aufruf der Methode sind 2 Variablen deklariert und übergebe deren Referenz an diese SHA1 Funktion. Über diese Zuweisung gebe ich den Hash via Referenz zurück in die Main:

    password = buffer_to_hex_string(reinterpret_cast <unsigned char*> (&Buff), BuffSize);
    


  • @zero01
    Schlechte Idee. Du übergibst ein Passwort und bekommst an dessen Stelle den dazu passenden SHA1 Hash zurück? Guter Code sollte sich selbst dokumentieren, und wenn du dir in 5 Jahren anguckst, was da passiert, wirste das nicht mehr wissen. Vergleich:

    string raw_data = "hello world";
    
    // Option 1: Man weiß sofort was passiert
    string hash_1 = calculate_sha1_hash( raw_data );
    
    // Option2: WTF?
    calculate_sha1_hash( raw_data );
    


  • Genau in dieser Art habe ich es jetzt 😉 Ich probiere immer per Pointer oder Referenz zu übergeben (um das ständige kopieren zu vermeiden) wo es nur geht und im ersten Beispiel stand ich mir selbst im Weg. Ich sehe es ein, ist rumgewurschtel.

    Für die Errorcodes von GetLastError übergebe ich den Error DWORD an eine void Funktion und haue via FormatMessage den Fehlertext über cout raus -> so ist der Plan.

    Lass uns bitte noch einmal auf die Casts der Api Calls zurück kommen, wie macht man das in diesem Fall? Du sagst, ich übergebe das Objekt selbst aber nicht den Inhalt des Objekts. Ich verstehe es nicht so ganz....ich müsste doch die Referenzierung im Cast entfernen um die Daten zu erhalten.

    Auszug aus der aufrufenden Funktion:

    SHA1_Class sha;
    	std::string password;
    	std::string salt;
    	std::string result;
    
    
    	cout<<"Please enter password to hash:";
    	cin>>password;
    
    
    	result = sha.GetSHA1asString(password, salt);
    	cout<<result.c_str()<<endl;
    

    Der jetzige Stand der Hash-Funktion:

    std::string SHA1_Class::GetSHA1asString(std::string password, std::string salt)
    {
    
    			  char Buff[256];
    			  unsigned long BuffSize;
    
    			  HCRYPTPROV handle_cryptprov = 0;
    			  HCRYPTHASH handle_hash = 0;
    
    
    
    			   if(CryptAcquireContext(&handle_cryptprov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
    			  {
    
    
    						if(CryptCreateHash(handle_cryptprov, CALG_SHA1, 0, 0, &handle_hash))
    					   {
    
    
    								  if(CryptHashData(handle_hash, reinterpret_cast <BYTE*>(&password), password.length(), 0))
    								  {
    
    										   ZeroMemory(&Buff, sizeof(Buff));
    										   BuffSize = sizeof(Buff);
    
    
    										   if(CryptGetHashParam(handle_hash, HP_HASHVAL, reinterpret_cast <BYTE*>(&Buff), &BuffSize, 0))
    										   {
    
    												 //---Ressourcen freigeben
    												 CryptDestroyHash(handle_hash);
    												 CryptReleaseContext(handle_cryptprov, 0);
    
    
    												 return buffer_to_hex_string(reinterpret_cast <unsigned char*> (&Buff), BuffSize);
    
    										   }
    										   else
    										   PrintErrorToScreen(GetLastError());
    
    
    
    
    
    								  }
    								  else
    								  PrintErrorToScreen(GetLastError());
    
    
    
    
    					   }
    					   else
    					   PrintErrorToScreen(GetLastError());
    
    
    
    			  }
    			  else
    			  PrintErrorToScreen(GetLastError());
    
    
    
    
    			  
    			  //---Ressourcen freigeben
    			  if(handle_hash != 0)
    			  CryptDestroyHash(handle_hash);
    
    			  if(handle_cryptprov != 0)
    			  CryptReleaseContext(handle_cryptprov, 0);
    
    
    }
    


  • @zero01 sagte in SHA1 Hash bzw. Salt:

    Formatier´ deinen Code bitte vernünftig, 8 Zeichen Einrückung sind fürchterlich. Und mehrere aufeinanderfolgende Leerzeilen auch.
    Du übergibst deine Parameter jetzt per value statt per reference. Das war sicher auch nicht der Plan...
    Und was deine Frage nach den Aufrufparametern für die WINAPI angeht: Was gibt &password denn zurück? Die Adresse des Objekts, das den Text speichert, oder einen Zeiger auf den Text selbst?



  • Hallo Doc,
    sorry für die Formatierung. Die Parameter per Value habe ich umgestellt, da die Rückgabe nun per return realisiert ist und ich sie im nachgang, wie bereits angemerkt, noch const machen kann.

    &password Gibt die Adresse des Objekts selbst zurück, da explizit der Referenzierungsoperator davor steht. Der Pointer auf den Text selbst (value) wäre hier schlicht password bzw. password.c_str() Siehe da, die Ausgabe geht nach Umstellung der folgenden Funktion schon mal trotz zusätzlichem Zeichenwirwar in Richtung hex:

    CryptHashData(handle_hash, password.c_str(), password.length, 0);
    

    Zugegebenermaßen steige ich bei den Casts von Buff vollständig aus. Sind hier die Casts überhaupt vonnöten? ich vermute nein aber bin mir absolut nicht sicher. Evtl. liegt der Fehler auch noch an der Längenermittlung des std::string password; password.length();

    Ich bin jetzt bis kommenden Donnerstag im Aussendienst und werde erst nach meiner Rückkehr weiter machen können.



  • Schalt mal die compiler warnungen an.

    CryptHashData(handle_hash, password.c_str(), password.length, 0);
    

    Da ist ein fehler im 3. Parameter 😉



  • Nach dem jetzigen, dank eurer Hilfe, erlangten Verständnis sieht mein Lösungsversuch jetzt wie folgt aus. Allerdings habe ich noch Zeichenwirrwar im hex string. Ist es richtig, dass die Übergabe via &Buff[0] erfolgen muss, da deine Funktion einen char Pointer erwartet und kein Array of char? Ich gebe deiner Funktion ja so die erste Stelle des Arrays via Reference und mit BuffSize die Länge.

    Kompilieren tut er es klaglos aber es kommt immer noch Müll herraus.

    std::string SHA1_Class::GetSHA1asString(std::string password, std::string salt)
    {
    
    	  char Buff[256];
    	  unsigned long BuffSize;
    
    	  HCRYPTPROV handle_cryptprov = 0;
    	  HCRYPTHASH handle_hash = 0;
    
    
    
    	   if(CryptAcquireContext(&handle_cryptprov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
    	  {
    
    
    				if(CryptCreateHash(handle_cryptprov, CALG_SHA1, 0, 0, &handle_hash))
    			   {
    
    
    						  if(CryptHashData(handle_hash, password.c_str(), password.length(), 0))
    						  {
    
    								   ZeroMemory(&Buff, sizeof(Buff));
    								   BuffSize = sizeof(Buff);
    
    
    								   if(CryptGetHashParam(handle_hash, HP_HASHVAL, Buff, &BuffSize, 0))
    								   {
    
    										 //---Ressourcen freigeben
    										 CryptDestroyHash(handle_hash);
    										 CryptReleaseContext(handle_cryptprov, 0);
    
    
    										 return buffer_to_hex_string(&Buff[0], BuffSize);
    
    								   }
    								   else
    								   PrintErrorToScreen(GetLastError());
    						  }
    						  else
    						  PrintErrorToScreen(GetLastError());
    			   }
    			   else
    			   PrintErrorToScreen(GetLastError());
    	  }
    	  else
    	  PrintErrorToScreen(GetLastError());
    
    
    
    
    
    	  //---Ressourcen freigeben
    	  if(handle_hash != 0)
    	  CryptDestroyHash(handle_hash);
    
    	  if(handle_cryptprov != 0)
    	  CryptReleaseContext(handle_cryptprov, 0);
    
    
    }
    


  • @firefly meinst du weil er unsigned long erwartet und ich ihm unsigned int liefere? Oder müsste ich gar +1 rechnen da der c_str() nullterminiert ist?

    Ich muss jetzt leider los, bis nächste Woche....



  • Nein meine ich nicht.

    Was ist der unterschied zwischen

    password.length
    

    und

    password.length()