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

sm-cipher

v1.0.3

Published

Dependency-free byte-oriented SM2, SM3 and SM4 TypeScript ESM library

Readme

sm-cipher

Dependency-free, byte-oriented SM2 / SM3 / SM4 implementation for TypeScript ESM. Every algorithm input and output is a Uint8Array; string and hexadecimal conversion is explicit at the application boundary.

The implementation provides native bigint SM2, typed-array SM3/HMAC-SM3, SM4 ECB/CBC/GCM, strict DER parsing, SM2 ASN.1 ciphertexts, public-key precomputation, and a standalone GHASH window core. It has no runtime dependencies.

Install

npm install sm-cipher

Quickstart

import {
  C1C3C2,
  generateKeyPair,
  sm2Encrypt,
  sm2Decrypt,
  sm2Sign,
  sm2Verify,
  sm3,
  sm4Encrypt,
  sm4Decrypt,
  bytesToHex,
  bytesToUtf8,
  hexToBytes,
  utf8ToBytes,
} from "sm-cipher"

const message = utf8ToBytes("message")
const pair = generateKeyPair()

// SM2 asymmetric encryption (C1C3C2 is the modern default)
const cipher = sm2Encrypt(message, pair.publicKey, C1C3C2)
const plaintext = sm2Decrypt(cipher, pair.privateKey, C1C3C2)

// SM2 signing
const signature = sm2Sign(message, pair.privateKey, { der: true })
const valid = sm2Verify(message, signature, pair.publicKey, { der: true })

// SM3 hashing
const digest = sm3(message)

// SM4 symmetric encryption (ECB by default)
const key = hexToBytes("0123456789abcdeffedcba9876543210")
const sm4Cipher = sm4Encrypt(message, key)
const sm4Plain = sm4Decrypt(sm4Cipher, key)

console.log(
  bytesToUtf8(plaintext),
  bytesToHex(digest),
  valid,
  bytesToUtf8(sm4Plain)
)

API

SM2

| Function | Signature | Notes | | ---------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | | generateKeyPair(seed?) | (seed?: Uint8Array) => { privateKey, publicKey } | Uses a secure random source unless a seed is supplied. | | getPublicKeyFromPrivateKey(privateKey) | (Uint8Array) => Uint8Array | Derives the uncompressed public key. | | compressPublicKey(publicKey) | (Uint8Array) => Uint8Array | Uncompressed to compressed point form. | | comparePublicKey(a, b) | (Uint8Array, Uint8Array) => boolean | Compressed/uncompressed-safe equality. | | verifyPublicKey(publicKey) | (Uint8Array) => boolean | Validates the point is on the curve. | | precomputePublicKey(publicKey, windowSize?) | (Uint8Array, number) => PrecomputedPublicKey | Speeds up repeated encryption/verification with one key. | | sm2Encrypt(message, publicKey, mode?, options?) | (Uint8Array, PublicKey, SM2Mode, Sm2CipherOptions) => Uint8Array | mode defaults to C1C3C2. { asn1: true } for ASN.1 ciphertext. | | sm2Decrypt(cipher, privateKey, mode?, options?) | (Uint8Array, Uint8Array, SM2Mode, Sm2CipherOptions) => Uint8Array | Mirrors sm2Encrypt. | | sm2Sign(message, privateKey, options?) | (Uint8Array, Uint8Array, options) => Uint8Array | { der: true }, { hash: true }, { userId }, { pointPool }. | | sm2Verify(message, signature, publicKey, options?) | (Uint8Array, Uint8Array, PublicKey, options) => boolean | Mirrors sm2Sign options. | | encodeDer(raw) / decodeDer(der) | (Uint8Array) => Uint8Array | Strict SM2 signature DER codec. | | getPoint() | () => SignaturePoint | Precomputes one (k, x1) signature nonce for a pool. | | C1C2C3, C1C3C2, SM2CipherMode | constants | C1C2C3 = 0, C1C3C2 = 1. |

SM3

| Function | Signature | Notes | | ----------------------- | -------------------------------------------- | ------------------------------------------------------ | | sm3(input, options?) | (Uint8Array, { key, mode? }) => Uint8Array | Plain digest, or HMAC-SM3 when options.key is given. | | sm3Hmac(key, message) | (Uint8Array, Uint8Array) => Uint8Array | Direct HMAC-SM3. | | kdf(z, length) | (Uint8Array, number) => Uint8Array | SM2 key derivation function. |

SM4

| Function | Signature | Notes | | ---------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------- | | sm4Encrypt(input, key, options?) | (Uint8Array, Uint8Array, SM4Options) => Uint8Array \| GCMResult | mode: 'ecb' (default), 'cbc', 'gcm'. Returns { output, tag } for GCM. | | sm4Decrypt(input, key, options?) | (Uint8Array, Uint8Array, SM4Options) => Uint8Array | Pass tag in options for GCM. | | sm4Ghash(h, ...segments) | (Uint8Array, ...Uint8Array[]) => Uint8Array | Standalone GHASH core; each segment is zero-padded to 16 bytes. |

SM4 padding is 'pkcs#7' / 'pkcs#5' / 'none' (default 'pkcs#7'), and CBC/GCM require an iv.

Byte / string helpers

| Function | Signature | | ------------------------------ | ---------------------------------------- | | hexToBytes(string) | (string) => Uint8Array | | bytesToHex(Uint8Array) | (Uint8Array) => string | | utf8ToBytes(string) | (string) => Uint8Array | | bytesToUtf8(Uint8Array) | (Uint8Array) => string | | concatBytes(...Uint8Array[]) | (...Uint8Array[]) => Uint8Array | | xorBytes(a, b) | (Uint8Array, Uint8Array) => Uint8Array | | equalBytes(a, b) | (Uint8Array, Uint8Array) => boolean | | copyBytes(input, name?) | (Uint8Array, string) => Uint8Array | | setRandomSource(source?) | (RandomSource?) => void | | randomBytes(length) | (number) => Uint8Array |

Mini Program Runtimes

Uint8Array is a typed view over ArrayBuffer and is supported by current mini-program runtimes. The implementation does not require TextEncoder, TextDecoder, Buffer, Node APIs, or browser DOM APIs.

Native BigInt is required and cannot be lowered by Vite. The output target is therefore ES2020. Test the minimum mini-program base library and device engines used by the application before release.

Browsers and Node use globalThis.crypto.getRandomValues by default. WeChat exposes secure randomness as the asynchronous wx.getRandomValues API instead, so preload a secure pool and install a synchronous source before generating SM2 keys, encryption nonces, or signatures:

import { setRandomSource } from "sm-cipher"

let pool = new Uint8Array(0)
let offset = 0

export function refillRandomPool(length = 65536): Promise<void> {
  return new Promise((resolve, reject) => {
    wx.getRandomValues({
      length,
      success(result) {
        pool = new Uint8Array(result.randomValues)
        offset = 0
        resolve()
      },
      fail: reject,
    })
  })
}

await refillRandomPool()
setRandomSource((length) => {
  if (offset + length > pool.length)
    throw new Error("Secure random pool exhausted")
  const output = pool.slice(offset, offset + length)
  pool.fill(0, offset, offset + length)
  offset += length
  return output
})

Do not replace the source with Math.random().

Performance

npm run bench builds the package and compares its public API with [email protected] and [email protected]. Each implementation is auto-calibrated to approximately 150 ms per sample, execution order rotates between samples, and the median of 5 samples is reported. SM2 and SM3 results are normalized to hexadecimal output, while SM4 uses byte keys and byte output where supported.

Representative operations per second on Node 25.2.1, Windows x64, Intel Core i5-1135G7 @ 2.40GHz are shown below. Higher is better; results depend on the runtime, hardware, and system load.

| Case | sm-cipher | sm-crypto | sm-crypto-v2 | | ------------------------------- | --------: | --------: | -----------: | | SM2 generateKeyPair | 2,357 | 101 | 2,169 | | SM2 encrypt | 552 | 49 | 159 | | SM2 encrypt (precomputed key) | 1,328 | N/A | 907 | | SM2 decrypt | 670 | 98 | 246 | | SM2 sign | 1,277 | 49 | 1,060 | | SM2 sign (supplied public key) | 2,148 | 97 | 1,807 | | SM2 verify | 575 | 52 | 227 | | SM3, hexadecimal output (4 KiB) | 34,890 | 14,432 | 30,749 | | SM4 ECB encrypt (4 KiB) | 22,571 | 9,745 | 14,885 | | SM4 ECB decrypt (4 KiB) | 21,185 | 9,561 | 14,116 | | SM4 CBC encrypt (4 KiB) | 19,350 | 8,395 | 12,391 | | SM4 CBC decrypt (4 KiB) | 20,712 | 9,354 | 12,818 | | SM4 GCM encrypt (4 KiB) | 9,208 | N/A | 5,572 | | SM4 GCM decrypt (4 KiB) | 9,637 | N/A | 5,718 |

Figures above use 15 samples (BENCH_SAMPLES=15) for tighter medians; SM2/SM3 use hexadecimal output and SM4 uses byte keys/output where supported. SM4 throughput improved after switching the block round function to combined S-box/L-transform lookup tables (T-tables), and SM2 improved after specializing Jacobian point doubling for the curve's a ≡ -3 (mod p) parameter — both reduce the number of field multiplications on the hot path without changing any public API or output.

The SM2 base-point table is initialized lazily. It uses 5-bit signed windows with 53 windows and 17 slots per window. Fresh-process measurements with forced garbage collection produced these median results:

| Library | Import | First keypair | Warm keypair | First-call heap growth | Retained heap estimate | | ------------ | ------: | ------------: | -----------: | ---------------------: | ---------------------: | | sm-cipher | 4.16ms | 7.96ms | 0.12ms | 1,037KiB | 215KiB | | sm-crypto-v2 | 35.39ms | 56.87ms | 0.41ms | 3,393KiB | 1,315KiB |

Heap deltas include lazy compilation and allocator effects, so they are process-level estimates rather than exact object sizes. [email protected] does not provide GCM. Small differences should be re-measured on the target device.

Run the benchmark yourself:

npm run bench
npm run bench:throughput
npm run bench:startup

BENCH_TIME_MS controls the target duration per throughput sample (default 150, minimum 50). BENCH_SAMPLES and BENCH_COLD_SAMPLES control throughput and fresh-process sample counts respectively; both default to 5.

Vite Library Build

npm install
npm run build
npm run test:bundle

Vite library mode emits dist/smcrypto.js as an ES2020 ESM library. TypeScript also emits dist/*.d.ts. The package exports field points at the entry files. Building requires a Node.js version supported by Vite 8 (Node 20.19+ or 22.12+); the built library targets ES2020.

Persistent Verification

The test and benchmark files are intentionally retained for future changes.

npm test
npm run typecheck
npm run test:bundle
npm run bench

Run the one-million-iteration SM4 standard vector with:

SMCRYPTO_LONG_TESTS=1 npm test

Tests are split into two files:

  • test/crossvalidation.test.mjs is the cross-library consistency suite. [email protected] is treated as the baseline oracle: every SM2/SM3/SM4 result produced by this library is checked bidirectionally against it, and [email protected] is triangulated against both this library and the baseline (SM4-GCM, which sm-crypto does not support, is checked bidirectionally against sm-crypto-v2 only). Run it alone with npm run test:cross. Set ORIGINAL_SM_CRYPTO to a local repository entry point to run the baseline checks against a source checkout of sm-crypto instead of the pinned dependency.
  • test/test.mjs covers this library's own behavior in isolation: input validation, edge cases, immutability, and known-answer/OpenSSL oracle checks for SM3/HMAC/SM4/GHASH-GCM. Run it alone with npm run test:unit.

npm test runs both files.

Security Boundary

The default random source is cryptographically secure, tags are compared without early exit, and GCM authenticates ciphertext before decryption. Native JavaScript bigint arithmetic is not guaranteed to be constant-time, so this library is not a side-channel-hardened cryptographic module.

Acknowledgements

  • sm-crypto — the de-facto reference implementation of the Chinese national cryptography standards in JavaScript, used as the baseline oracle for this library's cross-validation test suite.
  • sm-crypto-v2 — a faster, actively maintained fork of sm-crypto, triangulated against sm-crypto and this library in the same test suite, including SM4-GCM coverage.
  • @noble/curves and @noble/hashes — audited, dependency-free elliptic-curve and hash primitives that informed API and implementation choices for this library.

Thanks to their authors and maintainers for the reference implementations and prior art that made verifying this library's correctness possible.

License

MIT