Comprehensive documentation for all cryptographic functions in StarryCrypt.
All cryptographic functions are located in src/lib/crypto.ts. These functions use the Web Crypto API to perform encryption and decryption entirely in the browser.
- encryptText()
- decryptText()
- computeHmacSha512()
- computeSha256Fingerprint()
- Constants
- Error Handling
- Examples
- Security Notes
Encrypts plaintext using a password with PBKDF2-SHA512 key derivation and AES-256-GCM encryption.
async function encryptText(
text: string,
password: string,
iterations: number
): Promise<string>| Parameter | Type | Description | Constraints |
|---|---|---|---|
text |
string | The plaintext to encrypt | Any string, empty string allowed |
password |
string | Password for key derivation | Any string, non-empty recommended |
iterations |
number | PBKDF2 iterations for key derivation | 400,000 - 10,000,000 (clamped automatically) |
Type: Promise<string>
A Base64-encoded string containing:
- Protocol header (37 bytes)
- Initialization vector (12 bytes)
- AES-GCM ciphertext with authentication tag
- Throws if Web Crypto API is unavailable
- Throws if text encoding fails (rare)
import { encryptText } from '@/lib/crypto';
async function encrypt() {
const plaintext = 'Secret message';
const password = 'my-secure-password';
const iterations = 600000; // Higher = more secure but slower
try {
const ciphertext = await encryptText(plaintext, password, iterations);
console.log('Encrypted:', ciphertext);
// Output: "AQAAAAk5VvI..."
} catch (error) {
console.error('Encryption failed:', error);
}
}- Iteration count is auto-clamped to valid range
- New random salt and IV generated each time
- Same plaintext + password produces different ciphertext (due to random salt/IV)
- Encryption is deterministic only in terms of algorithm, not output
- Use higher iteration counts (1,000,000+) for sensitive data
- ~1-5 seconds depending on iteration count
- Higher iterations = slower but more secure against brute-force
- 400,000 iterations: ~1-2 seconds on modern hardware
- 1,000,000 iterations: ~3-4 seconds on modern hardware
Decrypts a ciphertext encrypted with encryptText() using a password.
async function decryptText(
encryptedBase64: string,
password: string
): Promise<string>| Parameter | Type | Description | Constraints |
|---|---|---|---|
encryptedBase64 |
string | Base64-encoded ciphertext from encryptText() |
Must be valid Base64 |
password |
string | Password used during encryption | Must match encryption password exactly |
Type: Promise<string>
The decrypted plaintext. Throws an error if:
- Ciphertext is invalid or corrupted
- Password is incorrect
- Ciphertext format is unrecognized
- Throws
Errorwith message "Decryption failed" for any failure - Generic error message for security (no details about failure type)
import { decryptText } from '@/lib/crypto';
async function decrypt() {
const ciphertext = 'AQAAAAk5VvI...'; // From encryptText()
const password = 'my-secure-password'; // Must match
try {
const plaintext = await decryptText(ciphertext, password);
console.log('Decrypted:', plaintext);
// Output: "Secret message"
} catch (error) {
console.error('Decryption failed:', error.message);
// "Decryption failed" - intentionally generic
}
}All decryption failures return a generic "Decryption failed" error:
- Wrong password
- Corrupted ciphertext
- Invalid format version
- Invalid iteration count
- Authentication tag verification failure
This prevents information leakage that could aid attackers.
- ~1-5 seconds (same as encryption)
- Time depends on iteration count stored in ciphertext
- Slower for ciphertexts encrypted with higher iteration counts
Generates an HMAC-SHA512 authentication tag for data verification.
async function computeHmacSha512(
data: string,
key: string
): Promise<string>| Parameter | Type | Description |
|---|---|---|
data |
string | The data to authenticate |
key |
string | The HMAC key |
Type: Promise<string>
Hexadecimal string representation of the HMAC-SHA512 hash (128 hex characters = 512 bits).
import { computeHmacSha512 } from '@/lib/crypto';
async function authenticate() {
const data = 'Important message';
const key = 'secret-key';
const hmac = await computeHmacSha512(data, key);
console.log('HMAC:', hmac);
// Output: "a7f3e2c1d9b8..."
}- Verify data hasn't been tampered with
- Message authentication codes
- Digital signatures (not cryptographically complete)
- Integrity checking
- HMAC-SHA512 uses SHA-512 hash function (512-bit output)
- Requires knowledge of the key to forge valid HMACs
- Not suitable for digital signatures (use RSA/ECDSA instead)
- Key should be kept secret
Generates a shortened SHA-256 fingerprint for human-readable display.
async function computeSha256Fingerprint(
data: string
): Promise<string>| Parameter | Type | Description |
|---|---|---|
data |
string | The data to fingerprint |
Type: Promise<string>
Uppercase hexadecimal string, first 12 characters of SHA-256 hash.
import { computeSha256Fingerprint } from '@/lib/crypto';
async function getFingerprint() {
const ciphertext = 'AQAAAAk5VvI...';
const fingerprint = await computeSha256Fingerprint(ciphertext);
console.log('Fingerprint:', fingerprint);
// Output: "A7F3E2C1D9B8"
}- Display abbreviated hash for quick visual verification
- QR code labels
- Message identification
- Short identifier for logs
- Length: 12 hex characters (48 bits)
- Collision Probability: ~2^-48 for random data (very low)
- Purpose: Visual identification, not cryptographic guarantee
const FORMAT_VERSION = 1;The protocol format version. Allows future compatibility with different formats.
const SALT_SIZE = 32; // Bytes (256 bits)
const HEADER_SIZE = 37; // Format(1) + Iterations(4) + Salt(32)
const IV_SIZE = 12; // Bytes (96 bits)
const GCM_TAG_SIZE = 16; // Bytes (128 bits)const MIN_ITERATIONS = 400000; // Minimum PBKDF2 iterations
const MAX_ITERATIONS = 10000000; // Maximum PBKDF2 iterationsThese are enforced to ensure:
- Minimum security (prevent too-fast derivation)
- Maximum performance (prevent excessive CPU use)
All cryptographic errors return generic messages to prevent information leakage:
// Instead of specific errors like:
// - "Password is incorrect"
// - "Ciphertext corrupted at byte 45"
// - "Authentication tag verification failed"
// All errors return:
throw new Error("Decryption failed");try {
const plaintext = await decryptText(ciphertext, password);
} catch (error) {
// Don't expose error details to users
// Instead, show generic message:
console.log('Unable to decrypt. Check password and try again.');
// Log full error for debugging (server-side only):
console.error('Debug info:', error);
}import { encryptText, decryptText } from '@/lib/crypto';
async function example() {
const plaintext = 'Sensitive information';
const password = 'user-password-123';
const iterations = 800000;
// Encrypt
console.log('Encrypting...');
const ciphertext = await encryptText(plaintext, password, iterations);
console.log('Ciphertext:', ciphertext);
// Decrypt
console.log('Decrypting...');
const decrypted = await decryptText(ciphertext, password);
console.log('Decrypted:', decrypted);
// Verify
console.log('Match:', plaintext === decrypted); // true
}import { useState } from 'react';
import { encryptText, decryptText } from '@/lib/crypto';
export function CryptoDemo() {
const [plaintext, setPlaintext] = useState('');
const [password, setPassword] = useState('');
const [ciphertext, setCiphertext] = useState('');
const [loading, setLoading] = useState(false);
const handleEncrypt = async () => {
setLoading(true);
try {
const result = await encryptText(plaintext, password, 600000);
setCiphertext(result);
} catch (error) {
console.error('Encryption failed:', error);
} finally {
setLoading(false);
}
};
const handleDecrypt = async () => {
setLoading(true);
try {
const result = await decryptText(ciphertext, password);
setPlaintext(result);
} catch (error) {
console.error('Decryption failed:', error);
} finally {
setLoading(false);
}
};
return (
<div>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter password"
/>
<textarea
value={plaintext}
onChange={(e) => setPlaintext(e.target.value)}
placeholder="Text to encrypt"
/>
<button onClick={handleEncrypt} disabled={loading}>
Encrypt
</button>
<button onClick={handleDecrypt} disabled={loading}>
Decrypt
</button>
{ciphertext && <pre>{ciphertext}</pre>}
</div>
);
}import { computeHmacSha512, computeSha256Fingerprint } from '@/lib/crypto';
async function verifyIntegrity() {
const data = 'Important data';
const key = 'shared-key';
// Generate HMAC
const hmac = await computeHmacSha512(data, key);
// Later, verify HMAC
const hmacCheck = await computeHmacSha512(data, key);
console.log('Verified:', hmac === hmacCheck); // true
// Generate fingerprint for display
const fingerprint = await computeSha256Fingerprint(data);
console.log('Fingerprint:', fingerprint); // "ABC123DEF456"
}- Strength: Stronger passwords are more secure against brute-force
- Length: Aim for 12+ characters
- Entropy: Use mix of uppercase, lowercase, numbers, symbols
- Uniqueness: Use unique passwords for different purposes
| Scenario | Recommended Iterations | Notes |
|---|---|---|
| Web application | 400,000 - 800,000 | Default, good performance |
| High security | 1,000,000 - 2,000,000 | More resistant to GPU attacks |
| Maximum security | 5,000,000 - 10,000,000 | Slow but very secure |
| Legacy hardware | 400,000 | Minimum, backward compatible |
| Function | Purpose | When to Use |
|---|---|---|
encryptText() |
Encrypt data with password | Primary use case |
decryptText() |
Decrypt data with password | Primary use case |
computeHmacSha512() |
Verify authenticity | Optional enhancement |
computeSha256Fingerprint() |
Display abbreviated hash | UI display, QR codes |
- Confidentiality: AES-256-GCM ensures encrypted data cannot be read
- Integrity: GCM authentication ensures data hasn't been modified
- Authenticity: Additional data (header) is authenticated
- Uniqueness: Random salt and IV ensure same plaintext gives different ciphertext
All functions require Web Crypto API support:
| Browser | Minimum Version | Notes |
|---|---|---|
| Chrome | 37+ | Full support |
| Firefox | 34+ | Full support |
| Safari | 11+ | Full support |
| Edge | 79+ | Full support |
| Opera | 24+ | Full support |
Features require HTTPS or localhost (Web Crypto API security requirement).
Causes:
- Incorrect password
- Corrupted ciphertext
- Browser doesn't support Web Crypto API
- Invalid Base64 encoding
Solution:
- Verify password is correct
- Ensure ciphertext wasn't modified
- Check browser compatibility
- Re-copy ciphertext carefully
Causes:
- High iteration count
- Slow device
Solution:
- Lower iteration count for faster performance
- Use 400,000 for standard use
- Pre-encrypt important data when time isn't critical
Causes:
- Non-HTTPS connection (outside localhost)
- Older browser
- Browser privacy settings
Solution:
- Use HTTPS connection
- Upgrade to modern browser
- Check privacy settings allow Web Crypto API