Error [TypeError: crypto.randomUUID is not a function]

TypeError: crypto.randomUUID is not a function:古い Node で randomUUID が無いときの直し方

FIX SUMMARY verified
Applies when
node:14.16.1-alpinecall to crypto.randomUUID on Node < 14.17

Verified: reproduced in node:14.16.1-alpine, then the TypeError: crypto.randomUUID is not a function signature was gone after the fix (exit 0).

UUID を生成しようと crypto.randomUUID() を呼んだら、古い Node で次のエラーが出て落ちることがあります。

TypeError: crypto.randomUUID is not a function
    at Object.<anonymous> (/app/index.js:3:20)

コピペ検索用の表記ゆれです。いずれも同じ原因です。

  • TypeError: crypto.randomUUID is not a function
  • crypto.randomUUID is not a function
  • randomUUID is not a function

crypto 自体は読み込めているのにメソッドだけが無い出方なので、requireimport の書き方を疑いたくなりますが、原因はモジュールの読み込みではありません。crypto.randomUUID が追加されたのが Node 14.17.0 からで、それより前の版には無いのが理由です。crypto モジュールはどの版にもありますが、randomUUID メソッドだけが古い版に存在しないため、is not defined ではなく is not a function という形で出ます。

これは典型的な版境界です。しかも境界が 14.16 と 14.17 という同じメジャー版の中にあるので、「Node 14 で動くはず」と思っていても、固定しているマイナー版によって結果が変わります。開発マシンや新しい CI では通るのに、FROM node:14.16 のような古い固定のコンテナでだけ落ちる、という形で現れます。

いつから crypto.randomUUID が使えるのか

crypto.randomUUID(RFC 4122 version 4 の UUID を返す)は Node 14.17.0 で追加され、15 系には 15.6.0 で入りました。16 以降はどの版にもあります。

Node の版crypto.randomUUID挙動
14.16.x 以前無しTypeError: crypto.randomUUID is not a function
14.17.0 以降の 14 系有りそのまま使える
15.0.0 〜 15.5.x無しTypeError: crypto.randomUUID is not a function
15.6.0 以降の 15 系 / 16 以降有りそのまま使える

追加が LTS(14)へ先にバックポートされた関係で、14 系は 14.17.0 から使えるのに、当時の最新だった 15 系は 15.6.0 まで入りませんでした。そのため 15.0〜15.5 は 14.17 より番号が大きいのに crypto.randomUUID が無い、という逆転が起きます。番号の大小ではなく「14.17 以降の 14 系 / 15.6 以降 / 16 以降」で判断してください。この「LTS へ先に入って番号が逆転する」形は crypto.hash is not a function とまったく同じ構図です。node:14.16.1 のように固定していたり、古いイメージがキャッシュに残っていたりすると、この境界より前で止まります。

再現(最小構成)

次の1ファイルを置きます。

// index.js
const crypto = require('crypto');

console.log(crypto.randomUUID());

これを Node 14.16 以前で node index.js すると TypeError: crypto.randomUUID is not a function が出て、終了コードは 1 になります。Node 14.17 以降なら UUID 文字列(3b12f1df-... のような形)が表示されます。同じコード・同じマシンでも、Node のマイナー版だけで結果が変わります。

解決

1. Node を 14.17 以降(実務では 18 以降の LTS)に上げる

crypto.randomUUID を使い続けるなら、実行する Node(CI・本番の Docker ベースイメージを含む)を上げます。API が入ったのは 14.17.0 ですが、14・15・16 はすでに EOL(サポート終了)なので、本番ではその時点でサポート中の LTS へ揃えます。Dockerfile の固定タグや CI のバージョン設定を見直してください。

2. uuid パッケージを使う(古い Node を残す場合)

古い Node をサポート対象に残すなら、UUID 生成で広く使われている uuid を使います。crypto.randomUUID と同じ version 4 の UUID を返します。

// index.js(uuid パッケージを使う)
const { v4: uuidv4 } = require('uuid');

console.log(uuidv4());

3. crypto.randomBytes から自前で作る(依存を増やしたくない場合)

パッケージを足したくないなら、どの版にもある crypto.randomBytes から version 4 の UUID を組み立てられます。ビット操作は RFC 4122 の version(3 番目のグループの先頭を 4 にする)と variant(4 番目のグループの先頭を 8b にする)を立てるためのものです(ハイフンを除いた 16 進では 13 文字目と 17 文字目にあたります)。

const crypto = require('crypto');

function randomUuid() {
  if (typeof crypto.randomUUID === 'function') {
    return crypto.randomUUID();
  }

  const bytes = crypto.randomBytes(16);
  bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
  bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant

  const hex = bytes.toString('hex');
  return [
    hex.slice(0, 8),
    hex.slice(8, 12),
    hex.slice(12, 16),
    hex.slice(16, 20),
    hex.slice(20),
  ].join('-');
}

console.log(randomUuid());

crypto.randomBytes は暗号論的に安全な乱数を返すので、生成される UUID の質は crypto.randomUUID と同等です。Math.random() で UUID を作る例が出回っていますが、乱数の質が保証されないので使わないでください。

切り分け(似ているが原因が違うもの)

  • is not a functionis not defined の違いcrypto.randomUUIDcrypto オブジェクトのメソッドなので、無いときは is not a function になります。一方 fetchstructuredClone のようなグローバルは、無いときに ReferenceError: ... is not defined です。メッセージの語尾は違いますが、どちらも「その版にその API がまだ無い」という同じ版境界です。fetchReferenceError: fetch is not defined の直し方structuredCloneReferenceError: structuredClone is not defined の直し方 を参照してください。
  • cryptoundefined で落ちたCannot read properties of undefined (reading 'randomUUID') のように crypto 自体が無いなら、これは版境界ではなく require('crypto')(ESM なら import)を書き忘れているか、ブラウザ環境で Node の crypto を期待している可能性が高いです。
  • ブラウザ(Web Crypto)で crypto.randomUUID が無い:ブラウザ側の crypto.randomUUID はセキュアコンテキストでのみ使えます。セキュアコンテキストには https のほか http://localhost / 127.0.0.1 / file: も含まれるので、ローカル開発では動いても、http で配信した本番では undefined になり得ます。この記事は Node 実行時の話ですが、ブラウザでも配信を https にする点を併せて確認してください。

検証(machine-verified)

この修正は node:14.16.1-alpine のバージョン固定コンテナ内で再現し、修正後に TypeError: crypto.randomUUID is not a function のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs node/crypto-randomuuid-not-a-function
● reproduce TypeError: crypto.randomUUID is not a function present ✓
● apply fix exit 0
● re-run TypeError: crypto.randomUUID is not a function gone ✓
PASS verified · node:14.16.1-alpine · signature gone