🦷

k6での暗号操作

2024/08/31に公開

はじめに

k6は、負荷テストやパフォーマンステストを行うためのツールであり、テストスクリプト内で暗号操作を行うことも可能です。この記事では、k6を使用したスクリプト内での暗号操作について、特にWebCrypto APIを用いた実装方法を解説します。

k6/cryptoモジュール(非推奨)

まず、従来のk6/cryptoモジュールについて簡単に触れておきます。これは、暗号操作を行うための古いAPIで、基本的な暗号化・復号、ハッシュ計算、署名操作が可能でした。しかし、現在ではWebCrypto APIが推奨されており、k6/cryptoは非推奨となっています。

詳細は、以下の公式ドキュメントをご参照ください:
k6/crypto | Grafana k6 documentation

WebCrypto APIの概要

WebCrypto APIは、暗号化や復号、デジタル署名の生成・検証、鍵の生成・管理などの暗号操作を行うための標準的なJavaScript APIです。k6では、このAPIを活用して、テストスクリプト内で暗号操作を実現できます。

詳細は、以下の公式ドキュメントをご参照ください:
webcrypto | Grafana k6 documentation

主要なAPIとその使い方

1. getRandomValuesメソッド

getRandomValuesは、暗号学的に安全なランダム値を生成し、指定された型付き配列に埋め込むために使用されます。このメソッドは、セッションIDや暗号鍵の生成など、安全性が求められる場面で役立ちます。

使い方の例:

import { crypto } from 'k6/experimental/webcrypto';

export default function () {
  const array = new Uint8Array(16);
  crypto.getRandomValues(array);
  console.log(array); // ランダムに生成された値が出力されます
}

ユースケース:

  • 暗号鍵やセッションIDの生成

2. randomUUIDメソッド

randomUUIDは、ランダムに生成されたUUID(バージョン4)を返します。このメソッドは、セッションやトランザクションのユニークな識別子を生成するのに非常に便利です。

使い方の例:

import { crypto } from 'k6/experimental/webcrypto';

export default function () {
  const uuid = crypto.randomUUID();
  console.log(uuid); // ランダムなUUIDが出力されます
}

ユースケース:

  • ユニークな識別子の生成

3. subtleオブジェクト

subtleオブジェクトは、ハッシュ、署名、暗号化、復号などの暗号プリミティブにアクセスするためのインターフェースです。このAPIを使えば、データの整合性確認や機密情報の保護が容易に行えます。

使い方の例:

import { crypto } from 'k6/experimental/webcrypto';

export default async function () {
  const data = new TextEncoder().encode('Hello, World!');

  // SHA-256でのハッシュ生成
  const hashBuffer = await crypto.subtle.digest('SHA-256', data);

  // ハッシュ値を16進数表記で出力
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');

  console.log(hashHex); // SHA-256ハッシュ値が出力されます
}

ユースケース:

  • データの整合性確認

実践例:AES-CBCによる暗号化と復号

ここでは、AES-CBCアルゴリズムを使用してテキストを暗号化および復号する方法を紹介します。この手法は、対称鍵を使ってデータを保護する一般的な方法です。

import { crypto } from 'k6/experimental/webcrypto';

export default async function () {
  const plaintext = stringToArrayBuffer('Hello, World!');

  // 対称鍵を生成
  const key = await crypto.subtle.generateKey(
    {
      name: 'AES-CBC',
      length: 256,
    },
    true,
    ['encrypt', 'decrypt']
  );

  // 暗号化
  const iv = crypto.getRandomValues(new Uint8Array(16));
  const ciphertext = await crypto.subtle.encrypt(
    {
      name: 'AES-CBC',
      iv: iv,
    },
    key,
    plaintext
  );

  // 復号して元のプレーンテキストと一致するか確認
  const deciphered = await crypto.subtle.decrypt(
    {
      name: 'AES-CBC',
      iv: iv,
    },
    key,
    ciphertext
  );

  console.log(
    'deciphered text == original plaintext: ',
    arrayBufferToHex(deciphered) === arrayBufferToHex(plaintext)
  );
}

function arrayBufferToHex(buffer) {
  return [...new Uint8Array(buffer)].map((x) => x.toString(16).padStart(2, '0')).join('');
}

function stringToArrayBuffer(str) {
  const buf = new ArrayBuffer(str.length * 2);
  const bufView = new Uint16Array(buf);
  for (let i = 0, strLen = str.length; i < strLen; i++) {
    bufView[i] = str.charCodeAt(i);
  }
  return buf;
}

SubtleCryptoオブジェクトの詳細

SubtleCryptoオブジェクトは、暗号操作を行うための中心的な役割を果たします。以下では、SubtleCryptoの主なメソッドとその使用例を紹介します。

1. encryptdecryptメソッド

これらのメソッドを使用すると、指定されたアルゴリズムと鍵を使ってデータを暗号化・復号できます。これにより、機密データの保護が可能になります。

使用例:

const ciphertext = await crypto.subtle.encrypt(
  { name: 'AES-CBC', iv: iv },
  key,
  plaintext
);

const plaintext = await crypto.subtle.decrypt(
  { name: 'AES-CBC', iv: iv },
  key,
  ciphertext
);

ユースケース:

  • データの機密性確保と復号

2. signverifyメソッド

signメソッドはデータにデジタル署名を生成し、verifyメソッドはその署名を検証します。これにより、データの真正性を確認することができます。

使用例:

const signature = await crypto.subtle.sign(
  { name: 'ECDSA', hash: { name: 'SHA-256' } },
  key,
  data
);

const isValid = await crypto.subtle.verify(
  { name: 'ECDSA', hash: { name: 'SHA-256' } },
  key,
  signature,
  data
);

ユースケース:

  • データの改ざん防止と検証

3. digestメソッド

digestメソッドは、データのハッシュ値を計算するために使用されます。ハッシュは、データが改ざんされていないことを確認するための不可逆的な一方向関数です。

使用例:

const hash = await crypto.subtle.digest('SHA-256', data);

ユースケース:

  • データの整合性確認

4. generateKeyメソッド

generateKeyメソッドは、新しい暗号鍵を生成し、その鍵をCryptoKeyオブジェクトとして返します。この鍵は、暗号化、復号、署名などに使用できます。

使用例:

const key = await crypto.subtle.generateKey(
  { name: 'AES-CBC', length: 256 },
  true,
  ['encrypt', 'decrypt']
);

ユースケース:

  • セキュアな鍵の生成

5. importKeyexportKeyメソッド

これらのメソッドは、外部から提供された鍵データをCryptoKeyオブジェクトにインポートしたり、CryptoKeyオブジェクトをエクスポートして保存したりするために使用されます。

使用例:

const importedKey = await crypto.subtle.importKey(
  'raw',
  keyData,
  { name: 'AES-CBC' },
  true,
  ['encrypt', 'decrypt']
);

const exportedKey = await crypto.subtle.exportKey('raw', key);

ユースケース:

  • 鍵の管理と再利用

6. deriveBitsメソッド

deriveBitsメソッドは、指定されたアルゴリズムと基礎鍵を使用してビットを導出します。これらのビットは、暗号化や復号のための共有秘密として使用できます。

使用例:

const derivedBits = await crypto.subtle.deriveBits(
  { name: 'ECDH', public: publicKey },
  privateKey,
  256
);

ユースケース:

  • 共有秘密の生成

CryptoKeyオブジェクト

CryptoKeyオブジェクトは、暗号化、復号、署名、または検証の操作に使用される暗号鍵を表します。このオブジェクトは、SubtleCrypto.generateKey()またはSubtleCrypto.importKey()メソッドを使用して作成されます。

プロパティ

  1. type:
    • 鍵の種類を示す文字列です。public(公開鍵)、private(秘密鍵)、secret(秘密鍵)などの値があります。
  2. extractable:
    • 鍵の生データがエクスポート可能かどうかを示すブール値です。trueの場合、鍵をエクスポートできます。
  3. algorithm:
    • 鍵を生成またはインポートするために使用されたアルゴリズムを含むオブジェクトです。
  4. usages:
    • 鍵が使用できる暗号操作を示す文字列の配列です。encryptdecryptsignverifyなどがあります。

CryptoKeyの生成方法

CryptoKeyは以下の方法で生成されます:

  1. generateKeyメソッドを使用する:

    • 新しい暗号鍵を生成します。例えば、AES鍵やRSA鍵の生成に使用されます。
    const key = await crypto.subtle.generateKey(
      { name: 'AES-CBC', length: 256 },
      true,
      ['encrypt', 'decrypt']
    );
    
  2. importKeyメソッドを使用する:

    • 外部から提供された鍵データをインポートしてCryptoKeyオブジェクトを作成します。
    const importedKey = await crypto.subtle.importKey(
      'raw',
      keyData,
      { name: 'AES-CBC' },
      true,
      ['encrypt', 'decrypt']
    );
    

CryptoKeyの使用例

CryptoKeyオブジェクトは、暗号操作(例えば、データの暗号化や署名の生成)に使用されます。以下は、CryptoKeyを使用した暗号化の例です。

const key = await crypto.subtle.generateKey(
  { name: 'AES-CBC', length: 256 },
  true,
  ['encrypt', 'decrypt']
);

const iv = crypto.getRandomValues(new Uint8Array(16));
const ciphertext = await crypto.subtle.encrypt(
  { name: 'AES-CBC', iv: iv },
  key,
  plaintext
);

const decryptedData = await crypto.subtle.decrypt(
  { name: 'AES-CBC', iv: iv },
  key,
  ciphertext
);

CryptoKeyに変換する方法

既存の鍵データをCryptoKeyオブジェクトに変換するには、importKeyメソッドを使用します。例えば、raw形式の鍵データをAES-CBCアルゴリズムでインポートする場合は次のようになります。

const rawKeyData = new Uint8Array([/*鍵のバイトデータ*/]);
const key = await crypto.subtle.importKey(
  'raw',
  rawKeyData.buffer,
  { name: 'AES-CBC' },
  true,
  ['encrypt', 'decrypt']
);

まとめ

今回のブログでは、WebCrypto APIを用いたk6スクリプトでの暗号操作について詳しく解説しました。WebCrypto APIは、暗号化、復号、デジタル署名、ハッシュ生成など、さまざまな暗号処理を安全かつ効率的に実行するための強力なツールです。このAPIを活用することで、k6を使ったパフォーマンステストやセキュリティテストにおいて、より高度な暗号処理が可能になります。

従来のk6/cryptoモジュールもありますが、WebCrypto APIは今後の暗号操作の標準となる機能を提供しており、より柔軟でセキュアな開発をサポートします。特に、テストスクリプトにおけるセキュリティの強化が求められる現代において、WebCrypto APIの活用は非常に有用です。

補足情報

なぜPEM形式からArrayBufferに変換する必要があるのか

PEM形式は、鍵データをBase64エンコードしてテキスト形式で表現したものです。WebCrypto APIは、暗号鍵データをArrayBuffer形式で扱うため、PEM形式の鍵を使用するためには、そのデータをArrayBufferに変換する必要があります。

ArrayBufferは、WebCrypto APIが直接操作できるバイナリデータ形式であり、暗号化操作に適しています。これにより、importKeyメソッドを使用して、鍵データをCryptoKeyオブジェクトとしてインポートできます。

pkcs8, ECDSA, P-256 の決定について

  1. PKCS#8:
    • PKCS#8は、秘密鍵の標準的な表現形式であり、WebCrypto APIはこの形式の鍵をインポートすることができます。
    • opensslコマンドで生成される秘密鍵はデフォルトでPKCS#8形式になっています。
  2. ECDSA:
    • ECDSA(Elliptic Curve Digital Signature Algorithm)は、楕円曲線暗号に基づくデジタル署名アルゴリズムです。これは、署名生成と検証に使用されます。
    • WebCrypto APIでは、ECDSAを使用してデジタル署名を行う場合、楕円曲線(P-256など)とハッシュアルゴリズム(例: SHA-256)を指定する必要があります。
  3. P-256:
    • P-256(またはprime256v1)は、楕円曲線の一種で、ECDSAで広く使用されます。WebCrypto APIでは、この楕円曲線を指定して鍵を生成したり、インポートしたりできます。
    • この楕円曲線は、パフォーマンスとセキュリティのバランスが良いため、WebCrypto APIでも標準的にサポートされています。

k6でBase64をデコード

k6では、Base64エンコードされた文字列をデコードするために、b64decodeメソッドを使用します。

b64decode( input, [encoding], [format] ) | Grafana k6 documentation

b64decodeメソッドの要約

b64decodeメソッドは、指定されたBase64エンコードされた文字列を、元のバイナリまたは文字列形式にデコードします。

パラメータ:
  • input (string): デコードするBase64エンコードされた文字列。
  • encoding (オプション, string): 使用するBase64エンコーディング方式。
    • "std": 標準的なエンコーディング方式。パディングに = が使用され、エンコードアルファベットには +/ が含まれます(デフォルト)。
    • "rawstd": "std" と同様ですが、パディングがありません。
    • "url": URLセーフなエンコーディング方式で、+/ の代わりに

-_ が使用されます。
- "rawurl": "url" と同様ですが、パディングがありません。

  • format (オプション, string): "s" を指定するとデコード結果が文字列として返され、それ以外の場合は ArrayBuffer として返されます。
戻り値:
  • ArrayBuffer または string: デコードされた元のデータが ArrayBuffer 形式または文字列形式で返されます。返される形式は format パラメータに依存します。

Discussion