• Tutorial: Online Highscore med PHP, MYSQL och libcurl - Del 2 - SHA1 hashning som säkerhet.

    Hej!

    Efter några månader så har jag fortsatt studien av Online Highscore och utvecklat en lösning där man laddar upp Namn & Score, samt en hashnyckel till php scritpet. Hash nyckeln beräknas på klienten och även på Servern.

    För att kunna följa med denna tutorial så hittar du del 1 här: Online Highscore del 1.

    C kod för Del 2: Ladda hem

    Php koden hittar du i del 1 samt här i del 2 och får klippa och klistra lite.

    MODIFIERING AV insert_highscore.php


    Kod:
    <?
    function getParam($name, $from, $link)
    {
     if (!array_key_exists($name, $from)) {return NULL;}
     $value = mysql_real_escape_string(urldecode(trim(strip_tags  ($from[$name]))), $link);
     return $value;
    }
    
    $con = mysql_connect("localhost","anvandarnamn","losenord");
    	if(!$con)
    	{
    		die( "Kunde inte ansluta till databasen");
    	}
    	
    @mysql_select_db(game_highscore) or die( "Kunde inte hitta databasen");
    
    $playername=getParam('playername', $_POST, $con);
    $score=getParam('score', $_POST, $con);
    $thehash = getParam('thehash', $_POST, $con);
    $thehash = strtolower($thehash);
    
    $thehash = str_replace(' ', '', $thehash);
    
    $key = "NYCKEL";
    
    $combine = sprintf("%s%s",$score,$key);
    
    $result = sha1($combine);
    
        if(strcmp($result,$thehash)==0)
        {
            $query = "INSERT INTO game_highscore VALUES ('','$score','$playername')";
            mysql_query($query);
        }
        else
        {
            echo 'CHEAT DETECTED! ABORTING!';
        }
    mysql_close();
    ?>
    Vi gör som tidigare och stegar igenom koden, och beskriver vad som händer.

    Kod:
    $thehash = getParam('thehash', $_POST, $con);
    Vi skapar varabeln $thehash och kollar efter en $_POST med namnet 'thehash'.

    Kod:
    $thehash = strtolower($thehash);
    sedan konverterar vi tecken strängen 'thehash' till små bokstäver.

    Kod:
    $thehash = str_replace(' ', '', $thehash);
    och tar bort alla mellanslag " ", ur strängen.

    Kod:
    $key = "NYCKEL";
    Vi skapar sedan en hashnyckel $key, och här ersätter du: "NYCKEL", med vad du vi ha för hemlig SHA1 nyckel.

    Kod:
    $combine = sprintf("%s%s",$score,$key);
    vi fortsätter med att kobinera den hemliga nyckeln med användarens poäng och sparar resultuatet i $combine

    Kod:
    $result = sha1($combine);
    sedan skapar vi en sha1 hash av den kombinerade strängen,

    Kod:
        if(strcmp($result,$thehash)==0)
        {
            $query = "INSERT INTO highscore VALUES ('','$score','$playername')";
            mysql_query($query);
        }
        else
        {
            echo 'CHEAT DETECTED! ABORTING!';
        }
    och avslutar med att jämföra SHA1 hashnyckeln $thehash med den nyckel vi genererade.
    Om strängarna är identiska så sätter vi in highscore i databasen.
    Stämmer inte strängarna så medelar vi att spelaren försökt att fuska, och vi avbryter.


    Modifiering av test_highscore.c


    Kod:
    #include <string.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <curl/curl.h>
    #include "sha1.h"
    
    char createdkey[100];               //den nyckel vi skapar för SHA1 hashen
    char storeme[100];                  //temporär sträng
    char alphabet[27];                  //alpabetet A-Z
    
    void alphabet_inorder(char[]);      //skapa alfabetet i ordning A-Z i en array
    void shuffle(char[]);               //blanda om bokstäverna i random ordning
    int find_char(char y[],char c);     //hitta en bokstav i en array
    
    void create_key(char alphabet[]);   //skapa din nyckel
    
    char alphabet[27];                  //array för att hålla alfabetet
    
    
    int main()
    {
      CURL *curl; 
      CURLcode res; 
      char name[20];                      //variabeln där vi sparar namnet
      char msg[200];                      //medelandet vi skickar till hemsidan med CURL
      char score[20];                      //spelarens poäng
    
        SHA1_CTX context;               //skapa en SHA1 context
        uint8_t digest[20];                //hashnyckeln då den bearbetas till HEX
        char sendhash[100];             //hash strängen vi skickar till hemsidan
        char hashme[100];               //den text sträng vi skapar hash nyckeln från
    
        //skapa alfabetet, randomisera det, och skapa nyckeln utifrån alfabetet.
    	alphabet_inorder(alphabet);
    	shuffle(alphabet);
        create_key(alphabet); 
    
        /* In windows, this will init the winsock stuff */
        curl_global_init(CURL_GLOBAL_ALL);
    
        /* get a curl handle */
        curl = curl_easy_init();
    
        //läs in spelarens namn och poäng 
        // scanf = primitivt, space funkar ej, men duger för detta exempel.
        printf("Enter name:");
        scanf("%s",name);
        printf("\nEnter Score:");
        scanf("%s",score);
    
                 /* get a curl handle */
                  curl = curl_easy_init();
    
                if(curl) 
                {
                    sprintf(hashme,"%s%s",score,createdkey);    //skapa strängen som vi vill hasha
    
                    //initiera SHA1
                    SHA1_Init(&context);		
                    //uppdatera SHA1 med vad vi vill hasha, samt längden på strängen
                    SHA1_Update(&context, (uint8_t*)hashme, strlen(hashme));
    
                    //hasha strängen och spara den i digest
                    SHA1_Final(&context, digest);
    
                    //konvertera digest till hex och spara den i "sendhash" variabeln
                    digest_to_hex(digest, sendhash);
    
                    //vi har nu vår hash-sträng klar för att skickas upp till hemsidan
    
                    //skriv in medelandet vi vill skicka till hemsidan med CURL
                    sprintf(msg,"playername=%s&score=%s&thehash=%s&project=curl",name,score,sendhash);
                      
                        /* First set the URL that is about to receive our POST. This URL can
                        just as well be a https:// URL if that is what should receive the
                        data. */
                        curl_easy_setopt(curl, CURLOPT_URL, "http://www.dinhemsida.se/highscore/insert_highscore.php");
                        /* Now specify the POST data */
                        curl_easy_setopt(curl, CURLOPT_POSTFIELDS,msg);
                        
                        /* Perform the request, res will get the return code */
                        res = curl_easy_perform(curl);
                        /* Check for errors */
                        if(res != CURLE_OK)
                        {
                        fprintf(stderr, "curl_easy_perform() failed: %s\n",
                              curl_easy_strerror(res));
                        }
                    /* always cleanup */
                    curl_easy_cleanup(curl);
                }
    
    	return 0;
    }
    
    /*hämta en bokstav ur den randomiserade arrayen
    detta medför att nyckeln skapas olika i minnet varje gång,
    vilket gör det svårare för nyfikna att kunna luska ut den*/
    void create_key(char alphabet[])
    {
        char pos=0;
        pos = find_char(alphabet,'N');
        storeme[0] = alphabet[pos];
        pos = find_char(alphabet,'Y');
        storeme[1] = alphabet[pos];
        pos = find_char(alphabet,'C');
        storeme[2] = alphabet[pos];
        pos = find_char(alphabet,'K');
        storeme[3] = alphabet[pos];
        pos = find_char(alphabet,'E');
        storeme[4] = alphabet[pos];
        pos = find_char(alphabet,'L');
        storeme[5] = '\0';               //end
    
    //om man vill skriva mellanslag:
    //    storeme[5] = ' ';
    
        sprintf(createdkey,"%s",storeme);
    }
    
    //Notera: Detta är bara stora bokstäver A-Z
    void alphabet_inorder(char x[])
    {
    	int i;
    
    	x[0] = 'A';
    	for(i=1;i<26;i++)
    	{
    		x[i]= x[0]+i;
    	}
    }
    
    void shuffle(char x[])
    {
    	int i;
    
    	char temp;                                
    	int rand_index;
    
    	srand(time(0));
    	for(i=0;i<26;i++)
    	{
    		rand_index = rand()%26;
    		temp = x[i];
    		x[i] = x[rand_index];
    		x[rand_index] = temp;
    	}
        x[26] = '\0';
    }
    
    int find_char(char y[],char c)
    {
    	int i=0;
        int found = 0;
    
        while(!found)
        {
            if(y[i] == c)
            {
                found=1;
                return i;
            }
        i++;
        }
    }
    Jag har kommenterat det mesta i koden, så jag går inte närmar in på den.

    En viktig aspekt att lyfta fram är följande:

    Vi vill inte skylta med vår hashnyckel "NYCKEL" (tempnamn).
    Skriver man:
    char *key = "NYCKEL";
    så räcker det med att man öppnar exe filen och tittar igenom den för att hitta teckensträngen.
    För att dölja detta har man 2 alternativ:

    1. Koda en lösning som skapar nyckeln
    2. Köra upx exe packare och ta bort UPX texten i .exe filen med hjälp av en hexedtor så att den ej går att packa upp igen
    3. Kombinera 1 + 2;

    I exemplet ovan så valde jag att skapa en nyckel genom randomisering.

    1. Skapa alfabetet (lägg till fler tecken om du vill)
    2. Randomisera ordningen på alfabetet med srand(time(0));
    3. Skapa nyckeln genom att söka efter önskad bokstav i strängen.

    Gör vi detta så kommer minnet aldrig att se lika dant ut då vi skapar vår lösenords sträng, samt att den inte finns synligt som text i .exe filen.

    Hämta Highscore från hemsidan - PHP


    För att ådstakomma detta så föränklar vi först tio top listan så att vi får den till ett enklare format som vi lättare kan parsa igenom.
    Vi skapar en ny fil: simple_score.php
    Kod:
    <?
    $con = mysql_connect("localhost","anvandarnamn","losenord");
    
    	if(!$con)
    	{
    		die( "Unable to connect to database");
    	}
    @mysql_select_db(dream_code_se) or die( "Unable to select database");
    $query="SELECT * FROM highscore ORDER BY score DESC LIMIT 0,10";
    
    mysql_real_escape_string($score);
    $result=mysql_query($query);
    
    mysql_real_escape_string($result);
    
    $num=mysql_numrows($result);
    
    $i=0;
    
    while ($i < $num) {
    $playername=mysql_result($result,$i,"playername");
    $score=mysql_result($result,$i,"score");
    
    echo "$playername,$score,";
    $i++;
    }
    
    mysql_close();
    ?>
    Vi har bantat koden ordentligt och kör nu bara php, istället för blandad HTML och php.

    Kod:
    while ($i < $num) {
    $playername=mysql_result($result,$i,"playername");
    $score=mysql_result($result,$i,"score");
    
    echo "$playername,$score,";
    $i++;
    }
    För att göra det "parse" vänligt skriver vi ut namn och score, åtskiljt av "," komma tecken.

    Hämta Highscore från hemsidan - CURL C kod



    Kod:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <curl/curl.h>
    
    //en enkel struct för en string med pekare och dess längd.
    struct string {
      char *ptr;
      size_t len;
    };
    
    //struct för highscore
    typedef struct highscore_table
    {
    char name[80];
    char score[80];
    }highscore_table;
    
    highscore_table test_highscore[10];               //skapa en highscore table med rum för 10st spelare
    
    //initiera strängen och lägg till endline
    void init_string(struct string *s) {
      s->len = 0;
      s->ptr = malloc(s->len+1);
      if (s->ptr == NULL) {
        fprintf(stderr, "malloc() failed\n");
        exit(EXIT_FAILURE);
      }
      s->ptr[0] = '\0';
    }
    
    //För att CURL ska kunna skriva till en char sträng så använder vi oss av denna:
    size_t writefunc(void *ptr, size_t size, size_t nmemb, struct string *s)
    {
      size_t new_len = s->len + size*nmemb;
      s->ptr = realloc(s->ptr, new_len+1);
      if (s->ptr == NULL) {
        fprintf(stderr, "realloc() failed\n");
        exit(EXIT_FAILURE);
      }
      memcpy(s->ptr+s->len, ptr, size*nmemb);
      s->ptr[new_len] = '\0';
      s->len = new_len;
    
      return size*nmemb;
    }
    
    //rensa highscore table
    void clear_highscore_table()
    {
    int i = 0,j=0;
    
            for(i=0;i<10;i++)
            {
                for(j=0;j<80;j++)
                {
                test_highscore[i].name[j] = '\0';
                test_highscore[i].score[j] = '\0';
                }
            }
    }
    
    void Parse_Highscore(char *hsstring)
    {
        int i=0,j=0;
        int count=0;
        int current_player=0;
        int current_score=0;
        int comma_begin=1;      //första kommatecknet
        int comma_end=0;        //sista kommantecknet
    
        int length = strlen(hsstring);
    
    //vi tar bort alla värden så som ny rad, tab etc och ersätter den med ett mellanslag
        for(i=0;i<length;i++)
        {
            if(hsstring[i] == '\n' || hsstring[i] == '\r' || hsstring[i] == '\t')
            {
                hsstring[i] = ' ';
            }
        }
    
        for(i=0;i<length;i++)
        {
            if(hsstring[i] == ',')
            {
                 //markera vart det andra comma tecknet är
                comma_end = i;
    
                    //varje jämt värde är ett namn
                    if(count==0 || count==2 || count==4 || count== 6 || count==8 || count==10
                    || count==10|| count==12|| count==14|| count==16 || count==18|| count==20)
                    {
                        //kopiera texten mellan comma tecknen
                        for(j=comma_begin;j<comma_end;j++)
                        {
                            //lagra spelarens namn i highscore listan
                            test_highscore[current_player].name[j-comma_begin-1] = hsstring[j];
                        }
                        current_player++;
                    }
    
                    //varje ojämt värde är poäng
                    if(count==1 || count== 3 || count==5 || count==7 || count==9
                    || count==11|| count==13 || count==15 || count==17 || count==19)
                    {
                         //kopiera texten mellan comma tecknen
                        for(j=comma_begin;j<comma_end;j++)
                        {
                            //lagra spelarens poäng i highscore listan
                            test_highscore[current_score].score[j-comma_begin-1] = hsstring[j];
                        }
                         current_score++;
                    }
                comma_begin = i;
                count++;
    
            }
        }
    }
    
    int main(void)
    {
      CURL *curl;
      CURLcode res;
      int i =0 ;
    
            clear_highscore_table();
            curl = curl_easy_init();
            if(curl)
            {
                struct string s;
                init_string(&s);
    
                curl_easy_setopt(curl, CURLOPT_URL, "http://www.dinhemsida.se/highscore/simple_score.php");
    
                //ange vår writefunc som skrivfunktion för CURL
                curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
    
                //meddela att vi vill skriva in datan i s
                curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
    
                //be curl göra allt vi bett om ovan
                res = curl_easy_perform(curl);
    
                    //om det inte gick, så medela att vi inte kunda ansluta
                    if(res != CURLE_OK)
                    {
                        printf("could not connect to website\n");
                    }
                    else
                    {
                        //gå igenom strängen
                        Parse_Highscore(s.ptr);
    
                          //skriv ut de 10 spelarnas namn och poäng
                          for(i=0;i<10;i++)
                          {
                               printf("Player:%s, Score:%s\n",test_highscore[i].name,test_highscore[i].score);
                          }
                    }
    
            free(s.ptr);
    
            /* always cleanup */
            curl_easy_cleanup(curl);
            }
    return 0;
    }
    Till Sist


    Jag hoppas att någon får nytta av denna kod, den är inte optimal och kan säkert snyggas till en hel del, men det lämnar jag upp till dig. Syftet med denna artikel är bara att visa på ett sätt hur man kan göra det, och att försöka hålla koden något så när läsbar. Det förekommer säkert ett och annat typo i texten, men det får ni ta.

    //SolarStrings, a.k.a Johan Forsblom
    Comments 1 Comment
    1. Hildenborgs avatar
      Hildenborg -
      Vet inte riktigt vad det är för sha1 system du använder till c koden, men jag lade för något år sen upp en minimalistisk c++ kod på google code: http://code.google.com/p/smallsha1/
      Den är mycket minnessnål och mycket snabb. Borde vara busenkel att översätta till ren c kod om man vill ha det så också.