チェックサムを使用してリソース識別子を生成する方法

著者は、『Write for Donations』プログラムの一環として寄付を受けるために、フリーでオープンソースの基金を選択しました。

イントロダクション

ユニークな識別子(UID)または識別子は文字列値または整数であり、API開発者はこれらを使用してAPI内の一意のリソースを指定することがよくあります。次に、APIの利用者はこれらの識別子を使用して、リソースのコレクションから単一のリソースを取得します。ユニークな識別子がない場合、リソースを分離し、必要なものとして呼び出すことはほぼ不可能です。識別子は、テーブルの名前、テーブル内のフィールド(列)または制約などのデータベースの構造要素を参照することができ、さらにデータベース内のユニークなアイテムに具体化することができます。例えば、ホテル予約ポータルに関連するデータベースでは、Hotel(id)はユニークなホテルを参照する識別子を指すかもしれません。Hotel(id=1234, name=”ハイアット”)の場合、IDが1234または名前が”ハイアット”で特定のホテルを識別することができます。

APIデザインパターンでは、ジョン・J・ジーウォックスは、優れた識別子に必要な7つの基本的な特徴を特定しています。これらの特徴は、一意のIDを生成する際に考慮することが重要です。

  • Easy to use: an identifier should avoid reserved characters like the forward slash (/) as these characters perform specific meaning in URLs.
  • Unique: an identifier should be able to refer to a single resource in an API.
  • Fast to generate: the ID generation process should perform in a predictable manner for consistency when scaling.
  • Unpredictable: when an identifier is unpredictable, it provides security benefits for vulnerability management.
  • Readable: an identifier should be human-readable, which is achieved by avoiding the digit 1, lowercase L, uppercase I, or the pipe character (|) as these characters may create confusion if someone needs to check the ID manually.
  • Verifiable: a checksum character can be used to verify the ID during an integrity check.
  • Permanent: once assigned, the identifiers should not change.

Note

注意:識別子を変更すると、予期しない混乱が生じることがあります。たとえば、Hotel(id=1234, name=”Hyatt”) という識別子があり、後で Hotel(id=5678, name=”Hyatt”) に変更された場合、以前のIDは再利用される可能性があります。以前の識別子が利用可能で、Hotel(id=1234, name=”Grand Villa”) という新しいホテルが作成された場合、この新しいホテルは元の識別子 (1234) を再利用します。そのため、ホテル1234を要求すると、予期しない結果が得られる可能性があります。

このチュートリアルでは、Node.JSを使用して、これらの特徴を満たすユニークなカスタムリソース識別子と関連するチェックサムを生成します。チェックサムとは、デジタルオブジェクトに対してハッシュ関数を使用して取得したファイルまたはデジタルデータのデジタル指紋のハッシュです。このチュートリアルのチェックサムは、資源に対応するバイトのサイズに対してエンコーディング(またはハッシュ化)のアルゴリズム処理によって派生された、単一の英数字文字です。

必要条件 (ひつようじょうけん)

このチュートリアルを始める前に、以下のものが必要です。

  • Node.js installed on your machine, which you can set up by following How To Install Node.js. This tutorial has been tested with Node.JS version 16.16.0.
  • Familiarity with Node.js. Learn more in the How To Code in Node.js series.
  • Familiarity with APIs. For a comprehensive tutorial on working with APIs, you can review How to Use Web APIs in Python3. Though written for Python, the article will help you understand the core concepts for working with APIs.
  • A text editor that supports JavaScript syntax highlighting, such as Atom, Visual Studio Code, or Sublime Text. This tutorial uses the command line editor nano.

ステップ1:エンコードされたIDを生成する

このステップでは、ランダムなバイトから一意の英数字文字列に識別子を生成する関数を作成します。識別子はベース32エンコーディングを使用してエンコードされますが、チュートリアルの後半でチェックサムは関連付けられません。エンコードプロセスにより、選択したバイト数に基づいて指定した長さの一意の識別子が作成され、良い識別子のいくつかの特徴を取り入れたIDが構築されます。

このプロジェクト用に新しいフォルダを作成して、そのフォルダに移動してください。

(For this project, begin by creating a new folder and then move into that folder.)

  1. mkdir checksum
  2. cd checksum

 

このチュートリアルでのプロジェクトフォルダの名前は”checksum”となります。

プロジェクトフォルダにお気に入りのエディタを使って、package.jsonファイルを作成し、開いてください。

  1. nano package.json

 

次に、次のコードを追加してください。 (Tsugini, tsugi no kōdo o tsuika shite kudasai.)

パッケージの設定ファイルであるpackage.json
{
  "name": "checksum",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module"
}

このファイルでは、プロジェクト名を checksum と定義し、コードのバージョンを “1.0.0” と考えています。また、メインの JavaScript ファイルを index.js と定義しています。package.json ファイルに “type”: “module” がある場合、ソースコードでは import 構文を使用する必要があります。このファイルでは、JavaScript で JSON データ形式を使用しています。JSON の取り扱いについては、「JavaScript で JSON を使用する方法」で詳しく学ぶことができます。

ファイルを保存して閉じる。

このチュートリアルでは、IDを生成するためにいくつかのNode.jsモジュールを使用します。そのモジュールはcryptoとbase32-encodeで、それに対応するデコーダであるbase32-decodeもあります。cryptoモジュールはNode.jsと一緒にパッケージ化されていますが、base32-encodeとbase32-decodeは後で使用するためにインストールする必要があります。エンコーディングは、文字(文字、数字、句読点、特定の記号)のシーケンスを効率的に伝送または保存するための特殊な形式に変換することです。デコーディングはその逆のプロセスであり、エンコードされた形式を元の文字のシーケンスに戻すことです。Base32エンコーディングは32文字のセットを使用し、数値を表現するためのテキストベースの32シンボル表記です。

ターミナルセッションで、次のコマンドを使用してプロジェクトフォルダーにこれらのモジュールパッケージをインストールしてください。

  1. npm i base32-encode base32-decode

 

これらのモジュールが追加されたことを示す出力を受け取ります。

Output

added 3 packages, and audited 5 packages in 2s found 0 vulnerabilities

インストール中に問題が発生した場合は、サポートのために「How To Use Node.js Modules with npm and package.json」を参照できます。

まだプロジェクトフォルダ内にいる状態で、index.jsという新しいファイルを作成してください。

  1. nano index.js

 

index.jsファイルに以下のJavaScriptコードを追加してください。

index.jsを日本語で言い換えると、次のようになります: 「インデックス.ジェイエス」。
import crypto from 'crypto';  
import base32Encode from 'base32-encode';
import base32Decode from 'base32-decode';
 
function generate_Id(byte_size) {
    const bytes = crypto.randomBytes(byte_size);
    return base32Encode(bytes, 'Crockford');
}

console.log('ID for byte size = 1:',generate_Id(1), '\n');
console.log('ID for byte size = 12:',generate_Id(12), '\n');
console.log('ID for byte size = 123:',generate_Id(123), '\n');

importコマンドは必要なモジュールを読み込みます。数値からバイト列を生成するために、バイト列のサイズを受け取り、cryptoモジュールのrandomBytes関数を使ってこのサイズのランダムなバイト列を作成するgenerate_Id関数を定義します。そして、generate_Id関数はこれらのバイト列をCrockfordのBase32エンコーディングを使ってエンコードします。

教育目的のために、いくつかのIDが生成され、その後、コンソールにログが記録されます。次の手順では、base32デコードモジュールを使用してリソースIDを解読します。

「index.js」ファイルを保存した後、次のコマンドでターミナルセッションでコードを実行してください。

node index.js

以下のような出力応答を受け取ります。

Output

ID for byte size = 1: Y8 ID for byte size = 12: JTGSEMQH2YZFD3H35HJ0 ID for byte size = 123: QW2E2KJKM8QZ7174DDB1Q3JMEKV7328EE8T79V1KG0TEAE67DEGG1XS4AR57FPCYTS24J0ZRR3E6TKM28AM8FYZ2AZTZ55C9VVQTABE0R7QRH7QBY7V3GBYBNN5D9JK0QMD9NXSWZN95S0772DHN43Q003G0QNTPA2J3AFA3P7Q167C1VNR92Z85PCDXCMEY0M7WA

生成されたバイトのランダム性により、IDの値が異なる場合があります。選択したバイトサイズに応じて、生成されたIDの長さが短くなることも、長くなることもあります。

index.jsで、JavaScriptのコメント機能を使って、コンソール出力をコメントアウトしてください(行の前に二重スラッシュ//を追加してください)。

インデックス.js
...
//console.log('ID for byte size = 1:',generate_Id(1), '\n'); 
//console.log('ID for byte size = 12:',generate_Id(12), '\n');
//console.log('ID for byte size = 123:',generate_Id(123), '\n');

以下の行は、関連するバイトに基づいてエンコードが異なる識別子を出力することを示しています。これらの行は、以下のセクションで使用されないため、このコードブロックではコメントアウトするか、完全に削除することができます。

このステップでは、ランダムなバイトをエンコードして、エンコードされたIDを作成しました。次のステップでは、エンコードされたバイトとチェックサムを組み合わせて、ユニークな識別子を作成します。

ステップ2 – リソース識別子の生成

以下では、チェックサム文字を持つIDを作成します。チェックサム文字の生成は2つの手順で行われます。指示のために、合成関数を作成する各機能は、以下のサブセクションで別々に構築されます。まず、モジュロ演算を実行する関数を作成します。次に、結果をチェックサム文字にマッピングする別の関数を作成します。これにより、リソースIDのチェックサムが生成されます。最後に、識別子とチェックサムを検証し、リソース識別子が正確であることを確認します。

モジュロ演算を実行する (Mojuro enzan wo jikkō suru)

このセクションでは、数値IDに対応するバイトを0から36まで(両端を含む)の数値に変換します。数値IDに対応するバイトは、BigInteger(BigInt)値に変換した割り算の余りとして整数に変換されます。

この手続きを実装するには、index.jsファイルの最後に以下のコードを追加してください。

index.jsが必要です。
...

function calculate_checksum(bytes) {
    const intValue = BigInt(`0x${bytes.toString('hex')}`);
    return Number(intValue % BigInt(37));
}

前もってファイルで定義されているバイトと一緒に機能calculate_checksumが作用します。この機能はバイトを16進数値に変換し、さらにBigInteger BigInt値に変換します。BigIntデータ型は、JavaScriptのプリミティブデータ型numberで表されるよりも大きな数値を表します。たとえば、整数の37は比較的小さいですが、モジュロ演算のためにBigIntに変換されます。

この変換を達成するために、まず intValue 変数を BigInt 変換メソッドで設定し、toString メソッドを使用してバイトを16進数に設定します。次に、intValue と BigInt の間の剰余を、サンプル値 37 を用いて % 記号で計算し、Number コンストラクタで数値を返します。この整数値(この例では37)は、自作の英数字文字列から英数字文字を選択するためのインデックスとして機能します。

intValueの値が123である場合、モジュール演算は123 % 37となります。この演算の結果、整数値が37の場合、余りは12で商は3となります。リソースIDの値が154の場合、演算154 % 37は余りが6となります。

この機能は、入力されたバイトをモジュロの結果にマッピングします。次に、モジュロの結果をチェックサム文字にマッピングする関数を作成します。

チェックサム文字の取得

前のセクションで剰余の結果を取得した後、それをチェックサム文字にマッピングすることができます。

以前のコードのすぐ下に、以下の行のコードをindex.jsファイルに追加してください。

インデックス.js
...

function get_checksum_character(checksumValue) {
    const alphabet = '0123456789ABCDEFG' +
        'HJKMNPQRSTVWXYZ*~$=U';  
    return alphabet[Math.abs(checksumValue)]; // 
}

関数get_checksum_characterにおいて、パラメータとしてchecksumValueを呼び出します。この関数内で、アルファベット文字からなるカスタムの文字列定数「alphabet」を定義します。checksumValueの設定値によって、この関数はアルファベット定数から定義された文字列とchecksumValueの絶対値を対応させた値を返します。

次に、これらのセクションで書かれた2つの関数を使用して、バイトのエンコーディングとチェックサム文字を組み合わせてIDを生成する関数を書きます。

index.jsファイルに次のコードを追加してください。

index.jsの内容を日本語で述べると、以下のようになります。
... 

function generate_Id_with_checksum(bytes_size) {
    const bytes = crypto.randomBytes(bytes_size);
    const checksum = calculate_checksum(bytes);
    const checksumChar = get_checksum_character(checksum);
    console.log("checksum character: ", checksumChar); 
    const encoded = base32Encode(bytes, 'Crockford');
    return encoded + checksumChar;
}

const Hotel_resource_id =generate_Id_with_checksum(132)
console.log("Hotel resource id: ",Hotel_resource_id)

このコードのセクションは、以前の2つの関数 calculate_checksum と get_checksum_character(チェックサム文字を生成するために使用される関数)を、エンコーディング関数と組み合わせて新しい関数 generate_Id_with_checksum に統合します。この関数はチェックサム文字を含むIDを生成します。

ファイルを保存して、別のターミナルセッションでコードを実行してください。

  1. node index.js

 

これに似た出力が受け取れます。

Output

checksum character: B Hotel resource id: 9V99B9P55K7M4DN5XYP4VTJYJGENZKJ0F9Q6EEEZ07X49G0V14AXJS3RYXBT3J1WJZXWGM76C6H7G895TJT27AW77BHBX2D16QNQ2ZNBY9MQHWG9NJ1WWVTNRCKRBX6HC3M7BB3JG0V413VJ767JN6FT0GFS5VQJ9X7KSP1KM29B02NAGXN3FP30WA8Y63N1XJAMGDPEE1RNHRTWH6P0B

同じチェックサム文字がIDの最後に表示されており、チェックサムが一致していることを示しています。

この図式のチャートは、この複合関数がどのように構造的に機能するかを示しています。 (Kono zu shiki no chāto wa, kono fukugō kansū ga dono yō ni kōzōteki ni kinō suru ka o shimeshiteimasu.)

A chart with Product ID at the top. It points to Crypto method, which points to Bytes. There are two branches from Bytes: base32-decode and Modulo process. The base32-decode branch points to the Encoded ID, whereas the Modulo process branch points to the Checksum. When the Encoded ID and Checksum are paired, they become the Resource ID.

このフローチャートは、リソース用にカウンターによって手動で作成された識別子である製品IDが、エンコーディングおよびモジュロ演算のプロセスを経て、一意のリソースIDに変換されることを示しています。図中のcryptoメソッドは、crypto.randomBytes()関数を指しています。

バイトサイズに基づいてチェックサム文字を含むIDを作成しました。次のセクションでは、IDの整合性をbase32デコードで検証するための識別子関数を実装します。

アイデンティファイアの正当性を確認する。

完全性を確保するために、新しい「verify_Id」という関数を使用して、識別子の最後の文字であるチェックサム文字と生成されたチェックサムを比較します。チェックサム文字を比較することは、元のIDの完全性を確認し、改ざんされていないことを確認するために重要なステップです。

index.jsファイルにこれらの行を追加してください。

index.jsを日本語に翻訳せよ。
...
function verify_Id(identifier) {
    const value = identifier.substring( 0, identifier.length-1);
    const checksum_char = identifier[identifier.length-1];     
    const buffer = Buffer.from( base32Decode(value, 'Crockford'));
    const calculated_checksum_char = get_checksum_character(calculate_checksum(buffer));
    console.log(calculated_checksum_char);
    const flag =calculated_checksum_char== checksum_char;
    return (flag);    
     }
console.log('\n');
console.log("computing checksum")
const flag = verify_Id(Hotel_resource_id);
if (flag) console.log("Checksums matched.");
else console.log("Checksums did not match.");

verify_Id関数は、チェックサムを確認することでIDの整合性をチェックします。識別子の残りの文字はバッファにデコードされ、その後、このバッファ上でcalculate_checksumとget_checksum_characterが順次実行され、チェックサム文字を取得します(calculated_checksum_char == checksum_charで比較されます)。

この模式図は、合成関数の仕組みを示しています。 (Kono moshidezu wa, gōsei kansū no shikumi o shimeshiteimasu.)

A chart with Resource ID at the top. It points to Slicing Method, which has two branches: Value and Checkum. The Value branch points to the base32-decode, which then becomes a Decoded checksum. The Checksum branch points to a Checksum. If the Decoded checksum and the Checksum match, it results in Verification.

このチャートでは、スライスとはID値(value)をチェックサム文字(checksum)から分離することを意味します。以前のコードブロックでは、関数identifier.substring(0, identifier.length-1)がID値を取得し、identifier[identifier.length-1]がリソースIDから最後の文字を取得します。

あなたのindex.jsファイルは、次のコードと一致するべきです。

index.jsを日本語で自然に言い換えてください、1つのオプションだけ必要です :
インデックス.js
import crypto from 'crypto';  // for generating bytes from the number
import base32Encode from 'base32-encode'; // for encoding the bytes into Unique ID as string type
import base32Decode from 'base32-decode';// for decoding the ID into bytes

function generate_Id(byte_size) {
    const bytes = crypto.randomBytes(byte_size);
    return base32Encode(bytes, 'Crockford');
}

//console.log('ID for byte size = 1:',generate_Id(1), '\n');
//console.log('ID for byte size = 12:',generate_Id(12), '\n');
//console.log('ID for byte size = 123:',generate_Id(123), '\n');

function calculate_checksum(bytes) {
    const intValue = BigInt(`0x${bytes.toString('hex')}`);
    return Number(intValue % BigInt(37));
}

function get_checksum_character(checksumValue) {
    const alphabet = '0123456789ABCDEFG' +
        'HJKMNPQRSTVWXYZ*~$=U'; // custom-built string  consisting of alphanumeric character
    return alphabet[Math.abs(checksumValue)]; // picking out an alphanumeric character
}

function generate_Id_with_checksum(bytes_size) {
    const bytes = crypto.randomBytes(bytes_size);
    const checksum = calculate_checksum(bytes);
    const checksumChar = get_checksum_character(checksum);
    console.log("checksum character: ", checksumChar); 
    const encoded = base32Encode(bytes, 'Crockford');
    return encoded + checksumChar;
}

const Hotel_resource_id =generate_Id_with_checksum(132)
console.log("Hotel resource id: ",Hotel_resource_id)

function verify_Id(identifier) {
    const value = identifier.substring( 0, identifier.length-1);
    const checksum_char = identifier[identifier.length-1]; 
    //console.log(value,checksum_char);
    const buffer = Buffer.from( base32Decode(value, 'Crockford'));
    const calculated_checksum_char = get_checksum_character(calculate_checksum(buffer));
    console.log(calculated_checksum_char);
    const flag =calculated_checksum_char== checksum_char;

    return (flag);
    
     }

console.log('\n');
console.log("computing checksum")
const flag = verify_Id(Hotel_resource_id);
if (flag) console.log("Checksums matched.");
else console.log("Checksums did not match.");

今、このコードを実行することができます。

node index.js

次の出力を受け取ります:

Output

… computing checksum AW75SY7FVC7TKT7VP5ZF0M8C67CN36YZK27BXHVFHSDXJFKH54HK2AXQFMPN89Q5YQRPGNHGAYQ5JFKVD40EKTXCET97Q0FEPX6MX1ZTNWGCA08SBRSHP8B0037ACJG6F6472FEVARCAWM6P5MRJ2F6WTRPXHYS9N1JEDZVH41D33RA5365VNFC5G5VYEFPFJJD8151B28XXDBRHAF80 H H Checksums matched.

以下は日本語での一つのオプションです:

現在、checksum 文字を使用して識別子の整合性を確認する verify_Id という関数があります。次に、教育目的でリソース ID を変更することで、チェックが失敗した場合の挙動を評価するための結果が一致しない結果が関数から返されるようにすることができます。

(オプション)ステップ3 — 一致しない結果のための識別子の変更

現在、チェックサムが一致するかどうかを確認するために、識別子の値を変更します。このステップでの変更は、ID内の任意の文字が操作されると、常に一致しないチェックサムになります。このような変更は、伝送エラーや悪意のある行動から生じるかもしれません。ただし、この変更は製品のビルドには推奨されません。非一致するチェックサムの結果を評価するための教育目的でのみ使用してください。

index.js ファイルで、ホテルのリソース ID を変更するために、以下の行を追加してください。

インデックス.js
...
const altered_Hotel_resource_id= Hotel_resource_id.replace('P','H');   
console.log("computing checksum")
const flag = verify_Id(altered_Hotel_resource_id);
if (flag) console.log("Checksum matched.");
else console.log("Checksums did not match.");

上記のコードでは、ID内のいかなるPもHに置き換え、変数名をHotel_resource_IDからaltered_Hotel_resource_idに変更します。再度言いますが、これらの変更は情報の目的であり、一致の整合性を確保するためにこのステップの終わりに元に戻すことができます。

ファイルを保存し、その後リソースIDの変更を行ったコードを再実行してください。

  1. node index.js

 

「チェックサムが一致しないという出力を受け取ります。」

Output

Checksums did not match.

このステップでは、チェックサムが整合性テストに合格するかどうかを確認するための関数を作成し、両方のケースに遭遇しました。一致しないチェックサムは、リソースIDが操作されていることを示しています。通知は、アプリケーションの要件に応じて、開発者がユーザーリクエストをブロックしたり、リソースIDに関連するリクエストを報告したりするなど、悪意のある行動に対して対処するためのものです。

この機能を元のチェックサムの結果に戻すためには、このステップの最初に追加された追加コードを削除し、ステップ2の最後にあるファイルとコードが一致するようにします。

ユニークなデータモデルを生成し、APIのバージョン管理などをサポートするために、カスタムのチェックサムが必要な場合は、このチュートリアルを使用してください。

結論

このチュートリアルでは、良い識別子の特性に合致するリソースIDを開発しました。また、Node.js環境でベース32エンコーディングを使用して一意のリソースIDにチェックサムを付けました。最後に、ベース32デコーディングでIDの整合性を検証しました。

最終ファイルとSilicon Cloudコミュニティリポジトリ内のファイルを対比することで、交互確認ができます。また、gitバージョン管理システムに慣れている場合は、リポジトリをgitクローンすることも可能です。GitHubおよびオープンソースプロジェクトの入門シリーズに従うことでもリポジトリをクローンできます。

今、チェックサムの基本を理解したから、MD5などの他のエンコーディングアルゴリズムで実験することができます。

コメントを残す 0

Your email address will not be published. Required fields are marked *