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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@otterseal/core

v0.0.2

Published

🦦 OtterSeal shared cryptography utilities - AES-256-GCM encryption with HKDF key derivation

Readme

🦦 @otterseal/core

Zero-knowledge cryptography library for OtterSeal. Provides secure encryption, decryption, and key derivation using industry-standard algorithms.

Features

  • AES-256-GCM Encryption: Authenticated encryption with associated data
  • HKDF Key Derivation: Cryptographically secure key generation from titles
  • Zero Knowledge: Keys never leave the browser/client
  • Type Safe: Full TypeScript support with strict types

Installation

npm install @otterseal/core

Usage

Derive an Encryption Key from a Title

import { deriveKey } from '@otterseal/core';

const title = 'My Secret Note';
const encryptionKey = await deriveKey(title);

Hash a Title to Get a Note ID

import { hashTitle } from '@otterseal/core';

const title = 'My Secret Note';
const noteId = await hashTitle(title);
// Note ID is safe to send to the server (cannot derive key from it)

Encrypt Content

import { encryptNote } from '@otterseal/core';

const content = 'This is my secret message';
const key = await deriveKey('My Secret Note');
const encrypted = await encryptNote(content, key);
// `encrypted` is now a base64 string safe to store/transmit

Decrypt Content

import { decryptNote } from '@otterseal/core';

const encryptedContent = '...'; // from server
const key = await deriveKey('My Secret Note');
const plaintext = await decryptNote(encryptedContent, key);
console.log(plaintext); // 'This is my secret message'

Security Architecture

Key Derivation

OtterSeal uses HKDF (HMAC-based Key Derivation Function) to cryptographically separate the database ID from the encryption key:

  1. Master Secret: HKDF-Extract(Title) — The raw secret derived from the user's title
  2. Note ID (Public): HKDF-Expand(Master, info="ID", salt="SecurePad")
    • Sent to the server for database lookups
    • Cannot be used to derive the encryption key
  3. Encryption Key (Private): HKDF-Expand(Master, info="KEY", salt="SecurePad")
    • Never leaves the client
    • Used for AES-256-GCM encryption

Encryption

All content is encrypted using AES-256-GCM:

  • Mode: Galois/Counter Mode (authenticated encryption)
  • Key Size: 256 bits
  • Authentication: GCM provides built-in authentication (AEAD)
  • Tamper Detection: Any modification to ciphertext is detected and rejected

API Reference

hashTitle(title: string): Promise<string>

Derives a public ID from a title. Safe to send to the server.

Parameters:

  • title — The note title

Returns: Promise resolving to a base64-encoded note ID

deriveKey(title: string): Promise<Uint8Array>

Derives the encryption key from a title. Never sent to the server.

Parameters:

  • title — The note title

Returns: Promise resolving to a 32-byte (256-bit) encryption key

encryptNote(content: string, key: Uint8Array): Promise<string>

Encrypts content using AES-256-GCM.

Parameters:

  • content — Plaintext to encrypt
  • key — Encryption key (from deriveKey())

Returns: Promise resolving to base64-encoded ciphertext

decryptNote(encrypted: string, key: Uint8Array): Promise<string>

Decrypts AES-256-GCM ciphertext.

Parameters:

  • encrypted — Base64-encoded ciphertext (from encryptNote())
  • key — Encryption key (must match the key used for encryption)

Returns: Promise resolving to plaintext, or throws if decryption fails

Error Handling

Decryption will throw an error if:

  • The ciphertext is invalid or corrupted
  • The key is incorrect
  • The ciphertext has been tampered with

Example:

import { decryptNote } from '@otterseal/core';

try {
  const plaintext = await decryptNote(corrupted, key);
} catch (err) {
  console.error('Decryption failed:', err.message);
  // Handle error (e.g., show "Note corrupted or wrong password" to user)
}

Browser Compatibility

  • All modern browsers (Chrome, Firefox, Safari, Edge)
  • Node.js 20+
  • Uses the Web Crypto API (SubtleCrypto)

License

MIT