Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to calculate PCKE's code_verifier?

I'm going through Okta's PCKE Flow demo to get a better understanding of how it works, and I'm having trouble reproducing the same code_challenge hash that's being generated from the code_verifier. Here's a screenshot of the demo:

Pcke Flow Demo

Using Zg6klgrnixQJ629GsawRMV8MjWvwRAr-vyvP1MHnB6X8WKZN as the code verifier, how did they produce
iF_7prUeJ6rr3jMG3LmhW3R1cZ2ecZavFqS0jtb6tzo as the code challenge?

Using this SHA256 hash calculator and Base64 Encoder, I got ODg1ZmZiYTZiNTFlMjdhYWViZGUzMzA2ZGNiOWExNWI3NDc1NzE5ZDllNzE5NmFmMTZhNGI0OGVkNmZhYjczYQ which doesn't match the expected value of iF_7prUeJ6rr3jMG3LmhW3R1cZ2ecZavFqS0jtb6tzo. What am I doing wrong to not get the expected value?

This SHA256 base 64 hash calculator from approsto gives me a value that is very close to the expected value. Using this calculator I get iF/7prUeJ6rr3jMG3LmhW3R1cZ2ecZavFqS0jtb6tzo which is one character off from the expected value (notice how there's a / instead of _).

What am I doing that's causing this discrepancy? How do I calculate the expected code_verifier value of iF_7prUeJ6rr3jMG3LmhW3R1cZ2ecZavFqS0jtb6tzo? Thanks

like image 484
burnt1ce Avatar asked Jan 25 '20 16:01

burnt1ce


People also ask

How do I get code challenge?

Create code challenge: Generate a code_challenge from the code_verifier that will be sent to Auth0 to request an authorization_code . Authorize user: Request the user's authorization and redirect back to your app with an authorization_code . Request tokens: Exchange your authorization_code and code_verifier for tokens.

What does PKCE stand for?

PKCE, pronounced “pixy” is an acronym for Proof Key for Code Exchange.

What is code challenge and verifier?

The code verifier is a cryptographically random string using the characters A-Z, a-z, 0-9, and the punctuation characters -. _~ (hyphen, period, underscore, and tilde), between 43 and 128 characters long. Once the client has generated the code verifier, it uses that to create the code challenge.

What is PKCE grant type?

Key Concepts. Learn about the OAuth 2.0 grant type, Authorization Code Flow with Proof Key for Code Exchange (PKCE). Use this grant type for applications that cannot store a client secret, such as native or single-page apps.


3 Answers

The PKCE code challenge is the Base64-URL-encoded SHA256 hash of the verifier. This means you need to take the original string, calculate the SHA256 hash of it, then Base64-URL-encode the hash. That's a lot of words, so let's walk through it.

There are two problems with what you've tried to do above:

The online SHA256 hash calculator you found outputs the hash as a hex-encoded string rather than the raw bytes. That's typically helpful, but in this case is not. So the next thing you're doing by base64 encoding is that you're base64 encoding the hex representation of the hash rather than the raw bytes. You need to use a hash function that outputs the raw bytes, and pass the raw bytes into the base64-url-encoder.

The next problem is that you need to base64-url encode, not base64 encode. Base64-URL-encoding is a minor variation of Base64 encoding, where the only difference is using the character - instead of + and _ instead of /, and trimming the = padding characters from the end. This makes it URL-safe, since otherwise the +/= characters would need to be escaped in the URL.

So, to calculate the PKCE code challenge, you need to use a SHA256 function that can give you the raw bytes, then use a modified Base64 encoding function to encode those bytes.

Here is some code in PHP that will do that:



    function pkce_code_challenge($verifier) {
        $hash = hash('sha256', $verifier, true);
        return rtrim(strtr(base64_encode($hash), '+/', '-_'), '=');
    }

It's also possible in plain JavaScript in a browser, but the code is slightly longer due to the complexity of the WebCrypto APIs:



    function sha256(plain) { 
        // returns promise ArrayBuffer
        const encoder = new TextEncoder();
        const data = encoder.encode(plain);
        return window.crypto.subtle.digest('SHA-256', data);
    }

    function base64urlencode(a) {
        // Convert the ArrayBuffer to string using Uint8 array.
        // btoa takes chars from 0-255 and base64 encodes.
        // Then convert the base64 encoded to base64url encoded.
        // (replace + with -, replace / with _, trim trailing =)
        return btoa(String.fromCharCode.apply(null, new Uint8Array(a)))
            .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
    }

    async function pkce_challenge_from_verifier(v) {
        hashed = await sha256(v);
        base64encoded = base64urlencode(hashed);
        return base64encoded;
    }

like image 79
aaronpk Avatar answered Oct 21 '22 07:10

aaronpk


Based on Aaron's example and hacking the pkce-challenge node package, here's what I use:

class PkceChallenge {
    random(length, mask) {
        let result = "";
        let randomIndices = new Int8Array(length);
        window.crypto.getRandomValues(randomIndices);
        const byteLength = 256
        const maskLength = Math.min(mask.length, byteLength);
        const scalingFactor = byteLength / maskLength;

        for (var i = 0; i < length; i++) {
            result += mask[Math.floor(Math.abs(randomIndices[i]) / scalingFactor)];
        }
        return result;
    }

    base64UrlEncode(array) {
        return btoa(String.fromCharCode.apply(null, new Uint8Array(array)))
            .replace(/\+/g, '-')
            .replace(/\//g, '_')
            .replace(/=+$/, '');
    }

    generateVerifier(length) {
        const mask = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
        return this.random(length, mask);
    }

    generateChallenge(length = 43) {
        this.verifier = this.generateVerifier(length);

        const encoder = new TextEncoder();
        const data = encoder.encode(this.verifier);
        return window.crypto.subtle.digest('SHA-256', data).then(array => { return { code_challenge: this.base64UrlEncode(array), code_verifier: this.verifier }; });
    }
}
like image 37
Liam Avatar answered Oct 21 '22 07:10

Liam


echo -n "qwe" | sha256sum -b | xxd -p -r | base64 | tr '/+' '_-' | tr -d '='

where is qwe is code_verifier

like image 43
Alexander Manko Avatar answered Oct 21 '22 08:10

Alexander Manko