Spring Security の BCryptPasswordEncoder でハッシュ化された password を Perl で扱う

Spring Security の BCryptPasswordEncoder でハッシュ化された password などの文字列を、Perl から使って認証とかに使いたい時だってあるじゃない？

いや、ねーよな。

# 前提

こんな出来事がありました。

## DB に保存されたハッシュ値

```
$2a$10$Cc6/UiniMuKkyVsVM.FUt.rmgDm0UOQxuhuGSSuL/LzZUGrNeGvxq
```

こんな値が保存されていた。
Spring Security の仕業らしい。
見慣れぬこのハッシュ値は一体どういう値なんだい。

という状況にハマっていた時、色々ググったが、コレって情報が出てこなかった。

## 生パスワード

```
JscxAX:5[4Q]NYoK
```

とあるログインユーザは
「こいつが俺のパスワードなんだけど合ってる？」
などと、Perl の私に尋ねてきた。

えっ、さっきの Spring Security がハッシュ化して保存されてるあのヘンテコな文字列とこのパスワードの検証？
Perl の私にはわかんないよ？

## いやいや、頑張りましょう

案外簡単なのですよ

### コード

```perl
use strict;
use warnings;
use utf8;
use feature qw(say);
use Crypt::Eksblowfish::Bcrypt qw(bcrypt);

my $raw_password = 'JscxAX:5[4Q]NYoK';

my $hashed_password = '$2a$10$Cc6/UiniMuKkyVsVM.FUt.rmgDm0UOQxuhuGSSuL/LzZUGrNeGvxq';

my $salt  = substr($hashed_password, 0, 29); # "$2a$10$" + 22 文字分
my $check = bcrypt($raw_password, $salt);

if ($check eq $hashed_password) {
    say "ATTERUYO!";
}
else {
    say "ATTENAIYO!";
}
```

### 結果

```
ATTERUYO!
```

よかった。あってた。

…え、これ、毎回 `ATTERUYO!` が出たりしない？

## もし、ユーザが間違ったパスワードを入力してたら

```perl
my $raw_password = 'JscxAX:4[5Q]NYoK';
```
一行だけ、微妙に間違ってる感じに変更。

### 結果

```
ATTENAIYO!
```

よかった。
ちゃんと間違いを判定できてる。

# Perl からも同じマナーで保存しといてね

Spring Security さんが
「Perl から、パスワード変更して、保存しておく機能作っておいてね」
とおっしゃっている。

えぇ、貴方はその値を検証で使うんですよね？
貴方にも扱える形式にしないとダメですか…？

…当然、そうしないとダメですよね。

## 嫌われ者の Perl さんにもちゃんと出来るはずよ。だから頑張って

Java に出来て Perl に出来ないとか、ラクダの風上にも置けないこと言っちゃダメです。

### コード

```perl
use strict;
use warnings;
use utf8;
use feature qw(say);
use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64);
use Crypt::Random qw(makerandom_octet);

sub gen_salt {
    return en_base64(makerandom_octet(Length => 16));
}

my $raw_password = 'Z86J9_Kr_sDcw#o4'; # 新しいパスワード

my $salt = '$2a$10$' . gen_salt();
my $hashed_password = bcrypt($raw_password, $salt);

say $hashed_password;
```

### 結果

```
$2a$10$/IwrxT05tg1ZlmUrV.7eAOsowJsbkVHs6ku54FC0VHBew23HOm61W
```

それっぽいのが出来たじゃない

# Spring Security で検証してね

`Z86J9_Kr_sDcw#o4`
というパスワードが上記の Perl のコードによって
`$2a$10$/IwrxT05tg1ZlmUrV.7eAOsowJsbkVHs6ku54FC0VHBew23HOm61W`
っていうそれっぽい文字列にされて DB に保存されてしまっている。
これ、Java (Spring Security) で本当にこのハッシュを正しく検証が出来るのか…？

## コード

いかにも、Java とか普段書かない人間が書いた感の強い、随分と適当なコードによる検証を行った。

```java
package com.example.demo;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

public class DemoApplication {
    public static void main(String[] args) {
        String rawPassword = "Z86Jq_Kr_sDcw#o4";
        String hashedPassword = "$2a$10$/IwrxT05tg1ZlmUrV.7eAOsowJsbkVHs6ku54FC0VHBew23HOm61W";

        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        if (passwordEncoder.matches(rawPassword, hashedPassword)) {
            System.out.println("ATTERUYO!");
        }
        else {
            System.out.println("ATTENAIYO!");
        }
    }
}
```

### 結果

```
ATTERUYO!
```

ちゃんと出来たよよかったね。

… え、これ、毎回 `ATTERUYO!` が出たりしない？

## もし、ユーザが間違ったパスワードを入力してたら

```java
        String rawPassword = "Z86Jq_Kr_sDcw#o4";
```

一行だけ、微妙に間違ってる感じに変更。

### 結果

```
ATTENAIYO!
```

よかった。
ちゃんと間違いを判定できてる。

# 他のハッシュ化は？

SCryptPasswordEncoder とか Pbkdf2PasswordEncoder とかあるけど、今時点で自分の環境でそれは課題になってないので、いつか誰かがまとめたらいいね。