リアルタイムデータ処理や大容量ストリーミングデータの処理において、効率的なハッシュ計算は重要な技術です。PHPのhash_update_stream
関数を使用することで、ストリームからのデータを直接ハッシュコンテキストに追加でき、メモリ効率的でパフォーマンスに優れた処理が実現できます。この記事では、hash_update_stream
関数の基本的な使い方から実践的な応用例まで、詳しく解説していきます。
hash_update_stream関数とは?
hash_update_stream
関数は、ストリームリソースからデータを読み取り、そのデータを直接ハッシュコンテキストに追加する関数です。ファイルハンドル、ネットワークストリーム、メモリストリームなど、様々なストリームリソースに対応しています。
従来の方法との比較
従来の方法(手動読み取り):
php$context = hash_init('sha256');
$handle = fopen('data.txt', 'r');
while (($chunk = fread($handle, 8192)) !== false) {
hash_update($context, $chunk);
}
$hash = hash_final($context);
fclose($handle);
hash_update_stream方式(推奨):
php$context = hash_init('sha256');
$handle = fopen('data.txt', 'r');
hash_update_stream($context, $handle);
$hash = hash_final($context);
fclose($handle);
hash_update_stream関数の基本構文
phphash_update_stream(
HashContext $context,
resource $handle,
int $length = -1
): int
パラメータの詳細
$context(ハッシュコンテキスト) hash_init()
関数で初期化されたハッシュコンテキストオブジェクトです。
$handle(ストリームハンドル) 読み取り可能なストリームリソース。ファイルハンドル、ソケット、メモリストリームなどが指定できます。
$length(読み取り長) 読み取る最大バイト数。-1(デフォルト)の場合、ストリームの終端まで読み取ります。
戻り値 実際に読み取られ、ハッシュコンテキストに追加されたバイト数を返します。
基本的な使用例
例1: ファイルストリームからのハッシュ計算
php<?php
/**
* ファイルストリームを使用したハッシュ計算
*/
function calculateStreamHash($filename, $algorithm = 'sha256') {
if (!file_exists($filename)) {
throw new InvalidArgumentException("ファイルが存在しません: {$filename}");
}
// ファイルを開く
$handle = fopen($filename, 'rb');
if (!$handle) {
throw new RuntimeException("ファイルを開けませんでした: {$filename}");
}
try {
// ハッシュコンテキストを初期化
$context = hash_init($algorithm);
// ストリームからハッシュコンテキストに追加
$bytesRead = hash_update_stream($context, $handle);
// 最終ハッシュ値を取得
$hash = hash_final($context);
return [
'hash' => $hash,
'bytes_processed' => $bytesRead,
'algorithm' => $algorithm
];
} finally {
fclose($handle);
}
}
// 使用例
try {
$filename = 'sample_data.txt';
$result = calculateStreamHash($filename);
echo "ファイル: {$filename}\n";
echo "ハッシュ値: {$result['hash']}\n";
echo "処理バイト数: " . number_format($result['bytes_processed']) . "\n";
echo "アルゴリズム: {$result['algorithm']}\n";
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
例2: 部分的なストリーム読み取り
php<?php
/**
* ストリームの一部分のみをハッシュ計算する
*/
function calculatePartialStreamHash($filename, $offset = 0, $length = 1024, $algorithm = 'sha256') {
$handle = fopen($filename, 'rb');
if (!$handle) {
throw new RuntimeException("ファイルを開けませんでした: {$filename}");
}
try {
// 指定位置にシークする
if ($offset > 0) {
fseek($handle, $offset);
}
$context = hash_init($algorithm);
// 指定した長さだけ読み取り
$bytesRead = hash_update_stream($context, $handle, $length);
$hash = hash_final($context);
return [
'hash' => $hash,
'offset' => $offset,
'requested_length' => $length,
'actual_bytes_read' => $bytesRead,
'current_position' => ftell($handle)
];
} finally {
fclose($handle);
}
}
// 使用例
try {
$filename = 'large_file.dat';
// ファイルの先頭1KBのハッシュ
$headerHash = calculatePartialStreamHash($filename, 0, 1024);
echo "ヘッダーハッシュ: {$headerHash['hash']}\n";
// ファイルの中間部分のハッシュ
$middleHash = calculatePartialStreamHash($filename, 1024, 2048);
echo "中間部分ハッシュ: {$middleHash['hash']}\n";
// 実際に読み取られたバイト数を確認
echo "実際の読み取りバイト数: {$middleHash['actual_bytes_read']}\n";
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
実践的な応用例
例3: ネットワークストリームのハッシュ計算
php<?php
/**
* HTTP/FTPストリームからのハッシュ計算
*/
class NetworkStreamHasher {
private $timeout;
private $userAgent;
public function __construct($timeout = 30, $userAgent = 'PHP Stream Hasher/1.0') {
$this->timeout = $timeout;
$this->userAgent = $userAgent;
}
/**
* HTTPリソースのハッシュを計算
*/
public function hashHttpResource($url, $algorithm = 'sha256') {
// ストリームコンテキストを作成
$context = stream_context_create([
'http' => [
'timeout' => $this->timeout,
'user_agent' => $this->userAgent,
'follow_location' => true,
'max_redirects' => 5
]
]);
// HTTPストリームを開く
$handle = fopen($url, 'rb', false, $context);
if (!$handle) {
throw new RuntimeException("HTTPストリームを開けませんでした: {$url}");
}
try {
// ストリームメタデータを取得
$metadata = stream_get_meta_data($handle);
$headers = $metadata['wrapper_data'] ?? [];
// ハッシュ計算
$hashContext = hash_init($algorithm);
$startTime = microtime(true);
$bytesRead = hash_update_stream($hashContext, $handle);
$endTime = microtime(true);
$hash = hash_final($hashContext);
return [
'url' => $url,
'hash' => $hash,
'bytes_downloaded' => $bytesRead,
'download_time' => $endTime - $startTime,
'download_speed' => $bytesRead > 0 ? $bytesRead / ($endTime - $startTime) : 0,
'http_headers' => $headers,
'algorithm' => $algorithm
];
} finally {
fclose($handle);
}
}
/**
* 複数のHTTPリソースを並列処理
*/
public function hashMultipleResources($urls, $algorithm = 'sha256') {
$results = [];
foreach ($urls as $url) {
try {
echo "処理中: {$url}\n";
$result = $this->hashHttpResource($url, $algorithm);
$results[$url] = $result;
echo "完了: " . number_format($result['bytes_downloaded']) . " バイト, ";
echo number_format($result['download_speed'] / 1024, 2) . " KB/s\n";
} catch (Exception $e) {
$results[$url] = [
'error' => $e->getMessage(),
'url' => $url
];
echo "エラー: {$url} - " . $e->getMessage() . "\n";
}
}
return $results;
}
}
// 使用例
try {
$hasher = new NetworkStreamHasher(60); // 60秒タイムアウト
$urls = [
'https://httpbin.org/bytes/1024', // 1KBのランダムデータ
'https://httpbin.org/base64/SFRUUEJJTiBpcyBhd2Vzb21l', // Base64データ
];
echo "=== ネットワークリソースのハッシュ計算 ===\n";
$results = $hasher->hashMultipleResources($urls);
echo "\n=== 結果サマリー ===\n";
foreach ($results as $url => $result) {
if (isset($result['error'])) {
echo "❌ {$url}: {$result['error']}\n";
} else {
echo "✅ {$url}:\n";
echo " ハッシュ: {$result['hash']}\n";
echo " サイズ: " . number_format($result['bytes_downloaded']) . " バイト\n";
}
}
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
例4: メモリストリームを使用した動的データ処理
php<?php
/**
* メモリストリームを使用した動的データハッシュ計算
*/
class MemoryStreamHasher {
/**
* 動的に生成されるデータのハッシュを計算
*/
public function hashGeneratedData($dataGenerator, $algorithm = 'sha256') {
// メモリストリームを作成
$memoryStream = fopen('php://memory', 'w+b');
if (!$memoryStream) {
throw new RuntimeException("メモリストリームを作成できませんでした");
}
try {
// データを生成してメモリストリームに書き込み
$totalBytes = 0;
foreach ($dataGenerator as $chunk) {
$written = fwrite($memoryStream, $chunk);
$totalBytes += $written;
}
// ストリームの先頭に戻る
rewind($memoryStream);
// ハッシュ計算
$context = hash_init($algorithm);
$bytesRead = hash_update_stream($context, $memoryStream);
$hash = hash_final($context);
return [
'hash' => $hash,
'generated_bytes' => $totalBytes,
'processed_bytes' => $bytesRead,
'algorithm' => $algorithm
];
} finally {
fclose($memoryStream);
}
}
/**
* CSVデータのハッシュ計算
*/
public function hashCsvData($data, $algorithm = 'sha256') {
$memoryStream = fopen('php://memory', 'w+b');
try {
// CSVデータを生成
foreach ($data as $row) {
fputcsv($memoryStream, $row);
}
rewind($memoryStream);
// ハッシュ計算
$context = hash_init($algorithm);
$bytesRead = hash_update_stream($context, $memoryStream);
$hash = hash_final($context);
return [
'hash' => $hash,
'rows_processed' => count($data),
'csv_bytes' => $bytesRead,
'algorithm' => $algorithm
];
} finally {
fclose($memoryStream);
}
}
/**
* JSONデータのストリーミングハッシュ
*/
public function hashJsonStream($objects, $algorithm = 'sha256') {
$memoryStream = fopen('php://memory', 'w+b');
try {
// JSON配列の開始
fwrite($memoryStream, '[');
$first = true;
foreach ($objects as $object) {
if (!$first) {
fwrite($memoryStream, ',');
}
fwrite($memoryStream, json_encode($object));
$first = false;
}
// JSON配列の終了
fwrite($memoryStream, ']');
rewind($memoryStream);
// ハッシュ計算
$context = hash_init($algorithm);
$bytesRead = hash_update_stream($context, $memoryStream);
$hash = hash_final($context);
return [
'hash' => $hash,
'objects_count' => iterator_count($objects),
'json_bytes' => $bytesRead,
'algorithm' => $algorithm
];
} finally {
fclose($memoryStream);
}
}
}
// 使用例
try {
$hasher = new MemoryStreamHasher();
// 1. 動的データ生成のハッシュ
echo "=== 動的データ生成のハッシュ ===\n";
$dataGenerator = function() {
for ($i = 0; $i < 1000; $i++) {
yield "データ行 {$i}: " . str_repeat('*', $i % 50) . "\n";
}
};
$result1 = $hasher->hashGeneratedData($dataGenerator());
echo "生成バイト数: " . number_format($result1['generated_bytes']) . "\n";
echo "ハッシュ: {$result1['hash']}\n\n";
// 2. CSVデータのハッシュ
echo "=== CSVデータのハッシュ ===\n";
$csvData = [
['名前', '年齢', '職業'],
['田中太郎', 30, 'エンジニア'],
['佐藤花子', 25, 'デザイナー'],
['鈴木一郎', 35, 'マネージャー']
];
$result2 = $hasher->hashCsvData($csvData);
echo "処理行数: {$result2['rows_processed']}\n";
echo "CSVバイト数: {$result2['csv_bytes']}\n";
echo "ハッシュ: {$result2['hash']}\n\n";
// 3. JSONストリームのハッシュ
echo "=== JSONストリームのハッシュ ===\n";
$jsonObjects = [
['id' => 1, 'name' => 'Product A', 'price' => 1000],
['id' => 2, 'name' => 'Product B', 'price' => 2000],
['id' => 3, 'name' => 'Product C', 'price' => 1500]
];
$result3 = $hasher->hashJsonStream($jsonObjects);
echo "オブジェクト数: {$result3['objects_count']}\n";
echo "JSONバイト数: {$result3['json_bytes']}\n";
echo "ハッシュ: {$result3['hash']}\n";
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
例5: ストリーミング暗号化との組み合わせ
php<?php
/**
* ストリーミング暗号化とハッシュ計算の同時実行
*/
class StreamEncryptionHasher {
private $cipher;
private $key;
private $iv;
public function __construct($cipher = 'aes-256-cbc', $key = null) {
$this->cipher = $cipher;
$this->key = $key ?: random_bytes(32);
$this->iv = random_bytes(openssl_cipher_iv_length($cipher));
}
/**
* ストリームを暗号化しながらハッシュ計算
*/
public function encryptAndHashStream($inputHandle, $outputHandle, $hashAlgorithm = 'sha256') {
$hashContext = hash_init($hashAlgorithm);
$encryptedHashContext = hash_init($hashAlgorithm);
$chunkSize = 8192;
$totalPlainBytes = 0;
$totalEncryptedBytes = 0;
// IVを出力ファイルの先頭に書き込み
fwrite($outputHandle, $this->iv);
hash_update($encryptedHashContext, $this->iv);
$totalEncryptedBytes += strlen($this->iv);
while (($chunk = fread($inputHandle, $chunkSize)) !== false && $chunk !== '') {
// 平文のハッシュ計算
hash_update($hashContext, $chunk);
$totalPlainBytes += strlen($chunk);
// 暗号化
$encryptedChunk = openssl_encrypt(
$chunk,
$this->cipher,
$this->key,
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING
);
if ($encryptedChunk === false) {
throw new RuntimeException("暗号化に失敗しました");
}
// 暗号化データの書き込みとハッシュ計算
fwrite($outputHandle, $encryptedChunk);
hash_update($encryptedHashContext, $encryptedChunk);
$totalEncryptedBytes += strlen($encryptedChunk);
}
return [
'plain_hash' => hash_final($hashContext),
'encrypted_hash' => hash_final($encryptedHashContext),
'plain_bytes' => $totalPlainBytes,
'encrypted_bytes' => $totalEncryptedBytes,
'cipher' => $this->cipher,
'iv' => bin2hex($this->iv)
];
}
/**
* 暗号化ファイルの検証とハッシュ計算
*/
public function verifyEncryptedFile($encryptedFile, $expectedPlainHash) {
$handle = fopen($encryptedFile, 'rb');
if (!$handle) {
throw new RuntimeException("暗号化ファイルを開けませんでした");
}
try {
// IVを読み取り
$iv = fread($handle, openssl_cipher_iv_length($this->cipher));
// 残りのデータをハッシュ計算(復号化しながら)
$hashContext = hash_init('sha256');
$chunkSize = 8192;
while (($encryptedChunk = fread($handle, $chunkSize)) !== false && $encryptedChunk !== '') {
$decryptedChunk = openssl_decrypt(
$encryptedChunk,
$this->cipher,
$this->key,
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,
$iv
);
if ($decryptedChunk !== false) {
hash_update($hashContext, $decryptedChunk);
}
}
$actualHash = hash_final($hashContext);
return [
'expected_hash' => $expectedPlainHash,
'actual_hash' => $actualHash,
'verification_passed' => hash_equals($expectedPlainHash, $actualHash),
'iv_used' => bin2hex($iv)
];
} finally {
fclose($handle);
}
}
}
// 使用例
try {
echo "=== ストリーミング暗号化とハッシュ計算 ===\n";
$encrypter = new StreamEncryptionHasher();
// テストファイルを作成
$plainFile = 'test_plain.txt';
$encryptedFile = 'test_encrypted.bin';
file_put_contents($plainFile, "これは暗号化テスト用のサンプルデータです。\n" . str_repeat("テストデータ ", 1000));
// 暗号化とハッシュ計算を同時実行
$plainHandle = fopen($plainFile, 'rb');
$encryptedHandle = fopen($encryptedFile, 'wb');
$result = $encrypter->encryptAndHashStream($plainHandle, $encryptedHandle);
fclose($plainHandle);
fclose($encryptedHandle);
echo "平文ハッシュ: {$result['plain_hash']}\n";
echo "暗号化ファイルハッシュ: {$result['encrypted_hash']}\n";
echo "平文サイズ: " . number_format($result['plain_bytes']) . " バイト\n";
echo "暗号化サイズ: " . number_format($result['encrypted_bytes']) . " バイト\n";
echo "使用暗号: {$result['cipher']}\n";
echo "IV: {$result['iv']}\n\n";
// 検証
echo "=== 暗号化ファイルの検証 ===\n";
$verification = $encrypter->verifyEncryptedFile($encryptedFile, $result['plain_hash']);
echo "期待ハッシュ: {$verification['expected_hash']}\n";
echo "実際ハッシュ: {$verification['actual_hash']}\n";
echo "検証結果: " . ($verification['verification_passed'] ? '成功' : '失敗') . "\n";
// クリーンアップ
unlink($plainFile);
unlink($encryptedFile);
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
パフォーマンス比較とベンチマーク
php<?php
/**
* 各種ストリーム処理方法のパフォーマンス比較
*/
function benchmarkStreamMethods($filename, $chunkSize = 8192) {
if (!file_exists($filename)) {
// テストファイル作成
$handle = fopen($filename, 'wb');
for ($i = 0; $i < 1024; $i++) {
fwrite($handle, str_repeat(chr(rand(65, 90)), 1024)); // 1MB
}
fclose($handle);
}
$fileSize = filesize($filename);
echo "テストファイルサイズ: " . number_format($fileSize / 1024 / 1024, 2) . "MB\n\n";
// 方法1: hash_update_stream
$start = microtime(true);
$handle1 = fopen($filename, 'rb');
$context1 = hash_init('sha256');
hash_update_stream($context1, $handle1);
$hash1 = hash_final($context1);
fclose($handle1);
$time1 = microtime(true) - $start;
// 方法2: 手動チャンク読み取り + hash_update
$start = microtime(true);
$handle2 = fopen($filename, 'rb');
$context2 = hash_init('sha256');
while (($chunk = fread($handle2, $chunkSize)) !== false && $chunk !== '') {
hash_update($context2, $chunk);
}
$hash2 = hash_final($context2);
fclose($handle2);
$time2 = microtime(true) - $start;
// 方法3: hash_file (参考)
$start = microtime(true);
$hash3 = hash_file('sha256', $filename);
$time3 = microtime(true) - $start;
echo "結果比較:\n";
echo "hash_update_stream: " . number_format($time1 * 1000, 2) . "ms\n";
echo "手動チャンク処理: " . number_format($time2 * 1000, 2) . "ms\n";
echo "hash_file: " . number_format($time3 * 1000, 2) . "ms\n\n";
echo "ハッシュ値一致確認:\n";
echo "hash_update_stream vs 手動処理: " . ($hash1 === $hash2 ? '一致' : '不一致') . "\n";
echo "hash_update_stream vs hash_file: " . ($hash1 === $hash3 ? '一致' : '不一致') . "\n";
return [
'stream_time' => $time1,
'manual_time' => $time2,
'file_time' => $time3,
'file_size' => $fileSize
];
}
// ベンチマーク実行
try {
$testFile = 'benchmark_test.dat';
benchmarkStreamMethods($testFile);
// クリーンアップ
if (file_exists($testFile)) {
unlink($testFile);
}
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
よくある質問と回答
Q: hash_update_stream()とhash_update_file()の違いは何ですか? A: hash_update_file()
はファイルパスを指定してファイル全体を処理しますが、hash_update_stream()
はすでに開かれたストリームリソースを操作します。hash_update_stream()
の方がより柔軟で、ネットワークストリームやメモリストリーム、部分的な読み取りなどに対応できます。
Q: どのような場面でhash_update_stream()が最も有効ですか? A: 以下の場面で特に有効です:
- リアルタイムストリーミングデータの処理
- ネットワーク経由でのデータ受信中のハッシュ計算
- 大容量ファイルの部分的なハッシュ計算
- 動的に生成されるデータのハッシュ化
Q: エラーハンドリングで注意すべき点は? A: ストリームの状態確認、読み取り権限、ネットワークエラー、メモリ不足などを適切に処理する必要があります。また、ストリームリソースの適切なクローズも重要です。
Q: パフォーマンスを最適化するには? A: 適切なチャンクサイズの選択、不要なメモリコピーの回避、ストリームのバッファリング設定の調整などが効果的です。
まとめ
hash_update_stream
関数は、PHPでストリーミングデータのハッシュ計算を効率的に行うための非常に強力なツールです。ファイルからネットワークストリーム、メモリストリームまで幅広いストリームリソースに対応し、リアルタイム処理や大容量データ処理において優れたパフォーマンスを発揮します。
今回紹介した実例を参考に、データ整合性チェック、セキュリティ検証、ストリーミング処理など様々な場面でhash_update_stream
関数を活用してみてください。適切な実装により、メモリ効率的で高性能なデータ処理システムを構築できるでしょう。
現代のWebアプリケーションでは、リアルタイム性とセキュリティの両立が求められます。