PHP の openssl_encrypt で DES-CBC が false を返す原因と対処|error:0308010C(OpenSSL 3)

saratogax
記事内に商品プロモーションを含む場合があります

PHP の公式 Docker イメージのパッチバージョンを上げただけで、openssl_encrypt の DES-CBC が false を返すようになっていませんか?

ビルドもコンテナの起動も問題なく通り、暗号化の処理だけが黙って失敗します。

私の環境では、E2E テストで外部 API の代わりに立てるモックサーバーのイメージを php:8.2.5-apache から php:8.2.35-apache に上げたところで起きました。

原因は PHP ではなく、OS と OpenSSL のバージョンが変わったことです。

Debian 11 から 13 に変わって OpenSSL が 1.1.1 から 3 系になり、DES が legacy プロバイダに移ったため、設定を変えないと使えません。

この記事では、エラーの確かめ方と openssl.cnf で legacy を有効にする方法、ビルド時に設定の失敗に気付く工夫をまとめます。

症状: openssl_encrypt が false を返す

このモックサーバーには、連携先の API の仕様に合わせて、DES-CBC で暗号化する処理があります。

$encrypted = openssl_encrypt($data, 'DES-CBC', $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($encrypted);

openssl_encrypt は、失敗すると false を返します(PHP マニュアル)。

このコードでは、false がそのまま base64_encode に渡り、空文字になります。

例外にならないので、呼び出し元には空の値が返ってくるだけで、原因が分かりにくい作りです。

失敗の理由は、openssl_error_string で確認できます。

docker run --rm php:8.2.35-apache php -r '
  echo OPENSSL_VERSION_TEXT, PHP_EOL;
  var_dump(openssl_encrypt("test", "DES-CBC", "12345678", OPENSSL_RAW_DATA, "12345678"));
  echo openssl_error_string(), PHP_EOL;'

実行結果は次のとおりです(イメージの取得のログは省いています)。

OpenSSL 3.5.7 9 Jun 2026
bool(false)
error:0308010C:digital envelope routines::unsupported

OpenSSL 3.5.7 の上で DES-CBC の暗号化が false になり、unsupported(対応していない)というエラーが返ってきます。

error:0308010C で検索すると Node.js の話がたくさん見つかりますが、同じ OpenSSL 3 のエラーです。

原因: ベースの OS が変わり、OpenSSL 3 になった

PHP の公式イメージは、タグに OS 名を付けないと、その時点で既定の Debian が使われます。

公式イメージのリポジトリの versions.json では、8.2 系の先頭(既定)は trixie(Debian 13)です。

以前使っていた 8.2.5 は、bullseye(Debian 11)でした。

それぞれの Debian に入っている OpenSSL は次のとおりです(Debian のパッケージのページの現在のバージョン)。

イメージベースの OSOpenSSL
php:8.2.5-apacheDebian 11(bullseye)1.1.1w
php:8.2.35-apacheDebian 13(trixie)3.5.7
※表は横スクロールできます

PHP のパッチバージョンを上げただけでも、OS のメジャーバージョンが 2 つ上がることがあります。

OpenSSL 3.0 の移行ガイドには、DES-CBC などが legacy プロバイダに移ったと書かれています(OpenSSL 3.0 の移行ガイド)。

Algorithms for “DESX-CBC”, “DES-ECB”, “DES-CBC”, “DES-OFB”, “DES-CFB”, “DES-CFB1” and “DES-CFB8” have been moved to the Legacy Provider.

使いたい場合は legacy プロバイダを読み込む必要があり、そのままでは DES-CBC を使えません。

OS 名を付けたタグでも避けられない

公式イメージには、php:8.2.35-apache-bookworm のように OS 名を付けたタグもあります。

ただ、8.2.35 の apache のイメージで選べるのは trixie と bookworm(Debian 12)だけで、bookworm の OpenSSL も 3 系です。

8.2 系の修正版に上げる時点で、OpenSSL 3 への対応は避けられません。

対処: openssl.cnf で legacy と default を有効にする

DES をやめられるならそれが一番ですが、今回は連携先の仕様で DES が決まっているため、legacy プロバイダを有効にしました。

本番や開発環境にはデプロイせず、テストの中だけで使うモックサーバーだという事情もあります。

default プロバイダも明示的に有効にする

ここで一つ落とし穴があります。

OpenSSL のドキュメントには、default プロバイダの読み込みについて次のように書かれています(OSSL_PROVIDER-default)。

If an attempt to load a provider has already been made (whether successful or not) then the default provider won’t be loaded automatically.

つまり、設定に legacy だけを書くと default が自動で読み込まれなくなり、AES などの普通の暗号まで使えなくなります。

legacy を有効にするときは、default も明示的に有効にする必要があります。

Debian の /etc/ssl/openssl.cnf を、次の形にします。

[provider_sect]
default = default_sect
legacy = legacy_sect

[default_sect]
activate = 1

[legacy_sect]
activate = 1

trixie のイメージの openssl.cnf には、[provider_sect] と [default_sect] がすでにあります。

そこで Dockerfile では、sed で [provider_sect] に legacy の行を足し、[default_sect] の activate = 1 がコメントアウトされていれば外すようにしました。

FROM php:8.2.35-apache

# DES-CBC は OpenSSL 3 では legacy プロバイダを読み込まないと使えない。
# プロバイダを明示すると default が自動で読み込まれなくなるため、default も有効にする。
# 書き換えが効かなかったときにイメージを作らないよう、最後に DES-CBC と AES の暗号化を試す
RUN sed -i \
      -e '/^\[provider_sect\]/a legacy = legacy_sect' \
      -e '/^\[default_sect\]/,/^\[/ s/^# *activate = 1/activate = 1/' \
      /etc/ssl/openssl.cnf \
 && printf '\n[legacy_sect]\nactivate = 1\n' >> /etc/ssl/openssl.cnf \
 && php -r 'foreach (["DES-CBC", "AES-128-CBC"] as $c) { if (openssl_encrypt("test", $c, "1234567890123456", OPENSSL_RAW_DATA, substr("1234567890123456", 0, openssl_cipher_iv_length($c))) === false) { fwrite(STDERR, "$c: " . openssl_error_string() . "\n"); exit(1); } }'

ビルドのときに暗号化を試す

最後の php -r は、DES-CBC と AES-128-CBC で実際に暗号化してみて、どちらかが false ならビルドを失敗させます。

sed での書き換えは、Debian の openssl.cnf の書き方が変わると、何も言わずに効かなくなります。

壊れた設定のイメージがそのまま作られて、使うときに初めて気付くのを防ぐために入れました。

AES も試しているのは、default の有効化に失敗したことにも気付けるようにするためです。

このイメージで E2E テストを流し、DES を使う処理も含めてすべて成功しました。

注意点: OS が変わると、ほかにも影響が出ることがある

ベースの OS が 2 世代上がると、OpenSSL 以外のライブラリも変わります。

このモックサーバーのイメージにはほかのバイナリも同梱していたので、E2E テストであわせて動くことを確認しました。

タグに OS 名を付けずに使っている場合、PHP のバージョンを上げるときは、ベースの OS が変わっていないかも確認しておくと安心です。

OS 側の OpenSSL のバージョンの確かめ方は、OpenSSL のバージョン確認と更新方法にまとめています。

OS 名を付ければ OS は勝手に変わりませんが、その OS の提供が終われば、いずれ上げることになります。

どちらの書き方を選ぶにしても、「パッチバージョンだから中身は同じ」とは限らないことだけは覚えておくとよいと思います。

まとめ

  • PHP の公式イメージは、OS 名を付けないタグだと、パッチバージョンを上げただけでベースの OS が変わることがある
  • Debian 12 以降は OpenSSL 3 で、DES-CBC は legacy プロバイダに移ったため、openssl_encrypt が false を返す
  • legacy を有効にするときは default も明示的に有効にする。ビルド時に暗号化を試しておくと、設定の書き換えの失敗に気付ける

DES のような古い暗号を使っている場合は、イメージを上げる前に一度確認してみてください。

ABOUT ME
saratoga
saratoga
フリーランスエンジニア
仕事にも趣味にも IT を駆使するフリーランスエンジニア。技術的な TIPS や日々の生活の中で深堀りしてみたくなったことを備忘録として残していきます。
記事URLをコピーしました