npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, πŸ‘‹, I’m Ryan HefnerΒ  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you πŸ™

Β© 2025 – Pkg Stats / Ryan Hefner

pw-punch

v1.1.3

Published

πŸ” Ultra-lightweight password hashing & token signing with WebCrypto. Zero dependencies. Edge-native. Built for Cloudflare, Deno, Bun, and Vercel.

Readme

pw-punch

πŸ₯Š pw-punch

πŸ” Ultra-lightweight password hashing & JWT-style token signing with pure WebCrypto.
Built for Edge, Serverless, and modern runtimes like Cloudflare, Deno, Vercel, Bun β€” no Node.js required.
Zero dependencies. Zero overhead. Just crypto.


⚑ Why pw-punch?

  • βœ… 0 dependencies β€” no extra weight
  • βœ… 0 Node.js required β€” pure WebCrypto API
  • βœ… 0 config β€” import and go
  • βœ… ~4KB gzipped β€” tiny footprint
  • βœ… Crypto only β€” no extra fluff

πŸ” Features

  • πŸ”’ Password hashing with PBKDF2 + random salt
  • ✍️ RSA-SHA256 (RS256) token signing (JWT standard)
  • πŸ•΅οΈ Token verification with standard claim checks (exp, nbf, iat, iss, aud, sub)
  • πŸ”„ Supports key rotation (kid support)
  • πŸ§ͺ Constant-time comparison utilities
  • 🧩 WebCrypto only β€” works on:
    • βœ… Cloudflare Workers
    • βœ… Deno Deploy
    • βœ… Bun
    • βœ… Modern Browsers
    • βœ… Node 18+ (WebCrypto)
  • πŸ’‘ Fully tree-shakable

πŸ“¦ Install

npm install pw-punch

πŸ”§ API Usage

πŸ”’ Hash a password

import { hashPassword } from 'pw-punch'

const hashed = await hashPassword('hunter2')
// "base64salt:base64hash"

βœ… Verify a password

import { verifyPassword } from 'pw-punch'

const isValid = await verifyPassword('hunter2', hashed)
// true or false

✍️ Sign a token

import { signToken } from 'pw-punch'

// Generate RSA key pair first
const keyPair = await crypto.subtle.generateKey(
  {
    name: 'RSASSA-PKCS1-v1_5',
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: 'SHA-256'
  },
  true,
  ['sign', 'verify']
)

const token = await signToken(keyPair.privateKey, { sub: 'user' }, { kid: 'key-1' })

πŸ•΅οΈ Verify a token

import { verifyToken } from 'pw-punch'

const payload = await verifyToken(token, keyPair.publicKey)
// returns payload or null

πŸ” Decode token (without verifying)

import { decodeToken } from 'pw-punch'

const { header, payload, signature } = decodeToken(token)

πŸ“˜ Full Example

// Generate RSA key pair
const keyPair = await crypto.subtle.generateKey(
  {
    name: 'RSASSA-PKCS1-v1_5',
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: 'SHA-256'
  },
  true,
  ['sign', 'verify']
)

// Sign token with minimal header (recommended)
const token = await signToken(keyPair.privateKey, { sub: 'user' })

// Sign token with key rotation
const tokenWithKid = await signToken(keyPair.privateKey, { sub: 'user' }, { kid: 'key-v1' })

// Sign token without typ field (shorter)
const shortToken = await signToken(keyPair.privateKey, { sub: 'user' }, { includeTyp: false })

// Verify token
const payload = await verifyToken(token, keyPair.publicKey)

πŸ” Security Guidelines

πŸ”‘ Key Management Best Practices

  • Key Size: Use minimum 2048-bit RSA keys (4096-bit recommended for high security)
  • Key Rotation: Rotate keys regularly and use kid field for versioning
  • Key Storage: Never store private keys in client-side code
  • Key Generation: Use crypto.subtle.generateKey() for secure random generation
// βœ… Good: Strong key generation
const keyPair = await crypto.subtle.generateKey(
  {
    name: 'RSASSA-PKCS1-v1_5',
    modulusLength: 4096, // Strong key size
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: 'SHA-256'
  },
  false, // Non-extractable for security
  ['sign', 'verify']
)

⏰ Token Expiration Guidelines

  • Short-lived tokens: 15 minutes to 1 hour for high-security APIs
  • Regular tokens: 1-24 hours for standard applications
  • Refresh strategy: Use refresh tokens for longer sessions
// βœ… Good: Appropriate expiration times
const now = Math.floor(Date.now() / 1000)
const payload = {
  sub: 'user123',
  iat: now,
  exp: now + 3600, // 1 hour expiration
  nbf: now // Valid from now
}

πŸ›‘οΈ Validation Best Practices

// βœ… Good: Custom validation with security checks
const payload = await verifyToken(token, publicKey, (claims) => {
  // Check issuer
  if (claims.iss !== 'trusted-issuer') return false
  
  // Check audience
  if (!claims.aud?.includes('my-api')) return false
  
  // Check custom claims
  if (claims.role !== 'admin' && claims.action === 'delete') return false
  
  return true
})

πŸ“– API Reference

hashPassword(password, type?, iterations?)

Hashes a password using PBKDF2 with SHA-256 or SHA-512.

Parameters:

  • password (string): Plain-text password to hash
  • type (256 | 512): Hash algorithm. Default: 256
  • iterations (number): PBKDF2 iterations. Default: 150,000

Returns: Promise<string> - Base64-encoded "salt:hash"

verifyPassword(password, hashed, type?, iterations?)

Verifies a password against a PBKDF2 hash.

Parameters:

  • password (string): Plain-text password to verify
  • hashed (string): Stored hash from hashPassword()
  • type (256 | 512): Hash algorithm. Default: 256
  • iterations (number): PBKDF2 iterations. Default: 150,000

Returns: Promise<boolean> - True if password matches

signToken(privateKey, payload, options?)

Signs a JWT token using RS256.

Parameters:

  • privateKey (CryptoKey): RSA private key for signing
  • payload (TokenPayload): JWT payload with claims
  • options (object, optional):
    • kid (string): Key ID for key rotation
    • includeTyp (boolean): Include "typ: JWT" header. Default: true

Returns: Promise<string> - Signed JWT token

verifyToken(token, publicKey, customValidate?)

Verifies and decodes a JWT token.

Parameters:

  • token (string): JWT token to verify
  • publicKey (CryptoKey): RSA public key for verification
  • customValidate (function, optional): Custom validation function

Returns: Promise<TokenPayload | null> - Decoded payload or null if invalid

decodeToken(token)

Decodes a JWT token without verification (for inspection only).

Parameters:

  • token (string): JWT token to decode

Returns: {header, payload, signature} - Decoded parts or null if invalid


πŸ§ͺ Tests & Demo

  • βœ… All core features tested using bun test
  • βœ… Additional interactive demo available:
npm run demo

Select and run hashing/token functions in CLI with colored output. Great for dev previewing & inspection.


πŸ“¦ Built With

  • πŸŒ€ 100% WebCrypto (FIPS-compliant)
  • ⚑ Bun for test/dev (optional)
  • πŸ“ TypeScript + tsc build
  • πŸ”¬ No dependencies at all

⚠️ Disclaimer

This is not a full JWT spec implementation.

  • Only RS256 is supported (no HMAC/EC)
  • You must check claims like aud, iss yourself, or provide a customValidate() hook
  • No support for JWE/JWS standards
  • RSA key pair management is up to the user

This is the way.


πŸ“„ License

MIT