エラーメッセージ
The specified key byte array is 96 bits which is not secure enough for any JWT HMAC-SHA algorithm.
別の表記
- io.jsonwebtoken.security.WeakKeyException: The specified key byte array is 96 bits which is not secure enough for any JWT HMAC-SHA algorithm.
- io.jsonwebtoken.security.SignatureException: Unable to compute HS512 signature. Cause: The signing key's size is 256 bits which is not secure enough for the HS512 algorithm.
- io.jsonwebtoken.security.WeakKeyException: The signing key's size is 256 bits which is not secure enough for the HS512 algorithm.
ビット数は、渡した鍵に応じた値になります。`Keys.hmacShaKeyFor` のメッセージは「…not secure enough for any JWT HMAC-SHA algorithm」です。鍵の長さが足りないアルゴリズムを明示的に指定して署名した場合は、メッセージにそのアルゴリズム名が含まれます。JJWT 0.12 以降はこれを「SignatureException: Unable to compute HS512 signature. Cause: The signing key's size is 256 bits…」としてラップし、0.11 は `WeakKeyException` として直接スローします。
JJWT (Java) — io.jsonwebtoken.security.WeakKeyException from Keys.hmacShaKeyFor / signWith
このエラーの意味
JJWT は、アルゴリズムのハッシュ出力よりビット数の少ない値から HMAC 鍵を作ることを拒否します。HS256 では 256 ビット(32 バイト)以上、HS384 では 384 ビット以上、HS512 では 512 ビット以上が必要です。RFC 7518 §3.2 はこれを必須(MUST)と定めています。短い共有秘密は、それで署名されたトークンが 1 つでもあればオフラインの総当たりで復元でき、そうなれば誰でも有効なトークンを発行できてしまうからです。メッセージ中の数値は、渡したバイト列のサイズを示しています。
発生する理由
人が読める文字列のシークレット
よくある`secret` や `changeme`、チュートリアルに載っている短いパスフレーズのような値は、32 バイトを大きく下回ります。その長さからも、もともと本番環境で使うための値ではなかったことがうかがえます。
JJWT 0.9 からのアップグレード
よくあるJJWT 0.9 は、文字列として渡された短い鍵を受け付けていました。0.10 以降は RFC 7518 の鍵長を強制するため、同じ設定がアップグレード後に失敗します。
HS256 には足りるが HS512 には短い鍵
ときどき32 バイトの鍵は HS256 の要件を満たしますが、HS384/HS512 の要件は満たしません。`signWith(key)` はその鍵で使える最も強いアルゴリズムを選びますが、それより強いアルゴリズムを明示的に指定すると失敗します。
修正方法
- 1.
適切なサイズのランダムなシークレットを生成する
暗号論的に安全な乱数値(64 バイトあればすべての HMAC の種類に対応できます)を使い、base64 でエンコードしてシークレットマネージャーに保存してください。
bash # 64 random bytes, base64-encoded: enough for HS256, HS384 and HS512 openssl rand -base64 64 | tr -d '\n' - 2.
保存したシークレットをバイト列にデコードする
base64 のテキストからではなく、デコードしたバイト列から鍵を作成してください。そうすれば、どのサービスでも同じ鍵が導出されます。
java import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; javax.crypto.SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(System.getenv("JWT_SECRET_B64"))); String token = io.jsonwebtoken.Jwts.builder().subject("user-42").signWith(key).compact();