k6での暗号操作
はじめに
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とその使い方
getRandomValues
メソッド
1. getRandomValues
は、暗号学的に安全なランダム値を生成し、指定された型付き配列に埋め込むために使用されます。このメソッドは、セッションIDや暗号鍵の生成など、安全性が求められる場面で役立ちます。
使い方の例:
import { crypto } from 'k6/experimental/webcrypto';
export default function () {
const array = new Uint8Array(16);
crypto.getRandomValues(array);
console.log(array); // ランダムに生成された値が出力されます
}
ユースケース:
- 暗号鍵やセッションIDの生成
randomUUID
メソッド
2. randomUUID
は、ランダムに生成されたUUID(バージョン4)を返します。このメソッドは、セッションやトランザクションのユニークな識別子を生成するのに非常に便利です。
使い方の例:
import { crypto } from 'k6/experimental/webcrypto';
export default function () {
const uuid = crypto.randomUUID();
console.log(uuid); // ランダムなUUIDが出力されます
}
ユースケース:
- ユニークな識別子の生成
subtle
オブジェクト
3. 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
の主なメソッドとその使用例を紹介します。
encrypt
とdecrypt
メソッド
1. これらのメソッドを使用すると、指定されたアルゴリズムと鍵を使ってデータを暗号化・復号できます。これにより、機密データの保護が可能になります。
使用例:
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
);
ユースケース:
- データの機密性確保と復号
sign
とverify
メソッド
2. 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
);
ユースケース:
- データの改ざん防止と検証
digest
メソッド
3. digest
メソッドは、データのハッシュ値を計算するために使用されます。ハッシュは、データが改ざんされていないことを確認するための不可逆的な一方向関数です。
使用例:
const hash = await crypto.subtle.digest('SHA-256', data);
ユースケース:
- データの整合性確認
generateKey
メソッド
4. generateKey
メソッドは、新しい暗号鍵を生成し、その鍵をCryptoKey
オブジェクトとして返します。この鍵は、暗号化、復号、署名などに使用できます。
使用例:
const key = await crypto.subtle.generateKey(
{ name: 'AES-CBC', length: 256 },
true,
['encrypt', 'decrypt']
);
ユースケース:
- セキュアな鍵の生成
importKey
とexportKey
メソッド
5. これらのメソッドは、外部から提供された鍵データをCryptoKey
オブジェクトにインポートしたり、CryptoKey
オブジェクトをエクスポートして保存したりするために使用されます。
使用例:
const importedKey = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'AES-CBC' },
true,
['encrypt', 'decrypt']
);
const exportedKey = await crypto.subtle.exportKey('raw', key);
ユースケース:
- 鍵の管理と再利用
deriveBits
メソッド
6. deriveBits
メソッドは、指定されたアルゴリズムと基礎鍵を使用してビットを導出します。これらのビットは、暗号化や復号のための共有秘密として使用できます。
使用例:
const derivedBits = await crypto.subtle.deriveBits(
{ name: 'ECDH', public: publicKey },
privateKey,
256
);
ユースケース:
- 共有秘密の生成
CryptoKeyオブジェクト
CryptoKey
オブジェクトは、暗号化、復号、署名、または検証の操作に使用される暗号鍵を表します。このオブジェクトは、SubtleCrypto.generateKey()
またはSubtleCrypto.importKey()
メソッドを使用して作成されます。
プロパティ
-
type
:- 鍵の種類を示す文字列です。
public
(公開鍵)、private
(秘密鍵)、secret
(秘密鍵)などの値があります。
- 鍵の種類を示す文字列です。
-
extractable
:- 鍵の生データがエクスポート可能かどうかを示すブール値です。
true
の場合、鍵をエクスポートできます。
- 鍵の生データがエクスポート可能かどうかを示すブール値です。
-
algorithm
:- 鍵を生成またはインポートするために使用されたアルゴリズムを含むオブジェクトです。
-
usages
:- 鍵が使用できる暗号操作を示す文字列の配列です。
encrypt
、decrypt
、sign
、verify
などがあります。
- 鍵が使用できる暗号操作を示す文字列の配列です。
CryptoKeyの生成方法
CryptoKey
は以下の方法で生成されます:
-
generateKey
メソッドを使用する:- 新しい暗号鍵を生成します。例えば、AES鍵やRSA鍵の生成に使用されます。
const key = await crypto.subtle.generateKey( { name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt'] );
-
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
の決定について
-
PKCS#8:
- PKCS#8は、秘密鍵の標準的な表現形式であり、WebCrypto APIはこの形式の鍵をインポートすることができます。
-
openssl
コマンドで生成される秘密鍵はデフォルトでPKCS#8形式になっています。
-
ECDSA:
- ECDSA(Elliptic Curve Digital Signature Algorithm)は、楕円曲線暗号に基づくデジタル署名アルゴリズムです。これは、署名生成と検証に使用されます。
- WebCrypto APIでは、ECDSAを使用してデジタル署名を行う場合、楕円曲線(
P-256
など)とハッシュアルゴリズム(例: SHA-256)を指定する必要があります。
-
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