ハッシュ化

ハッシュ化

hashサービスを使用して、アプリケーション内でユーザーパスワードをハッシュ化できます。AdonisJSは、bcryptscryptargon2のハッシュアルゴリズムをサポートしており、カスタムドライバーを追加することもできます

ハッシュ化された値は、PHC文字列形式で保存されます。PHCは、ハッシュをフォーマットするための決定論的エンコーディング仕様です。

使用方法

hash.makeメソッドは、プレーンな文字列値(ユーザーパスワードの入力)を受け取り、ハッシュ出力を返します。

import hash from '@adonisjs/core/services/hash'
const hash = await hash.make('user_password')
// $scrypt$n=16384,r=8,p=1$iILKD1gVSx6bqualYqyLBQ$DNzIISdmTQS6sFdQ1tJ3UCZ7Uun4uGHNjj0x8FHOqB0pf2LYsu9Xaj5MFhHg21qBz8l5q/oxpeV+ZkgTAj+OzQ

ハッシュ値を平文に変換することはできません。ハッシュ化は一方向のプロセスであり、ハッシュが生成された後に元の値を取得する方法はありません。

ただし、ハッシュを使用して、与えられた平文値が既存のハッシュと一致するかどうかを検証することはできます。hash.verifyメソッドを使用してこのチェックを実行できます。

import hash from '@adonisjs/core/services/hash'
if (await hash.verify(existingHash, plainTextValue)) {
// パスワードが正しいです
}

設定

ハッシュ化の設定は、config/hash.tsファイルに保存されます。デフォルトのドライバーはscryptに設定されています。なぜなら、scryptはNode.jsのネイティブなcryptoモジュールを使用しており、サードパーティのパッケージは必要ありません。

config/hash.ts
import { defineConfig, drivers } from '@adonisjs/core/hash'
export default defineConfig({
default: 'scrypt',
list: {
scrypt: drivers.scrypt(),
/**
* argon2を使用する場合はコメントを外してください
argon: drivers.argon2(),
*/
/**
* bcryptを使用する場合はコメントを外してください
bcrypt: drivers.bcrypt(),
*/
}
})

Argon

Argonは、ユーザーパスワードをハッシュ化するための推奨されるハッシュアルゴリズムです。AdonisJSハッシュサービスでargonを使用するには、argon2 npmパッケージをインストールする必要があります。

npm i argon2

以下は利用可能なオプションのリストです。

export default defineConfig({
// デフォルトドライバーをargonに更新してください
default: 'argon',
list: {
argon: drivers.argon2({
version: 0x13, // 19の16進数コード
variant: 'id',
iterations: 3,
memory: 65536,
parallelism: 4,
saltSize: 16,
hashLength: 32,
})
}
})

variant

使用するargonハッシュのバリアントです。

  • dはより高速で、GPU攻撃に対して非常に耐性があります。これは仮想通貨に役立ちます。
  • iはより遅く、トレードオフ攻撃に対して耐性があります。これはパスワードのハッシュ化やキーの派生に適しています。
  • id (デフォルト) は上記の両方のハイブリッド組み合わせで、GPU攻撃とトレードオフ攻撃に対して耐性があります。

version

使用するargonのバージョンです。利用可能なオプションは0x10 (1.0)0x13 (1.3)です。デフォルトでは最新バージョンを使用する必要があります。

iterations

ハッシュの強度を上げるためのiterationsカウントです。ハッシュの計算には時間がかかります。

デフォルト値は3です。

memory

値のハッシュ化に使用するメモリの量です。各並列スレッドはこのサイズのメモリプールを持ちます。

デフォルト値は65536 (KiB)です。

parallelism

ハッシュの計算に使用するスレッド数です。

デフォルト値は4です。

saltSize

ソルトの長さ(バイト単位)です。argonは、ハッシュの計算時にこのサイズの暗号的に安全なランダムなソルトを生成します。

パスワードのハッシュ化にはデフォルトで16が推奨されます。

hashLength

生のハッシュの最大長(バイト単位)です。出力値は、PHC形式にさらにエンコードされるため、上記のハッシュ長よりも長くなります。

デフォルト値は32です。

Bcrypt

AdonisJSハッシュサービスでBcryptを使用するには、bcrypt npmパッケージをインストールする必要があります。

npm i bcrypt

以下は利用可能な設定オプションのリストです。

export default defineConfig({
// デフォルトドライバーをbcryptに更新してください
default: 'bcrypt',
list: {
bcrypt: drivers.bcrypt({
rounds: 10,
saltSize: 16,
version: '2b'
})
}
})

rounds

ハッシュの計算コストです。rounds値がハッシュの計算にかかる時間にどのような影響を与えるかについては、BcryptのドキュメントのA Note on Roundsセクションを参照してください。

デフォルト値は10です。

saltSize

ソルトの長さ(バイト単位)です。ハッシュの計算時に、このサイズの暗号的に安全なランダムなソルトを生成します。

デフォルト値は16です。

version

ハッシュアルゴリズムのバージョンです。サポートされている値は2a2bです。最新バージョンである2bを使用することを推奨します。

Scrypt

scryptドライバーは、パスワードハッシュの計算にNode.jsのcryptoモジュールを使用します。設定オプションは、Node.jsのscryptメソッドで受け入れられるものと同じです。

export default defineConfig({
// デフォルトドライバーをscryptに更新してください
default: 'scrypt',
list: {
scrypt: drivers.scrypt({
cost: 16384,
blockSize: 8,
parallelization: 1,
saltSize: 16,
maxMemory: 33554432,
keyLength: 64
})
}
})

モデルフックを使用してパスワードをハッシュ化する

ユーザーパスワードをハッシュ化するためにhashサービスを使用するのであれば、ロジックをbeforeSaveモデルフック内に配置役立つ場合があります。

@adonisjs/authモジュールを使用している場合、モデル内でパスワードのハッシュ化を行う必要はありません。AuthFinderが自動的にパスワードのハッシュ化を処理し、ユーザーの資格情報を安全に処理します。このプロセスについては、こちらで詳しく説明しています。

import { BaseModel, beforeSave } from '@adonisjs/lucid'
import hash from '@adonisjs/core/services/hash'
export default class User extends BaseModel {
@beforeSave()
static async hashPassword(user: User) {
if (user.$dirty.password) {
user.password = await hash.make(user.password)
}
}
}

ドライバーの切り替え

アプリケーションが複数のハッシュドライバーを使用している場合は、hash.useメソッドを使用してドライバーを切り替えることができます。

hash.useメソッドは、設定ファイル内のマッピング名を受け取り、一致するドライバーのインスタンスを返します。

import hash from '@adonisjs/core/services/hash'
// 設定ファイルの"list.scrypt"マッピングを使用します
await hash.use('scrypt').make('secret')
// 設定ファイルの"list.bcrypt"マッピングを使用します
await hash.use('bcrypt').make('secret')
// 設定ファイルの"list.argon"マッピングを使用します
await hash.use('argon').make('secret')

パスワードの再ハッシュが必要かどうかの確認

最新の設定オプションを使用してパスワードを安全に保つことが推奨されます。とくに、古いバージョンのハッシュアルゴリズムに関する脆弱性が報告された場合には、再ハッシュを行う必要があります。

最新のオプションで設定を更新した後、hash.needsReHashメソッドを使用してパスワードハッシュが古いオプションを使用しているかどうかを確認し、再ハッシュを行うことができます。

このチェックは、ユーザーログイン時に行う必要があります。なぜなら、平文パスワードにアクセスできるのはその時だけだからです。

import hash from '@adonisjs/core/services/hash'
if (await hash.needsReHash(user.password)) {
user.password = await hash.make(plainTextPassword)
await user.save()
}

モデルフックを使用してハッシュを計算する場合、user.passwordに平文値を割り当てることができます。

if (await hash.needsReHash(user.password)) {
// モデルフックによってパスワードが再ハッシュされます
user.password = plainTextPassword
await user.save()
}

テスト中にハッシュサービスを偽装する

値をハッシュ化することは通常遅いプロセスであり、テストを遅くする可能性があります。そのため、パスワードのハッシュ化を無効にするためにhash.fakeメソッドを使用してハッシュサービスを偽装することを検討するかもしれません。

以下の例では、UserFactoryを使用して20人のユーザーを作成しています。パスワードのハッシュ化をモデルフックで行っているため、5〜7秒かかる場合があります(設定によって異なります)。

import hash from '@adonisjs/core/services/hash'
test('ユーザーリストを取得する', async ({ client }) => {
await UserFactory().createMany(20)
const response = await client.get('users')
})

ただし、一度ハッシュサービスを偽装すると、同じテストが桁違いに高速に実行されます。

import hash from '@adonisjs/core/services/hash'
test('ユーザーリストを取得する', async ({ client }) => {
hash.fake()
await UserFactory().createMany(20)
const response = await client.get('users')
hash.restore()
})

カスタムハッシュドライバーの作成

ハッシュドライバーは、HashDriverContractインターフェイスを実装する必要があります。また、公式のハッシュドライバーはハッシュをストレージに保存するためにPHC形式を使用しています。ハッシュの作成と検証方法を確認するために、既存のドライバーの実装を確認できます。

import {
HashDriverContract,
ManagerDriverFactory
} from '@adonisjs/core/types/hash'
/**
* ハッシュドライバーが受け入れる設定
*/
export type PbkdfConfig = {
}
/**
* ドライバーの実装
*/
export class Pbkdf2Driver implements HashDriverContract {
constructor(public config: PbkdfConfig) {
}
/**
* ハッシュ値がハッシュアルゴリズムに従ってフォーマットされているかどうかをチェックします。
*/
isValidHash(value: string): boolean {
}
/**
* 生の値をハッシュに変換します。
*/
async make(value: string): Promise<string> {
}
/**
* 平文の値が提供されたハッシュと一致するかどうかを検証します。
*/
async verify(
hashedValue: string,
plainValue: string
): Promise<boolean> {
}
/**
* ハッシュが再ハッシュされる必要があるかどうかを検証します。
* 設定パラメータが変更された場合に再ハッシュが必要です。
*/
needsReHash(value: string): boolean {
}
}
/**
* 設定ファイル内でドライバーを参照するためのファクトリ関数です。
* できます。
*/
export function pbkdf2Driver (config: PbkdfConfig): ManagerDriverFactory {
return () => {
return new Pbkdf2Driver(config)
}
}

前述のコード例では、次の値をエクスポートしています。

  • PbkdfConfig: 受け入れる設定のTypeScript型。

  • Pbkdf2Driver: ドライバーの実装。HashDriverContractインターフェイスに準拠する必要があります。

  • pbkdf2Driver: 最後に、ドライバーのインスタンスを遅延して作成するためのファクトリ関数。

ドライバーの使用

実装が完了したら、pbkdf2Driverファクトリ関数を使用して、設定ファイル内でドライバーを参照できます。

config/hash.ts
import { defineConfig } from '@adonisjs/core/hash'
import { pbkdf2Driver } from 'my-custom-package'
export default defineConfig({
list: {
pbkdf2: pbkdf2Driver({
// 設定はここに記述します
}),
}
})