@web5/crypto
v1.0.6
Published
Web5 cryptographic library
Downloads
19,877
Readme
Web5 Crypto API
| A cryptography and JOSE library for building secure Web5 applications | | --------------------------------------------------------------------- |
- Web5 Crypto API
The Web5 Crypto API is a core component of the Web5 JS ecosystem, providing the cryptography and JSON Object Signing and Encryption (JOSE) capabilities essential for building secure applications and services with Decentralized Identifiers (DID) and Verifiable Credentials (VC).
This JavaScript library was designed for modern development runtimes, including Node.js, web browsers, and React Native. It provides cryptographic functionality for cipher, hash, and signature algorithms and basic JOSE support for JSON Web Key (JWK). Additionally, it includes the crypto interfaces and local Key Management System (KMS) implementation that are used by other libraries in this monorepo.
Supported Algorithms & Key Types
The following algorithms and key types are currently supported, with plans to expand its offerings as the library progresses towards a 1.0 release.
| Capability | Details | | -------------- | ----------------------------------------------- | | Cipher | AES-CTR, AES-GCM, XChaCha20, XChaCha20-Poly1305 | | Signature | ECDSA, EdDSA | | Hash | SHA-256 | | Key Derivation | ConcatKDF, ECDH, PBKDF2 | | ECC Curves | Ed25519, secp256k1, X25519 |
Extensions
Packages that extend the functionality of the @web5/crypto
library:
| Extension | Repository | | ----------------- | ------------------------------------------------------ | | AWS KMS extension | TBD54566975/crypto-aws-kms |
Getting Started
The Web5 Crypto API is distributed as @web5/crypto
via npmjs.com,
jsdelivr.com, unpkg.com, and
github.com.
Node.js
This library is designed and tested for the active (v20
) and maintenance
(v18
) LTS releases of Node.js
Install the latest version of @web5/crypto
using npm
or your preferred package manager:
npm install @web5/crypto
Example ESM import:
import { Ed25519 } from "@web5/crypto";
Example CJS require:
const { Ed25519 } = require("@web5/crypto");
Web Browsers
A polyfilled distribution is published to jsdelivr.com and
unpkg.com that can imported in a module <script>
tag:
<!DOCTYPE html>
<html lang="en">
<body>
<script type="module">
// Example import from JSDELIVR
import { Ed25519 } from "https://cdn.jsdelivr.net/npm/@web5/crypto/dist/browser.mjs";
</script>
</body>
</html>
[!IMPORTANT] The
@web5/crypto
library depends on the Web Crypto API in web browsers. Web Crypto is available in all modern browsers but is accessible only in a secure context.This means you will not be able to use many
@web5/crypto
features in a browser unless the page is served overhttps://
orwss://
. Locally-delivered resources such as those withhttp://127.0.0.1
,http://localhost
, orhttp://*.localhost
URLs are also considered to have been delivered securely.
React Native
For React Native, you may need a
polyfill for crypto.getRandomValues
.
import "react-native-get-random-values";
Contributing
We welcome you to join our open source community. Whether you're new to open source or a seasoned contributor, there's a place for you here. From coding to documentation, every contribution matters. Check out our contribution guide for ways to get started.
For help, discussion about best practices, or to chat with others building on Web5 join our Discord Server:
Remember, contributing is not just about code; it's about building together. Join us in shaping the future of the Web!
Core Concepts
Key URIs
One of the core design principles for the SDKs in the Web5 ecosystem is the protection of private key material. Instead of directly handling sensitive key information, our SDKs interact with cryptographic keys through Key Management Systems (KMS) referenced by a unique identifier called a Key URI. This approach ensures that private keys remain secure, while still allowing for versatile cryptographic functionalities.
Each KMS assigns a unique identifier to a key when it is created. This identifier can be used to form a Uniform Resource Identifier (URI) by adding a prefix. The following table shows the format of supported Key URIs:
| Prefix | Key URI Format |
| --------- | -------------------------- |
| urn:jwk
| urn:jwk:<jwk-thumbprint>
|
All cryptographic keys are represented in JSON Web Key
(JWK) format and the jwk-thumbprint
, a
standardized, deterministic, and unique hash of the
key, acts as a fingerprint, enabling consistent key referencing across all Web5 libraries without
exposing private key information.
Using a Local KMS
This library includes a self-contained Key Management System (KMS) implementation that supports generating keys, hashing, and creating/verifying digital signatures. Cryptographic operations are performed within the confines of the local environment, without relying on external services. This KMS is designed to be used by browser-based web apps or when testing backend services that depend on cloud-based services (e.g., AWS KMS). Extensions that support external KMS services are listed here.
Start by instantiating a local KMS implementation of the CryptoApi
interface:
import { LocalKeyManager } from "@web5/crypto";
const kms = new LocalKeyManager();
An ephemeral, in-memory key store is used by default but any persistent store that implements the
KeyValueStore
interface can also be passed. See the Persistent Key Store customization
for an example.
Generate a random private key:
const privateKeyUri = await kms.generateKey({ algorithm: "Ed25519" });
console.log(privateKeyUri);
// Output: urn:jwk:8DaTzHZcvQXUVvl8ezQKgGQHza1hiOZlPkdrB55Vt6Q
Create an EdDSA signature over arbitrary data using the private key:
const data = new TextEncoder().encode("Message");
const signature = await kms.sign({
keyUri: privateKeyUri,
data,
});
console.log(signature);
// Output:
// Uint8Array(64) [
// 208, 172, 103, 148, 119, 138, 101, 95, 41, 218, 64,
// 178, 132, 102, 15, 126, 102, 85, 3, 174, 96, 140,
// 203, 58, 186, 72, 85, 64, 15, 250, 235, 12, 97,
// 247, 52, 31, 110, 58, 85, 191, 161, 253, 82, 4,
// 1, 4, 230, 135, 81, 145, 59, 128, 83, 106, 105,
// 135, 32, 124, 96, 0, 25, 41, 218, 75
// ]
Get the public key in JWK format:
const publicKey = await kms.getPublicKey({ keyUri: privateKeyUri });
console.log(publicKey);
// Output:
// {
// kty: "OKP",
// crv: "Ed25519",
// alg: "EdDSA",
// kid: "8DaTzHZcvQXUVvl8ezQKgGQHza1hiOZlPkdrB55Vt6Q",
// x: "6alAHg28tLuqWtaj0YOg7d5ySM4ERwPqQrLoy2pwdZk"
// }
Verify the signature using the public key:
const isValid = await kms.verify({
key: publicKey,
signature,
data,
});
console.log(isValid);
// Output: true
Export the private key:
const privateKey = await kms.exportKey({ keyUri });
console.log(privateKey);
// Output:
// {
// kty: "OKP",
// crv: "Ed25519",
// alg: "EdDSA",
// kid: "8DaTzHZcvQXUVvl8ezQKgGQHza1hiOZlPkdrB55Vt6Q",
// x: "6alAHg28tLuqWtaj0YOg7d5ySM4ERwPqQrLoy2pwdZk",
// d: "0xLuQyXFaWjrqp2o0orhwvwhtYhp2Z7KeRcioIs78CY"
// }
Import a private key generated by another library:
import type { Jwk } from "@web5/crypto";
const privateKey: Jwk = {
kty: "OKP",
crv: "Ed25519",
x: "0thnrEFw6MVSs1XFgd4OD-Yn05XGQv24hvskmGajtKQ",
d: "0xLuQyXFaWjrqp2o0orhwvwhtYhp2Z7KeRcioIs78CY",
};
const keyUri = await kms.importKey({ key: privateKey });
Compute the hash digest of arbitrary data:
const data = new TextEncoder().encode("Message");
const hash = await kms.digest({ algorithm: "SHA-256", data });
console.log(hash);
// Output:
// Uint8Array(32) [
// 8, 187, 94, 93, 110, 170, 193, 4,
// 158, 222, 8, 147, 211, 14, 208, 34,
// 177, 164, 217, 181, 180, 141, 180, 20,
// 135, 31, 81, 201, 203, 53, 40, 61
// ]
JSON Web Key (JWK)
A JSON Web Key (JWK) is a standardized format for representing cryptographic keys in a JSON data structure. The JWK format is used by all Web5 SDKs to promote portability and interoperability.
Defining a JWK:
import type { Jwk } from "@web5/crypto";
const publicKey: Jwk = {
kty: "OKP",
crv: "Ed25519",
x: "KsZRg2-gm9MoSrZqN9aSHUv-zYW8ajgyCfKxLGWJ2DI",
};
Computing the thumbprint of a JWK outputs a value
that can be used as a unique key identifier (kid
) or to securely compare JWKs to determine if they
are equivalent:
import { computeJwkThumbprint } from "@web5/crypto";
const thumbprint = await computeJwkThumbprint({ jwk: publicKey });
console.log(thumbprint);
// Output: VhWyK5rpk2u_51_KniJxjRhwTUOsL8BLuKJaqpNYuEA
The provided type guard functions can be used for type narrowing or to perform runtime verification of a specific JWK key type:
isPublicJwk(publicKey); // true
isPrivateJwk(publicKey); // false
isOkpPublicJwk(publicKey); // true
isOkpPrivateJwk(publicKey); // false
isOctPrivateJwk(publicKey); // false
isEcPublicJwk(publicKey); // false
isEcPrivateJwk(publicKey); // false
Random Number Generation
Cryptographically strong random number generation is vital in cryptography. The unpredictability of these random numbers is crucial for creating secure cryptographic keys, initialization vectors, and nonces (numbers used once). Their strength lies in their resistance to being guessed or replicated, making them foundational to maintaining the integrity and confidentiality of cryptographic systems.
The Web5 Crypto API includes two utility functions for random number generation:
randomBytes()
The randomBytes()
method generates cryptographically strong random values (CSPRNG) of the
specified length. The implementation relies on
crypto.getRandomValues
,
which defers to the runtime (browser, Node.js, etc.) for entropy. The pseudo-random number
generator algorithm may vary across runtimes, but is suitable for generating initialization vectors,
nonces and other random values.
[!IMPORTANT] Use a cipher algorithm's
generateKey()
method to generate encryption keys rather thanrandomBytes()
. This ensures that secrets used for encryption are guaranteed to be generated in a secure context and that the runtime uses the best source of entropy available.
import { CryptoUtils } from "@web5/crypto";
const nonce = CryptoUtils.randomBytes(24);
randomUuid()
The randomUuid()
method generates a UUID (Universally Unique Identifier) using a cryptographically
strong random number generator (CSPRNG) following the version 4 format, as specified in
RFC 4122.
UUIDs are of a fixed size (128 bits), can guarantee uniqueness across space and time, and can be
generated without a centralized authority to administer them. Since UUIDs are unique and persistent,
they make excellent Uniform Resource Names (URN).
The following is an example of the string representation of a UUID as a URN:
urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6
.
[!NOTE] This function is available only in secure contexts (HTTPS and localhost).
import { CryptoUtils } from "@web5/crypto";
const uuid = CryptoUtils.randomUuid();
Customization
Persistent Local KMS Key Store
By default, LocalKeyManager
uses an in-memory key store to simplify prototyping and testing.
To persist keys that are generated or imported, an implementation of the
KeyValueStore
interface can be passed.
For example, to use the LevelDB-backed store from @web5/common
:
import type { KeyIdentifier, Jwk } from "@web5/crypto";
import { Level } from "level";
import { LevelStore } from "@web5/common";
const db = new Level<KeyIdentifier, Jwk>("db_location", {
valueEncoding: "json",
});
const keyStore = new LevelStore<KeyIdentifier, Jwk>({ db });
const kms = new LocalKeyManager({ keyStore });
Cryptographic Primitives
This library encourages using its capabilities through concrete implementations of the CryptoApi
interface (e.g., LocalKeyManager
or
AwsKeyManager
). These implementations provides high-level,
user-friendly access to a range of cryptographic functions. However, for developers requiring
lower-level control or specific customizations, the library also exposes its cryptographic
primitives directly. These primitives include a variety of cipher, hash, signature, key derivation,
and key conversion algorithms for advanced use cases.
[!WARNING] While
@web5/crypto
offers low-level cryptographic primitives, it's crucial to acknowledge the complexity and potential risks associated with their use. Secure key management and security system design are highly specialized fields, often requiring expert knowledge. Even with correct usage of cryptographic functions, designing a secure system poses significant challenges.It's important to approach the creation of secure systems with caution, seeking expert review and continual learning to ensure effectiveness and security. Crypto 101 is an introductory course on cryptography to begin your learning journey, freely available for developers of all skill levels.
AES-CTR
import { AesCtr, CryptoUtils } from "@web5/crypto";
// Key Generation
const length = 256; // Length of the key in bits (e.g., 128, 192, 256)
const privateKey = await AesCtr.generateKey({ length });
// Encryption
const data = new TextEncoder().encode("Message");
const counter = CryptoUtils.randomBytes(16); // Initial value of the counter block
const encryptedData = await AesCtr.encrypt({
data,
counter,
key: privateKey,
length: 64, // Length of the counter in bits
});
// Decryption
const decryptedData = await AesCtr.decrypt({
data: encryptedData,
counter,
key: privateKey,
length: 64, // Length of the counter in bits
});
AES-GCM
import { AesGcm, CryptoUtils } from "@web5/crypto";
// Key Generation
const length = 256; // Length of the key in bits (e.g., 128, 192, 256)
const privateKey = await AesGcm.generateKey({ length });
// Encryption
const data = new TextEncoder().encode("Message");
const iv = CryptoUtils.randomBytes(12); // Initialization vector
const encryptedData = await AesGcm.encrypt({
data,
iv,
key: privateKey,
});
// Decryption
const decryptedData = await AesGcm.decrypt({
data: encryptedData,
iv,
key: privateKey,
});
ConcatKDF
import { ConcatKdf, CryptoUtils } from "@web5/crypto";
// Key Derivation
const derivedKeyingMaterial = await ConcatKdf.deriveKey({
sharedSecret: CryptoUtils.randomBytes(32),
keyDataLen: 128,
otherInfo: {
algorithmId: "A128GCM",
partyUInfo: "Alice",
partyVInfo: "Bob",
suppPubInfo: 128,
},
});
Ed25519
import { Ed25519 } from "@web5/crypto";
// Key Generation
const privateKey = await Ed25519.generateKey();
// Public Key Derivation
const publicKey = await Ed25519.computePublicKey({ key: privateKey });
// EdDSA Signing
const signature = await Ed25519.sign({
key: privateKey,
data: new TextEncoder().encode("Message"),
});
// EdDSA Signature Verification
const isValid = await Ed25519.verify({
key: publicKey,
signature: signature,
data: new TextEncoder().encode("Message"),
});
PBKDF2
import { Pbkdf2, CryptoUtils } from "@web5/crypto";
// Key Derivation
const derivedKey = await Pbkdf2.deriveKey({
hash: "SHA-256", // Hash function to use ('SHA-256', 'SHA-384', 'SHA-512')
password: new TextEncoder().encode("password"), // Password as a Uint8Array
salt: CryptoUtils.randomBytes(16), // Salt value
iterations: 1000, // Number of iterations
length: 256, // Length of the derived key in bits
});
secp256k1
import { Secp256k1 } from "@web5/crypto";
// Key Generation
const privateKey = await Secp256k1.generateKey();
// Public Key Derivation
const publicKey = await Secp256k1.computePublicKey({ key: privateKey });
// ECDH Shared Secret Computation
const sharedSecret = await Secp256k1.sharedSecret({
privateKeyA: privateKey,
publicKeyB: anotherPublicKey,
});
// ECDSA Signing
const signature = await Secp256k1.sign({
key: privateKey,
data: new TextEncoder().encode("Message"),
});
// ECDSA Signature Verification
const isValid = await Secp256k1.verify({
key: publicKey,
signature: signature,
data: new TextEncoder().encode("Message"),
});
SHA-256
import { Sha256 } from "@web5/crypto";
// Hashing
const data = new TextEncoder().encode("Message");
const hash = await Sha256.digest({ data });
X25519
import { X25519 } from "@web5/crypto";
// Key Generation
const privateKey = await X25519.generateKey();
// Public Key Derivation
const publicKey = await X25519.computePublicKey({ key: privateKey });
// ECDH Shared Secret Computation
const sharedSecret = await X25519.sharedSecret({
privateKeyA: privateKey,
publicKeyB: anotherPublicKey,
});
XChaCha20
import { XChaCha20, CryptoUtils } from "@web5/crypto";
// Key Generation
const privateKey = await XChaCha20.generateKey();
// Encryption
const data = new TextEncoder().encode("Message");
const nonce = CryptoUtils.randomBytes(24);
const encryptedData = await XChaCha20.encrypt({
data,
nonce,
key: privateKey,
});
// Decryption
const decryptedData = await XChaCha20.decrypt({
data: encryptedData,
nonce,
key: privateKey,
});
XChaCha20-Poly1305
import { XChaCha20Poly1305, CryptoUtils } from "@web5/crypto";
// Key Generation
const privateKey = await XChaCha20Poly1305.generateKey();
// Encryption
const data = new TextEncoder().encode("Message");
const nonce = CryptoUtils.randomBytes(24);
const additionalData = new TextEncoder().encode("Associated data");
const { ciphertext, tag } = await XChaCha20Poly1305.encrypt({
data,
nonce,
additionalData,
key: privateKey,
});
// Decryption
const decryptedData = await XChaCha20Poly1305.decrypt({
data: ciphertext,
nonce,
tag,
additionalData,
key: privateKey,
});
Project Resources
| Resource | Description | | --------------------------------------- | ----------------------------------------------------------------------------- | | CODEOWNERS | Outlines the project lead(s) | | CODE OF CONDUCT | Expected behavior for project contributors, promoting a welcoming environment | | CONTRIBUTING | Developer guide to build, test, run, access CI, chat, discuss, file issues | | GOVERNANCE | Project governance | | LICENSE | Apache License, Version 2.0 |