-
Notifications
You must be signed in to change notification settings - Fork 2
Crypt Digest
Cryptographic hashing and salted PBKDF2 password hashing using the Web Crypto API.
The Digest module provides cryptographic hash functions for creating fixed-size digests from arbitrary data, plus salted PBKDF2 password hashing (pbkdf2Hash / pbkdf2Verify) for at-rest password storage. All functions use the native Web Crypto API for secure, standards-compliant hashing.
| Feature | Bun | Deno | Node.js | Workers | Browser |
|---|---|---|---|---|---|
| SHA-1 | ✅ | ✅ | ✅ | ✅ | ✅ |
| SHA-256 | ✅ | ✅ | ✅ | ✅ | ✅ |
| SHA-384 | ✅ | ✅ | ✅ | ✅ | ✅ |
| SHA-512 | ✅ | ✅ | ✅ | ✅ | ✅ |
| Hex encoding | ✅ | ✅ | ✅ | ✅ | ✅ |
| Base64 encoding | ✅ | ✅ | ✅ | ✅ | ✅ |
| Binary input | ✅ | ✅ | ✅ | ✅ | ✅ |
| PBKDF2 (salted) | ✅ | ✅ | ✅ | ✅ | ✅ |
Deno:
deno add @tundralibs/cryptBun:
bunx jsr add @tundralibs/cryptNode.js:
npx jsr add @tundralibs/cryptGenerates a cryptographic hash using the specified algorithm.
Signature:
async function digest(
data: string | Uint8Array,
options?: DigestOptions,
): Promise<string>;Parameters:
-
data- The data to hash (string or Uint8Array) -
options- Optional configuration:-
algorithm?: 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512'- Hash algorithm (default:'SHA-256') -
encoding?: 'hex' | 'base64'- Output encoding (default:'hex')
-
Returns: Promise resolving to the encoded hash string
Throws: Error when algorithm is not supported
Example:
import { digest } from '@tundralibs/crypt/digest';
// Default SHA-256 with hex encoding
const hash = await digest('my data');
console.log(hash); // "b2167b0aa7ef7794740b055ac7a880a52934aa67ef1ca6887ad81dccefd5b9de"
// SHA-512 with base64 encoding
const hash512 = await digest('my data', {
algorithm: 'SHA-512',
encoding: 'base64',
});
// Hash binary data
const binaryData = new Uint8Array([1, 2, 3, 4]);
const binaryHash = await digest(binaryData, { algorithm: 'SHA-384' });Convenience function for SHA-256 hashing (most commonly used).
Signature:
async function sha256(
data: string | Uint8Array,
encoding?: 'hex' | 'base64',
): Promise<string>;Parameters:
-
data- The data to hash -
encoding- Output encoding (default:'hex')
Returns: Promise resolving to the SHA-256 hash
Example:
import { sha256 } from '@tundralibs/crypt/digest';
const hash = await sha256('my data');
console.log(hash); // SHA-256 hash in hex
const base64Hash = await sha256('my data', 'base64');
console.log(base64Hash); // SHA-256 hash in base64The root package barrel also exports a
hash()alias —hash(data)is exactlysha256(data), hex-encoded. It exists only on@tundralibs/crypt, not on this@tundralibs/crypt/digestsubpath:import { hash } from '@tundralibs/crypt'.
Convenience function for SHA-512 hashing.
Signature:
async function sha512(
data: string | Uint8Array,
encoding?: 'hex' | 'base64',
): Promise<string>;Parameters:
-
data- The data to hash -
encoding- Output encoding (default:'hex')
Returns: Promise resolving to the SHA-512 hash
Example:
import { sha512 } from '@tundralibs/crypt/digest';
const hash = await sha512('my data');
// Returns 128 hex characters (512 bits)Convenience function for SHA-384 hashing.
Signature:
async function sha384(
data: string | Uint8Array,
encoding?: 'hex' | 'base64',
): Promise<string>;Parameters:
-
data- The data to hash -
encoding- Output encoding (default:'hex')
Returns: Promise resolving to the SHA-384 hash
Example:
import { sha384 } from '@tundralibs/crypt/digest';
const hash = await sha384('my data');
// Returns 96 hex characters (384 bits)Convenience function for SHA-1 hashing.
Signature:
async function sha1(
data: string | Uint8Array,
encoding?: 'hex' | 'base64',
): Promise<string>;Parameters:
-
data- The data to hash -
encoding- Output encoding (default:'hex')
Returns: Promise resolving to the SHA-1 hash
Example:
import { sha1 } from '@tundralibs/crypt/digest';
const hash = await sha1('my data');
// Returns 40 hex characters (160 bits)Hash a password with salted PBKDF2 for at-rest storage. Returns a
self-describing pbkdf2-<hash>$<iterations>$<salt-hex>$<hash-hex> string that
carries everything pbkdf2Verify needs. Iterations default to the
digest-aware PBKDF2_PASSWORD_ITERATIONS (600,000 for the default SHA-256,
210,000 for SHA-384/512, per current OWASP guidance); the stored string
records its count, so raising a default never breaks old hashes.
Unlike a bare digest(), the random salt makes every hash of the same
password unique, so the output cannot be matched by equality — you verify
a candidate, you do not look a user up by their password hash.
pbkdf2Hash(
password: string,
opts?: { iterations?: number; hash?: 'SHA-256' | 'SHA-384' | 'SHA-512' },
): Promise<string>Example:
import { pbkdf2Hash, pbkdf2Verify } from '@tundralibs/crypt/digest';
const stored = await pbkdf2Hash('correct horse battery staple');
const ok = await pbkdf2Verify('correct horse battery staple', stored); // trueVerify a password against a pbkdf2Hash output. Constant-time on the digest
comparison; returns false on any malformed or unrecognised input rather
than throwing.
pbkdf2Verify(password: string, stored: string): Promise<boolean>Low-level PBKDF2 derivation to raw bytes — the primitive pbkdf2Hash builds
on. The salt (see SALT_BYTES, 16) must be stored to re-derive.
pbkdf2(
password: string,
salt: Uint8Array,
iterations: number,
bits?: number, // default 256
hash?: 'SHA-256' | 'SHA-384' | 'SHA-512', // default 'SHA-256'
): Promise<Uint8Array>For deriving a ready-to-use AES CryptoKey or fast HKDF sub-keys, see
Generators (derivePBKDF2Key, hkdf).
import { sha256 } from '@tundralibs/crypt/digest';
async function verifyFileIntegrity(
fileContent: string,
expectedHash: string,
): Promise<boolean> {
const actualHash = await sha256(fileContent);
return actualHash === expectedHash;
}
const isValid = await verifyFileIntegrity(
'file content',
'b2167b0aa7ef7794740b055ac7a880a52934aa67ef1ca6887ad81dccefd5b9de',
);import { pbkdf2Hash, pbkdf2Verify } from '@tundralibs/crypt/digest';
// ⚠️ Never store a bare SHA digest of a password — use the salted,
// deliberately slow PBKDF2 pair instead.
const stored = await pbkdf2Hash('hunter2'); // pbkdf2-sha256$600000$…
const ok = await pbkdf2Verify('hunter2', stored); // trueimport { digest } from '@tundralibs/crypt/digest';
async function generateChecksum(data: Uint8Array): Promise<string> {
// Use SHA-512 for stronger checksums
return await digest(data, { algorithm: 'SHA-512', encoding: 'hex' });
}
const checksum = await generateChecksum(new Uint8Array([1, 2, 3, 4, 5]));import { sha256 } from '@tundralibs/crypt/digest';
class ContentStore {
private entries = new Map<string, string>();
async store(content: string): Promise<string> {
const hash = await sha256(content);
this.entries.set(hash, content);
return hash;
}
retrieve(hash: string): string | undefined {
return this.entries.get(hash);
}
}
const storage = new ContentStore();
const id = await storage.store('my content');
const content = storage.retrieve(id);- SHA-256: Recommended for general use (256-bit security)
- SHA-384: Higher security for sensitive data (384-bit security)
- SHA-512: Highest security level (512-bit security)
-
SHA-1:
⚠️ Deprecated - Use only for legacy compatibility
| Algorithm | Hex Length | Base64 Length | Bits |
|---|---|---|---|
| SHA-1 | 40 chars | ~27 chars | 160 |
| SHA-256 | 64 chars | ~44 chars | 256 |
| SHA-384 | 96 chars | ~64 chars | 384 |
| SHA-512 | 128 chars | ~88 chars | 512 |
The per-algorithm byte lengths are exported as DIGEST_OUTPUT_BYTES
(e.g. DIGEST_OUTPUT_BYTES['SHA-256'] → 32).
-
Don't store bare digests of passwords - Use the salted
pbkdf2Hash/pbkdf2Verifypair - Use SHA-256 minimum - Avoid SHA-1 for new applications
- Add salt when needed - For password hashing or unique identifiers
- Consider HMAC - For message authentication (see Crypt-Sign)