こんにちは!今回は、PHPの標準関数であるsha1()について詳しく解説していきます。文字列から160ビット(40文字の16進数)のハッシュ値を生成できる関数です!
sha1関数とは?
sha1()関数は、SHA-1アルゴリズムを使用して文字列のハッシュ値を計算する関数です。
“Secure Hash Algorithm 1″の略で、任意の長さの文字列から固定長(40文字)のハッシュ値を生成します。ファイルの整合性確認、データの識別などに使用されます!
基本的な構文
sha1(string $string, bool $binary = false): string
- $string: ハッシュ化する文字列
- $binary:
trueで生のバイナリ出力(20バイト)、falseで16進数文字列(40文字) - 戻り値: SHA-1ハッシュ値
基本的な使用例
シンプルなハッシュ生成
// 基本的な使用
$text = "Hello World";
$hash = sha1($text);
echo $hash . "\n";
// 出力: 0a4d55a8d778e5022fab701977c5d840bbc486d0(40文字)
// 空文字列
$hash = sha1("");
echo $hash . "\n";
// 出力: da39a3ee5e6b4b0d3255bfef95601890afd80709
// 数値
$hash = sha1("12345");
echo $hash . "\n";
// 出力: 8cb2237d0679ca88db6464eac60da96345513964
バイナリ出力
// 16進数文字列(デフォルト)
$hash = sha1("Hello", false);
echo $hash . "\n";
echo "長さ: " . strlen($hash) . "\n";
// 長さ: 40
// バイナリ出力
$hash = sha1("Hello", true);
echo bin2hex($hash) . "\n";
echo "長さ: " . strlen($hash) . "\n";
// 長さ: 20
大文字小文字の区別
// 大文字小文字は区別される
$hash1 = sha1("Hello");
$hash2 = sha1("hello");
echo $hash1 . "\n";
echo $hash2 . "\n";
if ($hash1 === $hash2) {
echo "同じハッシュ\n";
} else {
echo "異なるハッシュ\n";
}
// 出力: 異なるハッシュ
ファイルのハッシュ
// sha1_file()を使用
// $fileHash = sha1_file('file.txt');
// 手動で読み込む場合
$content = file_get_contents('file.txt');
$hash = sha1($content);
echo $hash . "\n";
実践的な使用例
例1: データ整合性チェック
class IntegrityChecker {
/**
* データのチェックサムを生成
*/
public static function generateChecksum($data) {
if (is_array($data)) {
$data = json_encode($data);
}
return sha1($data);
}
/**
* データの整合性を検証
*/
public static function verify($data, $expectedChecksum) {
$actualChecksum = self::generateChecksum($data);
return [
'valid' => $actualChecksum === $expectedChecksum,
'expected' => $expectedChecksum,
'actual' => $actualChecksum
];
}
/**
* 複数データの整合性を一括チェック
*/
public static function verifyMultiple($items) {
$results = [];
foreach ($items as $item) {
$result = self::verify($item['data'], $item['checksum']);
$result['id'] = $item['id'] ?? 'unknown';
$results[] = $result;
}
return $results;
}
/**
* データと一緒にチェックサムを保存
*/
public static function pack($data) {
$checksum = self::generateChecksum($data);
return [
'data' => $data,
'checksum' => $checksum,
'timestamp' => time()
];
}
/**
* パックされたデータを検証して取り出す
*/
public static function unpack($package) {
if (!isset($package['data']) || !isset($package['checksum'])) {
return ['valid' => false, 'error' => 'Invalid package format'];
}
$verification = self::verify($package['data'], $package['checksum']);
if ($verification['valid']) {
return [
'valid' => true,
'data' => $package['data'],
'timestamp' => $package['timestamp'] ?? null
];
}
return [
'valid' => false,
'error' => 'Checksum mismatch'
];
}
}
// 使用例
echo "=== チェックサム生成 ===\n";
$data = ['name' => 'John', 'age' => 30];
$checksum = IntegrityChecker::generateChecksum($data);
echo "チェックサム: {$checksum}\n";
echo "\n=== 整合性検証 ===\n";
$result = IntegrityChecker::verify($data, $checksum);
echo "検証結果: " . ($result['valid'] ? 'OK' : 'NG') . "\n";
// データ改ざん
$tamperedData = ['name' => 'Jane', 'age' => 30];
$result = IntegrityChecker::verify($tamperedData, $checksum);
echo "改ざんデータ: " . ($result['valid'] ? 'OK' : 'NG') . "\n";
echo "\n=== パック/アンパック ===\n";
$package = IntegrityChecker::pack($data);
print_r($package);
$unpacked = IntegrityChecker::unpack($package);
echo "アンパック: " . ($unpacked['valid'] ? 'OK' : 'NG') . "\n";
例2: キャッシュシステム
class CacheManager {
private $cacheDir;
public function __construct($cacheDir = '/tmp/cache') {
$this->cacheDir = $cacheDir;
if (!is_dir($cacheDir)) {
mkdir($cacheDir, 0755, true);
}
}
/**
* キャッシュキーを生成
*/
private function getCacheKey($identifier) {
return sha1($identifier);
}
/**
* キャッシュファイルパスを取得
*/
private function getCacheFilePath($key) {
// ディレクトリ分散(最初の2文字でサブディレクトリ)
$subdir = substr($key, 0, 2);
$dir = $this->cacheDir . '/' . $subdir;
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
return $dir . '/' . $key . '.cache';
}
/**
* キャッシュに保存
*/
public function set($identifier, $data, $ttl = 3600) {
$key = $this->getCacheKey($identifier);
$filepath = $this->getCacheFilePath($key);
$cacheData = [
'data' => $data,
'expires' => time() + $ttl,
'checksum' => sha1(serialize($data))
];
file_put_contents($filepath, serialize($cacheData));
return $key;
}
/**
* キャッシュから取得
*/
public function get($identifier) {
$key = $this->getCacheKey($identifier);
$filepath = $this->getCacheFilePath($key);
if (!file_exists($filepath)) {
return null;
}
$cacheData = unserialize(file_get_contents($filepath));
// 有効期限チェック
if ($cacheData['expires'] < time()) {
unlink($filepath);
return null;
}
// 整合性チェック
$actualChecksum = sha1(serialize($cacheData['data']));
if ($actualChecksum !== $cacheData['checksum']) {
unlink($filepath);
return null;
}
return $cacheData['data'];
}
/**
* キャッシュを削除
*/
public function delete($identifier) {
$key = $this->getCacheKey($identifier);
$filepath = $this->getCacheFilePath($key);
if (file_exists($filepath)) {
return unlink($filepath);
}
return false;
}
/**
* すべてのキャッシュをクリア
*/
public function clear() {
$count = 0;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->cacheDir),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'cache') {
unlink($file->getPathname());
$count++;
}
}
return $count;
}
}
// 使用例
echo "=== キャッシュシステム ===\n";
$cache = new CacheManager();
// データをキャッシュ
$data = ['user_id' => 123, 'name' => 'John', 'email' => 'john@example.com'];
$key = $cache->set('user:123', $data, 300);
echo "キャッシュキー: {$key}\n";
// キャッシュから取得
$cached = $cache->get('user:123');
if ($cached !== null) {
echo "キャッシュヒット:\n";
print_r($cached);
} else {
echo "キャッシュミス\n";
}
// 複雑なキーでもOK
$complexKey = json_encode(['action' => 'search', 'query' => 'php', 'page' => 1]);
$cache->set($complexKey, ['results' => [1, 2, 3]], 600);
例3: 重複検出システム
class DuplicateDetector {
private $hashes = [];
/**
* コンテンツを追加
*/
public function add($id, $content) {
$hash = sha1($content);
if (!isset($this->hashes[$hash])) {
$this->hashes[$hash] = [];
}
$this->hashes[$hash][] = $id;
return $hash;
}
/**
* 重複をチェック
*/
public function check($content) {
$hash = sha1($content);
if (isset($this->hashes[$hash])) {
return [
'is_duplicate' => true,
'hash' => $hash,
'original_ids' => $this->hashes[$hash]
];
}
return [
'is_duplicate' => false,
'hash' => $hash
];
}
/**
* 重複グループを取得
*/
public function getDuplicates() {
return array_filter($this->hashes, function($ids) {
return count($ids) > 1;
});
}
/**
* 統計情報を取得
*/
public function getStatistics() {
$duplicates = $this->getDuplicates();
return [
'total_hashes' => count($this->hashes),
'unique_content' => count($this->hashes),
'duplicate_groups' => count($duplicates),
'total_duplicates' => array_sum(array_map('count', $duplicates)) - count($duplicates)
];
}
}
// 使用例
echo "=== 重複検出 ===\n";
$detector = new DuplicateDetector();
// コンテンツを追加
$detector->add('doc1', 'This is the original content');
$detector->add('doc2', 'This is different content');
$detector->add('doc3', 'This is the original content'); // 重複
$detector->add('doc4', 'Another unique content');
$detector->add('doc5', 'This is the original content'); // 重複
// 重複チェック
$result = $detector->check('This is the original content');
if ($result['is_duplicate']) {
echo "重複が見つかりました:\n";
echo " 元のID: " . implode(', ', $result['original_ids']) . "\n";
}
echo "\n=== 重複グループ ===\n";
$duplicates = $detector->getDuplicates();
foreach ($duplicates as $hash => $ids) {
echo "ハッシュ: {$hash}\n";
echo " ID: " . implode(', ', $ids) . "\n";
}
echo "\n=== 統計 ===\n";
$stats = $detector->getStatistics();
print_r($stats);
例4: ファイル管理システム
class FileManager {
/**
* ファイルのハッシュを計算
*/
public static function getFileHash($filepath) {
if (!file_exists($filepath)) {
return null;
}
return sha1_file($filepath);
}
/**
* ファイルを比較
*/
public static function compareFiles($file1, $file2) {
$hash1 = self::getFileHash($file1);
$hash2 = self::getFileHash($file2);
if ($hash1 === null || $hash2 === null) {
return null;
}
return [
'identical' => $hash1 === $hash2,
'hash1' => $hash1,
'hash2' => $hash2
];
}
/**
* ディレクトリ内の重複ファイルを検出
*/
public static function findDuplicates($directory) {
$hashes = [];
$files = glob($directory . '/*');
foreach ($files as $file) {
if (is_file($file)) {
$hash = self::getFileHash($file);
if (!isset($hashes[$hash])) {
$hashes[$hash] = [];
}
$hashes[$hash][] = $file;
}
}
// 2つ以上のファイルがあるグループのみ返す
return array_filter($hashes, function($files) {
return count($files) > 1;
});
}
/**
* ファイルのメタデータを生成
*/
public static function generateMetadata($filepath) {
if (!file_exists($filepath)) {
return null;
}
return [
'path' => $filepath,
'filename' => basename($filepath),
'size' => filesize($filepath),
'hash' => self::getFileHash($filepath),
'modified' => filemtime($filepath),
'created' => filectime($filepath)
];
}
/**
* ファイルインデックスを作成
*/
public static function createIndex($directory) {
$index = [];
$files = glob($directory . '/*');
foreach ($files as $file) {
if (is_file($file)) {
$metadata = self::generateMetadata($file);
$index[$metadata['hash']] = $metadata;
}
}
return $index;
}
}
// 使用例
echo "=== ファイル管理 ===\n";
// テストファイルを作成
file_put_contents('/tmp/test1.txt', 'Hello World');
file_put_contents('/tmp/test2.txt', 'Hello World'); // 同じ内容
file_put_contents('/tmp/test3.txt', 'Different content');
// ファイル比較
$comparison = FileManager::compareFiles('/tmp/test1.txt', '/tmp/test2.txt');
echo "test1.txt vs test2.txt: " . ($comparison['identical'] ? '同一' : '異なる') . "\n";
// メタデータ生成
$metadata = FileManager::generateMetadata('/tmp/test1.txt');
print_r($metadata);
例5: ETags生成
class ETagGenerator {
/**
* コンテンツからETagを生成
*/
public static function generate($content) {
return sha1($content);
}
/**
* ファイルからETagを生成
*/
public static function fromFile($filepath) {
if (!file_exists($filepath)) {
return null;
}
// ファイル内容とタイムスタンプを組み合わせる
$content = file_get_contents($filepath);
$mtime = filemtime($filepath);
return sha1($content . $mtime);
}
/**
* 弱いETagを生成(W/付き)
*/
public static function generateWeak($content) {
return 'W/"' . sha1($content) . '"';
}
/**
* 強いETagを生成
*/
public static function generateStrong($content) {
return '"' . sha1($content) . '"';
}
/**
* ETagを検証
*/
public static function validate($content, $etag) {
$etag = trim($etag, '"');
$etag = preg_replace('/^W\//', '', $etag);
$currentEtag = self::generate($content);
return $etag === $currentEtag;
}
/**
* HTTPレスポンスヘッダーを設定
*/
public static function setHeader($content, $weak = false) {
$etag = $weak ?
self::generateWeak($content) :
self::generateStrong($content);
header("ETag: {$etag}");
// If-None-Matchヘッダーをチェック
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
$clientEtag = $_SERVER['HTTP_IF_NONE_MATCH'];
if (self::validate($content, $clientEtag)) {
header('HTTP/1.1 304 Not Modified');
exit;
}
}
}
}
// 使用例
echo "=== ETag生成 ===\n";
$content = "This is my content";
$etag = ETagGenerator::generate($content);
echo "ETag: {$etag}\n";
$strongEtag = ETagGenerator::generateStrong($content);
echo "強いETag: {$strongEtag}\n";
$weakEtag = ETagGenerator::generateWeak($content);
echo "弱いETag: {$weakEtag}\n";
// 検証
$isValid = ETagGenerator::validate($content, $strongEtag);
echo "検証: " . ($isValid ? 'OK' : 'NG') . "\n";
例6: セッションID生成
class SessionManager {
/**
* セッションIDを生成
*/
public static function generateId($userId, $userAgent, $ipAddress) {
$timestamp = microtime(true);
$random = random_bytes(16);
$data = $userId . $userAgent . $ipAddress . $timestamp . bin2hex($random);
return sha1($data);
}
/**
* セキュアなトークンを生成
*/
public static function generateToken($length = 40) {
$randomBytes = random_bytes(20);
$hash = sha1($randomBytes . microtime(true));
return substr($hash, 0, $length);
}
/**
* ワンタイムトークンを生成
*/
public static function generateOneTimeToken($userId, $action) {
$timestamp = time();
$random = random_bytes(16);
$data = $userId . $action . $timestamp . bin2hex($random);
return [
'token' => sha1($data),
'expires' => $timestamp + 300 // 5分有効
];
}
/**
* CSRFトークンを生成
*/
public static function generateCsrfToken($sessionId) {
$secret = 'your-secret-key'; // 実際は設定ファイルから読み込む
return sha1($sessionId . $secret . time());
}
}
// 使用例
echo "=== セッション管理 ===\n";
$userId = 123;
$userAgent = 'Mozilla/5.0...';
$ipAddress = '192.168.1.1';
$sessionId = SessionManager::generateId($userId, $userAgent, $ipAddress);
echo "セッションID: {$sessionId}\n";
$token = SessionManager::generateToken();
echo "トークン: {$token}\n";
$oneTimeToken = SessionManager::generateOneTimeToken($userId, 'reset-password');
echo "ワンタイムトークン: {$oneTimeToken['token']}\n";
echo "有効期限: " . date('Y-m-d H:i:s', $oneTimeToken['expires']) . "\n";
$csrfToken = SessionManager::generateCsrfToken($sessionId);
echo "CSRFトークン: {$csrfToken}\n";
例7: データ署名
class DataSigner {
private $secret;
public function __construct($secret) {
$this->secret = $secret;
}
/**
* データに署名
*/
public function sign($data) {
$serialized = is_string($data) ? $data : json_encode($data);
$signature = sha1($serialized . $this->secret);
return [
'data' => $data,
'signature' => $signature
];
}
/**
* 署名を検証
*/
public function verify($signedData) {
if (!isset($signedData['data']) || !isset($signedData['signature'])) {
return false;
}
$serialized = is_string($signedData['data']) ?
$signedData['data'] :
json_encode($signedData['data']);
$expectedSignature = sha1($serialized . $this->secret);
return hash_equals($expectedSignature, $signedData['signature']);
}
/**
* 署名付きURLを生成
*/
public function signUrl($url, $expiresIn = 3600) {
$expires = time() + $expiresIn;
$signature = sha1($url . $expires . $this->secret);
$separator = strpos($url, '?') !== false ? '&' : '?';
return $url . $separator . "expires={$expires}&signature={$signature}";
}
/**
* 署名付きURLを検証
*/
public function verifyUrl($signedUrl) {
$parts = parse_url($signedUrl);
if (!isset($parts['query'])) {
return false;
}
parse_str($parts['query'], $params);
if (!isset($params['expires']) || !isset($params['signature'])) {
return false;
}
// 有効期限チェック
if (time() > $params['expires']) {
return false;
}
// 署名を再構築
unset($params['signature']);
$baseUrl = $parts['scheme'] . '://' . $parts['host'] .
($parts['path'] ?? '') . '?' . http_build_query($params);
$expectedSignature = sha1($baseUrl . $params['expires'] . $this->secret);
return hash_equals($expectedSignature, $_GET['signature'] ?? '');
}
}
// 使用例
echo "=== データ署名 ===\n";
$signer = new DataSigner('my-secret-key');
// データに署名
$data = ['user_id' => 123, 'action' => 'delete'];
$signed = $signer->sign($data);
print_r($signed);
// 署名を検証
$isValid = $signer->verify($signed);
echo "\n署名検証: " . ($isValid ? 'OK' : 'NG') . "\n";
// データを改ざん
$tampered = $signed;
$tampered['data']['user_id'] = 999;
$isValid = $signer->verify($tampered);
echo "改ざんデータ: " . ($isValid ? 'OK' : 'NG') . "\n";
// 署名付きURL
echo "\n=== 署名付きURL ===\n";
$url = 'https://example.com/download/file.pdf';
$signedUrl = $signer->signUrl($url, 3600);
echo "署名付きURL:\n{$signedUrl}\n";
md5()との比較
$text = "Hello World";
// MD5(128ビット、32文字)
$md5 = md5($text);
echo "MD5: {$md5}\n";
echo "長さ: " . strlen($md5) . "\n";
// SHA-1(160ビット、40文字)
$sha1 = sha1($text);
echo "SHA-1: {$sha1}\n";
echo "長さ: " . strlen($sha1) . "\n";
// SHA-256(より安全)
$sha256 = hash('sha256', $text);
echo "SHA-256: {$sha256}\n";
echo "長さ: " . strlen($sha256) . "\n";
セキュリティ上の注意
// ⚠️ SHA-1はパスワードハッシュには使用しない
// 悪い例
$badPasswordHash = sha1('password123');
// 良い例: password_hash()を使用
$goodPasswordHash = password_hash('password123', PASSWORD_DEFAULT);
// ⚠️ SHA-1は暗号学的に脆弱
// セキュリティが重要な用途ではSHA-256以上を使用
$secureHash = hash('sha256', $data);
// または
$secureHash = hash('sha512', $data);
まとめ
sha1()関数の特徴をまとめると:
できること:
- 160ビット(40文字)のハッシュ生成
- データの整合性確認
- ユニークIDの生成
- 重複検出
推奨される使用場面:
- ファイル整合性チェック
- キャッシュキー生成
- ETag生成
- 重複検出
- 非暗号化用途の識別子生成
利点:
- MD5より衝突耐性が高い
- 高速
- 広くサポートされている
セキュリティ上の注意:
- パスワードには使用しない
- 暗号学的に脆弱(衝突攻撃が可能)
- セキュリティ用途にはSHA-256以上を推奨
関連関数:
md5(): MD5ハッシュ(より脆弱)hash(): 様々なハッシュアルゴリズムpassword_hash(): パスワード専用sha1_file(): ファイルのSHA-1ハッシュ
使い分け:
// ファイル整合性、キャッシュキー: SHA-1でOK
$cacheKey = sha1($data);
// セキュリティ重要: SHA-256以上
$signature = hash('sha256', $data);
// パスワード: password_hash()
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
sha1()は、整合性確認や識別子生成には便利ですが、セキュリティが重要な用途では使用を避け、より強力なハッシュアルゴリズムを選択しましょう!
