cloudsurfin
cloudsurfin

Reputation: 2587

Generate an HS512 JWT in PHP without libraries?

I'm close but jwt.io doesn't like the signature I generate. With this code I generate the following JWT. If this ain't the way, how should I be generating a JWT in PHP if I can't use external libraries?

function gen_jwt():String{

    $signing_key = "changeme";
    
    // header always eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9
    $header = [ 
        "alg" => "HS512", 
        "typ" => "JWT" 
    ];
    $header = base64_url_encode(json_encode($header));
    log_message('debug',__CLASS__.'('.__FUNCTION__.':'.__LINE__.') json base64_url_encode: ' . $header);
    
    // test case 0 generates (on jwt.io): 
    //      with secret base64 encoded: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjB9.ZW0gOCJV4e1KgGEsw0bL7oCF1AI1PBL8VVgSoss4tmr7682p6DpNc1uGbBpOEfkPjKJv0JBnLvjH2XUbo8PHUg
    //      without secret b64 encoded: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjB9.pqzfdCTmr-eWW9sEgV-COCdS4dI7MDpCIFWss6kXnAC9eLdGX1qOOr8BtJih59o_U_AdHtBh8JwUQ4dEPTk0rg
    $payload =  [
        // "exp" => time() + ...,
        "exp" => 0,
    ];
    $payload = base64_url_encode(json_encode($payload));
    
    $signature = hash_hmac('sha512', "$header.$payload", $signing_key, false);
    log_message('debug',__CLASS__.'('.__FUNCTION__.':'.__LINE__.') signature: ' . $signature);
    $signature = base64_url_encode($signature);
    log_message('debug',__CLASS__.'('.__FUNCTION__.':'.__LINE__.') signature: ' . $signature);
    
    // all three parts b64 url-encoded 
    $jwt = "$header.$payload.$signature";
    log_message('debug',__CLASS__.'('.__FUNCTION__.':'.__LINE__.') jwt: ' . $jwt);

    return $jwt;
    
}

/**
 * per https://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid/15875555#15875555
 */
function base64_url_encode($text):String{
    return str_replace(
        ['+', '/', '='],
        ['-', '_', ''],
        base64_encode($text)
    );
}

/**
 * per https://www.uuidgenerator.net/dev-corner/php
 */
function guidv4($data = null): String {
    // Generate 16 bytes (128 bits) of random data or use the data passed into the function.
    $data = $data ?? random_bytes(16);
    assert(strlen($data) == 16);

    // Set version to 0100
    $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
    // Set bits 6-7 to 10
    $data[8] = chr(ord($data[8]) & 0x3f | 0x80);

    // Output the 36 character UUID.
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

Comes out: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjB9.ZmIxNzIyN2Q2ZjFhYjg3ZTJjMTY0NDJkNGQ4NzFlYWFmMjFhYzg1NzI5NGRkOGVhZmY4MTYzNWI1YTMyYWEyN2UxOTFmN2E5MzA1ZTZjZmI0OGVlZmMwN2U2NTc1MzNhZDg0NmMxMTZhZDZlOGVlYjJmMGVmOWUxOTMyYzE5MmE

...which jwt.io (and my own decoding efforts) say is an invalid signature. Help? Thanks!

Upvotes: 1

Views: 642

Answers (1)

cloudsurfin
cloudsurfin

Reputation: 2587

I guess the trick was to base64-url-encode the binary output of the hmac like... $signature = base64_url_encode(hash_hmac('sha512', "$header.$payload", $signing_key, true));

So the copy-paste-able code would be:

function gen_jwt():String{
    $signing_key = "changeme";
    $header = [ 
        "alg" => "HS512", 
        "typ" => "JWT" 
    ];
    $header = base64_url_encode(json_encode($header));
    $payload =  [
        "exp" => 0,
    ];
    $payload = base64_url_encode(json_encode($payload));
    $signature = base64_url_encode(hash_hmac('sha512', "$header.$payload", $signing_key, true));
    $jwt = "$header.$payload.$signature";
    return $jwt;    
}

/**
 * per https://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid/15875555#15875555
 */
function base64_url_encode($text):String{
    return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($text));
}

Upvotes: 1

Related Questions