Skip to content
LogoLogo

@portable/web-crypto

Functions

WebCrypto.derToRawEcdsa

Converts a DER-encoded ECDSA signature, as WebAuthn and OpenSSL produce, into the raw r || s form that crypto.subtle.verify and JWS (ES256, ES384, ES512) use. Only strict DER is accepted: BER length forms, extra leading zeros, negative integers and trailing bytes throw.

function derToRawEcdsa(
  signature: Uint8Array,
  options: derToRawEcdsa.Options = {},
): Uint8Array<ArrayBuffer>;
OptionTypeDefaultDescription
namedCurveNamedCurve'P-256'The curve of the key that made the signature. It sets the width of r and s.
import * as WebCrypto from '@portable/web-crypto';
 
const raw = WebCrypto.derToRawEcdsa(
  new Uint8Array(assertion.signature),
);
await crypto.subtle.verify(
  { name: 'ECDSA', hash: 'SHA-256' },
  publicKey,
  raw,
  signedData,
);

WebCrypto.rawToDerEcdsa

Converts a raw r || s ECDSA signature, as crypto.subtle.sign and JWS produce, into DER. The curve follows from the length: 64 bytes for P-256, 96 for P-384 and 132 for P-521.

function rawToDerEcdsa(signature: Uint8Array): Uint8Array<ArrayBuffer>;
import * as WebCrypto from '@portable/web-crypto';
 
const raw = await crypto.subtle.sign(
  { name: 'ECDSA', hash: 'SHA-256' },
  privateKey,
  data,
);
const der = WebCrypto.rawToDerEcdsa(new Uint8Array(raw));

WebCrypto.digest

Hashes bytes, or the UTF-8 bytes of a string, with crypto.subtle.digest.

function digest(
  hash: Hash,
  data: string | Uint8Array,
  options: digest.Options = {},
): Promise<Uint8Array<ArrayBuffer>>;
OptionTypeDefaultDescription
signalAbortSignalRejects with signal.reason once aborted.
import * as WebCrypto from '@portable/web-crypto';
 
const hash = await WebCrypto.digest('SHA-256', 'hello');

WebCrypto.hkdf

Derives length bytes from key with HKDF (RFC 5869), extract and expand in one call.

function hkdf(
  hash: Hash,
  key: string | Uint8Array,
  options: hkdf.Options = {},
): Promise<Uint8Array<ArrayBuffer>>;
OptionTypeDefaultDescription
infostring | Uint8Array<ArrayBufferLike>emptyContext for the derived key.
lengthnumberthe hash lengthBytes to derive, at most 255 times the hash length.
saltstring | Uint8Array<ArrayBufferLike>empty, which RFC 5869 treats as a string of zero bytes as long as the hash
signalAbortSignalRejects with signal.reason once aborted.
import * as WebCrypto from '@portable/web-crypto';
 
const key = await WebCrypto.hkdf('SHA-256', sharedSecret, {
  info: 'my app v1',
  length: 32,
});

WebCrypto.hmac

Computes an HMAC (RFC 2104) of data with key. Strings are read as UTF-8. The imported CryptoKey is cached for up to 16 keys of at most 256 bytes, so signing again with the same secret skips importKey.

function hmac(
  hash: Hash,
  key: string | Uint8Array,
  data: string | Uint8Array,
  options: hmac.Options = {},
): Promise<Uint8Array<ArrayBuffer>>;
OptionTypeDefaultDescription
signalAbortSignalRejects with signal.reason once aborted.
import * as WebCrypto from '@portable/web-crypto';
 
const signature = await WebCrypto.hmac(
  'SHA-256',
  'whsec_secret',
  '{"id":1}',
);

WebCrypto.importSeed

Imports a 32-byte Ed25519 or X25519 private key, the form RFC 8032 and RFC 7748 use, and returns it with its public key. WebCrypto imports these keys only as PKCS #8 or JWK.

function importSeed(
  algorithm: SeedAlgorithm,
  seed: Uint8Array,
  options: importSeed.Options = {},
): Promise<CryptoKeyPair>;
OptionTypeDefaultDescription
extractablebooleanfalseWhether the private key can be exported. The public key always can.
signalAbortSignalRejects with signal.reason once aborted.
import * as WebCrypto from '@portable/web-crypto';
 
const seed = crypto.getRandomValues(new Uint8Array(32));
const { privateKey, publicKey } = await WebCrypto.importSeed(
  'Ed25519',
  seed,
);
const signature = await crypto.subtle.sign('Ed25519', privateKey, data);

WebCrypto.publicKeyOf

Returns the public key of an extractable private key: Ed25519, X25519, ECDSA, ECDH or RSA. The public key can verify what the private key signs and encrypt what it decrypts.

function publicKeyOf(
  privateKey: CryptoKey,
  options: publicKeyOf.Options = {},
): Promise<CryptoKey>;
OptionTypeDefaultDescription
signalAbortSignalRejects with signal.reason once aborted.
import * as WebCrypto from '@portable/web-crypto';
 
const privateKey = await crypto.subtle.importKey(
  'pkcs8',
  der,
  'Ed25519',
  true,
  ['sign'],
);
const publicKey = await WebCrypto.publicKeyOf(privateKey);

WebCrypto.importPem

Imports a PEM private or public key. WebCrypto imports only DER, and only as PKCS #8 or SubjectPublicKeyInfo, so importPem decodes the base64 and wraps a PKCS #1 RSA PRIVATE KEY or RSA PUBLIC KEY, the form openssl genrsa writes, in the structure WebCrypto takes. It reads the first PRIVATE KEY, RSA PRIVATE KEY, PUBLIC KEY or RSA PUBLIC KEY block and skips other blocks, such as a certificate before the key.

function importPem(
  algorithm: importPem.Algorithm,
  pem: string | Uint8Array,
  options: importPem.Options = {},
): Promise<CryptoKey>;
OptionTypeDefaultDescription
extractablebooleanfalseWhether the key can be exported.
usagesreadonly KeyUsage[]What the key may be used for. The default depends on the algorithm and on whether the key is private or public, such as ['sign'] and ['verify'] for a signing algorithm.
signalAbortSignalRejects with signal.reason once aborted.
import * as WebCrypto from '@portable/web-crypto';
 
const privateKey = await WebCrypto.importPem(
  { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
  pem,
);
const signature = await crypto.subtle.sign(
  'RSASSA-PKCS1-v1_5',
  privateKey,
  data,
);

Errors

WebCrypto.InvalidInputError

Thrown for a malformed DER signature, a raw signature or seed of the wrong length, a key that is not an extractable private key, or a PEM key that doesn't decode or doesn't import for the algorithm.

class InvalidInputError extends Errors.InvalidInputError {
  readonly kind: string;
}
import * as WebCrypto from '@portable/web-crypto';
 
try {
  WebCrypto.derToRawEcdsa(new Uint8Array([0x30, 0x00]));
} catch (error) {
  if (error instanceof WebCrypto.InvalidInputError) {
    console.log(error.message);
  }
}

WebCrypto.UnsupportedError

Thrown by importPem for a valid PEM block it doesn't import, such as an EC PRIVATE KEY, a certificate or an encrypted key.

class UnsupportedError extends Errors.UnsupportedError {
  readonly kind: string;
}
import * as WebCrypto from '@portable/web-crypto';
 
try {
  await WebCrypto.importPem(
    { name: 'ECDSA', namedCurve: 'P-256' },
    pem,
  );
} catch (error) {
  if (error instanceof WebCrypto.UnsupportedError) {
    console.log(
      'Convert the key to PKCS #8 with `openssl pkcs8 -topk8 -nocrypt`.',
    );
  }
}

Types

WebCrypto.NamedCurve

An ECDSA curve, named as WebCrypto names it.

type NamedCurve =  |  | ;

WebCrypto.Digest

A function that hashes data with the algorithm hash and returns the digest or a promise of it. Packages that hash take one as their digest option, and WebCrypto.digest is one.

type Digest = (
  hash: Hash,
  data: Uint8Array<ArrayBuffer>,
) => Uint8Array | Promise<Uint8Array>;

WebCrypto.Hash

A hash algorithm, by its WebCrypto name. The @portable/* packages call a Digest or Hmac function only with these names. If WebCrypto adds a hash later, such as SHA-3, it will get its own type, so a switch over Hash in your code still compiles.

type Hash =  |  |  | ;

WebCrypto.Hasher

An incremental hash with the same methods as sha256.create() from @noble/hashes. crypto.subtle.digest needs the whole input at once, so a package that hashes a stream takes a hasher option, a function that returns one of these.

type Hasher = {
  update(data: Uint8Array): unknown;
  digest(): Uint8Array | Promise<Uint8Array>;
};

WebCrypto.Hmac

Computes an HMAC of data with key and hash, sync or async. Packages that sign with HMAC take one as their hmac option. WebCrypto.hmac fits this type, and so does a wrapper around @noble/hashes, which is sync and faster on Node.

type Hmac = (
  hash: Hash,
  key: Uint8Array<ArrayBuffer>,
  data: Uint8Array<ArrayBuffer>,
) => Uint8Array | Promise<Uint8Array>;

WebCrypto.SeedAlgorithm

A curve whose private key is a 32-byte seed.

type SeedAlgorithm =  | ;