Skip to content
Developer Tools9 min readPublished: August 23, 2026

How to Decode and Verify JWTs Locally: Native CLI & Zero-Bloat Cryptographic Validation

Learn how to decode Base64Url JWT payloads and verify HMAC SHA-256 signatures locally using native Linux Bash tools and Windows PowerShell without heavy third-party runtimes. By executing diagnostics directly in your terminal, you eliminate the security risks of online decoders while leveraging hardware-accelerated CPU instructions for bloat-free verification.

Written by WasyTech Engineering · Core Engineering Team
Share:𝕏RedditLinkedIn

> Security Warning: Never paste production, staging, or user-facing JSON Web Tokens (JWTs) into online web-based decoders or third-party web portals. Doing so exposes session tokens, cryptographic signatures, sensitive user identity metadata, and internal claims to third-party web server logs, browser caches, and potential man-in-the-middle exfiltration.

---

Quick Diagnostic: Native Decoding One-Liners

Execute these commands in your native shell to instantly parse a JWT payload without external packages or network overhead.

#### Linux / macOS (POSIX Bash)

bash
# Extract and decode the payload (Part 2)
JWT="your.jwt.token_here"
echo "$JWT" | cut -d'.' -f2 | awk '{l=length($0)%4; if(l==2) print $0"=="; else if(l==3) print $0"="; else print $0}' | tr '_-' '/+' | base64 -d

#### Windows (PowerShell 5.1+)

powershell
$JWT = "your.jwt.token_here"
$Payload = $JWT.Split('.')[1]
$Padded = $Payload.PadRight($Payload.Length + (4 - $Payload.Length % 4) % 4, '=')
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Padded.Replace('-', '+').Replace('_', '/')))

---

The Structural Anatomy of a JWT (RFC 7519)

As defined in IETF RFC 7519, a JSON Web Token is an open standard string format that represents claims securely between two parties. The structural format consists of three base64url-encoded parts separated by periods (.):

$$\text{JWT} = \text{Base64Url}(\text{Header}) \mathbin{\Vert} \text{"."} \mathbin{\Vert} \text{Base64Url}(\text{Payload}) \mathbin{\Vert} \text{"."} \mathbin{\Vert} \text{Base64Url}(\text{Signature})$$

bash
+-----------------------------+ . +-----------------------------+ . +-----------------------------+
|           HEADER            |   |           PAYLOAD           |   |          SIGNATURE          |
|  {"alg": "HS256", ...}      |   |  {"sub": "1234", ...}       |   |  HMACSHA256(Header.Payload) |
+-----------------------------+   +-----------------------------+   +-----------------------------+
  1. Header: Identifies the token type and cryptographic hashing algorithm (e.g., HS256, RS256, EdDSA).
  2. Payload: Contains the RFC 7519 registered, public, or private claims (such as sub, iss, iat, exp, roles, and scopes).
  3. Signature: Cryptographically generated by hashing the encoded header, a period separator, and the encoded payload using the algorithm specified in the header.

> Crucial Concept: Base64Url decoding provides zero cryptographic security. Encoding is not encryption. Any party with access to the raw token string can inspect the plaintext payload. Decoding only handles string serialization; signature validation is strictly mandatory to verify that claims have not been tampered with in transit.

---

Memory & String Allocation: POSIX C vs. .NET CLR

Decoding Base64Url strings natively without dedicated libraries requires handling character transpositions (- to +, _ to /) and byte-padding normalization ($4 - \text{length} \pmod 4$). The underlying execution environments handle memory and string transformation through vastly different architectural models:

bash
Linux (GNU Coreutils pipeline):
[stdin pipe] -> [awk padding check] -> [tr stream replacement] -> [base64 streaming buffer] -> [stdout]
                     ^                      ^                          ^
                     |                      |                          |
             (4KB I/O buffer)       (In-place mutate)          (Stack frame decode)

Windows (.NET CLR String Pipeline):
[Managed Heap (UTF-16)] -> [PadRight() -> New String] -> [Replace() -> New String] -> [Byte Array Alloc] -> [UTF-8 String Alloc]

#### Linux GNU Coreutils (base64, tr, awk) * Streaming Memory Architecture: POSIX pipes stream memory via standard 4096-byte kernel pipe buffers. Data moves through Linux file descriptors without allocating massive continuous heap structures. * Primitive Mutability: tr performs in-place byte translation inside a small fixed-size buffer (char array), eliminating memory allocation overhead. * Process Isolation: The decoding process relies on small C binaries (/usr/bin/base64). GNU coreutils operates with near-zero RSS memory (typically under 1.5 MB total process size), terminating execution immediately upon writing bytes to standard output (stdout).

#### Windows PowerShell (.NET System.Convert) * CLR String Immutability: .NET represents strings as immutable UTF-16 character arrays on the managed heap. Operations like .Replace() and .PadRight() do not mutate in place; they allocate an entirely new System.String instance for every intermediate transformation. * Type Marshaling Overhead: Converting a Base64 string to readable text requires calling [System.Convert]::FromBase64String(), which yields an intermediate managed byte array (byte[]). This byte array must then be parsed via [System.Text.Encoding]::UTF8.GetString(). * Garbage Collection Pressure: While perfectly manageable for ad-hoc terminal diagnostics, processing millions of tokens in high-throughput loops via unmanaged PowerShell scripts generates high object allocation rates across Generation 0 of the .NET CLR Garbage Collector.

---

Native CLI Decoding Implementations

These native scripts decode both the header and the payload cleanly while accommodating variable-length Base64Url padding constraints.

#### Zero-Dependency Bash Implementation

bash
#!/usr/bin/env bash
# Native JWT Decoder using POSIX tools (sh, awk, tr, base64)

decode_jwt() {
    local jwt="$1"
    
    # Split token segments by period delimiter
    local header_b64=$(echo "$jwt" | cut -d'.' -f1)
    local payload_b64=$(echo "$jwt" | cut -d'.' -f2)
    local signature_b64=$(echo "$jwt" | cut -d'.' -f3)

    if [ -z "$header_b64" ] || [ -z "$payload_b64" ]; then
        echo "Error: Invalid JWT structure. Expected format: Header.Payload.Signature" >&2
        return 1
    fi

    # Inner function to normalize Base64Url to standard Base64 and decode
    b64url_decode() {
        local input="$1"
        local len=${#input}
        local pad=$(( (4 - (len % 4)) % 4 ))
        
        # Append base64 padding '=' characters
        local padded="$input"
        [ $pad -eq 1 ] && padded="${input}="
        [ $pad -eq 2 ] && padded="${input}=="
        [ $pad -eq 3 ] && padded="${input}==="

        # Translate '-' to '+' and '_' to '/', then decode
        echo "$padded" | tr -- '-_' '+/' | base64 -d 2>/dev/null
    }

    echo "=== HEADER ==="
    b64url_decode "$header_b64"
    echo -e "\n\n=== PAYLOAD ==="
    b64url_decode "$payload_b64"
    echo -e "\n\n=== SIGNATURE (Raw Base64Url) ==="
    echo "$signature_b64"
}

# Example usage:
# decode_jwt "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

#### Zero-Dependency PowerShell Implementation

powershell
function ConvertFrom-Jwt {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Token
    )

    $parts = $Token.Split('.')
    if ($parts.Count -lt 2) {
        Write-Error "Invalid JWT format. Must contain at least a Header and Payload separated by '.'"
        return
    }

    function Parse-Base64Url ([string]$Segment) {
        # Calculate RFC-compliant Base64 padding
        $mod4 = $Segment.Length % 4
        if ($mod4 -gt 0) {
            $Segment = $Segment.PadRight($Segment.Length + (4 - $mod4), '=')
        }
        # Translate RFC 7519 URL safe characters back to standard Base64 characters
        $base64 = $Segment.Replace('-', '+').Replace('_', '/')
        $bytes = [System.Convert]::FromBase64String($base64)
        return [System.Text.Encoding]::UTF8.GetString($bytes)
    }

    [PSCustomObject]@{
        Header    = Parse-Base64Url $parts[0]
        Payload   = Parse-Base64Url $parts[1]
        Signature = if ($parts.Count -ge 3) { $parts[2] } else { $null }
    }
}

# Example usage:
# ConvertFrom-Jwt -Token "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

---

Cryptographic Verification via Hardware Acceleration

When verifying an HMAC SHA-256 (HS256) signature locally, operating systems leverage hardware cryptographic instruction sets compiled directly into low-level cryptographic libraries (like OpenSSL or Windows CNG/CAPI):

  • Intel/AMD SHA Extensions: Exposes assembly instructions such as SHA256RNDS2, SHA256MSG1, and SHA256MSG2. These instructions run cryptographic rounds directly on vector execution units, executing SHA-256 hashing in a fraction of the clock cycles required by software-based bit-shift iterations.
  • ARMv8 Cryptography Extensions: Implements native instructions including SHA256H and SHA256SU0 directly within ARM NEON execution pipelines.

Heavyweight GUI applications and Electron wrappers wrap bloated V8 runtimes, JavaScript garbage collectors, and multiple layer abstractions around these operations. Native command-line verification runs with zero-copy efficiency, binding directly to platform-native crypto APIs.

bash
+-----------------------------------------------------------------------------------+
| Node.js / Electron:                                                               |
| [JS VM Context] -> [V8 Native C++ Binding] -> [OpenSSL] -> [CPU SHA Instructions] |
+-----------------------------------------------------------------------------------+
| Native CLI (OpenSSL / .NET):                                                      |
| [Shell / PowerShell] -> [Platform Crypto API] -----------> [CPU SHA Instructions] |
+-----------------------------------------------------------------------------------+

#### Linux Local Verification Script (OpenSSL)

This script validates whether a symmetric secret key signed the JWT token using OpenSSL's digest engine:

bash
#!/usr/bin/env bash
# Verify HS256 JWT signature using OpenSSL and native Bash

verify_jwt_hs256() {
    local jwt="$1"
    local secret="$2"

    local header=$(echo "$jwt" | cut -d'.' -f1)
    local payload=$(echo "$jwt" | cut -d'.' -f2)
    local provided_signature=$(echo "$jwt" | cut -d'.' -f3)

    # Reconstruct the HMAC signing input string (Header.Payload)
    local signing_input="${header}.${payload}"

    # Compute raw binary HMAC SHA-256 and encode to Base64Url
    local calculated_signature=$(echo -n "$signing_input" | \
        openssl dgst -sha256 -hmac "$secret" -binary | \
        openssl base64 -e | \
        tr -d '=' | tr '/+' '_-' | tr -d '\n')

    echo "Provided Signature:   $provided_signature"
    echo "Calculated Signature: $calculated_signature"

    if [ "$provided_signature" = "$calculated_signature" ]; then
        echo -e "\n\033[0;32m[+] SUCCESS: Cryptographic signature matches. Token is authentic.\033[0m"
        return 0
    else
        echo -e "\n\033[0;31m[-] ERROR: Signature mismatch! Token has been tampered with or secret is invalid.\033[0m"
        return 1
    fi
}

# Example usage:
# verify_jwt_hs256 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" "your-256-bit-secret"

#### Windows PowerShell Verification Script (System.Security.Cryptography)

Leveraging the System.Security.Cryptography.HMACSHA256 class from Microsoft .NET, this script computes the verification hash natively:

powershell
function Test-JwtSignature {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Token,
        
        [Parameter(Mandatory = $true)]
        [string]$Secret
    )

    $parts = $Token.Split('.')
    if ($parts.Count -ne 3) {
        Write-Error "A complete JWT containing Header, Payload, and Signature is required for verification."
        return
    }

    $header = $parts[0]
    $payload = $parts[1]
    $providedSignature = $parts[2]

    # Reconstruct input
    $signingInput = "$header.$payload"
    $signingInputBytes = [System.Text.Encoding]::UTF8.GetBytes($signingInput)
    $secretBytes = [System.Text.Encoding]::UTF8.GetBytes($Secret)

    # Compute HMAC SHA-256 using .NET native cryptography providers
    $hmac = New-Object System.Security.Cryptography.HMACSHA256
    $hmac.Key = $secretBytes
    $computedHashBytes = $hmac.ComputeHash($signingInputBytes)
    $hmac.Dispose()

    # Convert binary digest to Base64Url format
    $computedSignature = [System.Convert]::ToBase64String($computedHashBytes)`
        .Split('=')[0]`
        .Replace('+', '-')`
        .Replace('/', '_')

    Write-Host "Provided Signature:   $providedSignature"
    Write-Host "Calculated Signature: $computedSignature"

    # Constant-time comparison or clean equality validation
    if ($providedSignature -eq $computedSignature) {
        Write-Host "`n[+] SUCCESS: Cryptographic signature matches. Token is authentic." -ForegroundColor Green
        return $true
    } else {
        Write-Host "`n[-] ERROR: Signature mismatch! Token invalid or wrong secret provided." -ForegroundColor Red
        return $false
    }
}

# Example usage:
# Test-JwtSignature -Token "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" -Secret "your-256-bit-secret"

---

Performance & Security Comparison

Evaluation MetricNative CLI (Bash / Coreutils)Native PowerShell (.NET)Online Web Tools (e.g., jwt.io)Heavy Desktop Apps (Electron)
**Data Privacy****100% Local** (No network I/O)**100% Local** (No network I/O)**Critical Risk** (Third-party servers)**Moderate Risk** (Telemetry/Network)
**Memory Footprint****< 2 MB RSS****~30 - 50 MB**High Browser Tab Overhead**150 - 400 MB+**
**Startup / Exec Time****< 5 ms****~50 - 150 ms**Network/Render Bound (>500ms)**1500 - 4000 ms**
**Cryptographic Execution**Direct C / AssemblyNative `.NET Core` CAPI/CNGBrowser V8 JavaScript engineEmbedded Chromium V8 engine
**Dependencies**None (Built-in POSIX)None (Built-in Windows)Web Browser, Internet connectionNode.js runtime, Chromium binary

---

Architectural Takeaways

  1. Keep Secrets Air-Gapped: By decoding and validating your JSON Web Tokens directly in the shell, you eliminate the risk of exposing sensitive authentication parameters, database identifiers, and customer roles to public web proxies or external log pipelines.
  2. Decode vs. Verify: Never make authorization decisions based on decoded payloads alone. The payload is readable by anyone who captures it; validity is established exclusively by cryptographically computing and matching the signature against trusted keys.
  3. Eliminate Diagnostics Bloat: You do not need to install gigabytes of bloated GUIs or install unstable NPM packages just to inspect tokens. Your operating system's native tools already contain optimized, hardware-accelerated cryptographic primitives capable of executing in sub-millisecond timeframes.

Frequently Asked Technical Questions

Yes. As specified in RFC 7519, standard JWT headers and payloads are merely Base64Url-encoded, not encrypted. Anyone can decode and inspect the JSON claims using standard string manipulation utilities without the cryptographic key. However, without the secret or public key to verify the cryptographic signature, you cannot guarantee the payload has not been modified or forged in transit.

WasyTech Engineering

Core Engineering Team

The collective engineering minds behind WasyTech's zero-bloat utility architecture.

Focus:Systems ProgrammingPerformance DiagnosticsWindows InternalsNetwork Protocols

Related Systems Guides

View all guides →
Developer Tools8 min readAugust 23, 2026

How to View EXIF Data Without Uploading: Local File I/O and Native Metadata Extraction

Discover how operating system frameworks like Windows propsys.dll, macOS ImageIO, and Linux libexif parse image headers directly from disk without network transmission. Learn the underlying binary architecture of JPEG APP1 markers and HEIC metadata containers while avoiding the telemetry risks of web-based EXIF viewers.