랜덤값 생성 시 중복이나 예측 가능한 값이 생성되면 보안 문제나 데이터 무결성 문제가 발생할 수 있습니다. 안전하고 고유한 랜덤값을 생성하는 방법들을 알아보겠습니다.
<?php
function generateSecureRandomString($length = 32) {
// PHP 7.0+ random_bytes 사용
$bytes = random_bytes($length);
return bin2hex($bytes);
}
function generateRandomToken($length = 16) {
// base64 인코딩으로 더 짧은 문자열
$bytes = random_bytes($length);
return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '=');
}
// 사용 예시
echo "보안 문자열: " . generateSecureRandomString(16) . "\n";
echo "토큰: " . generateRandomToken(12) . "\n";
?>
<?php
function generateUUID() {
$data = random_bytes(16);
// 버전과 variant 비트 설정
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // version 4
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // variant bits
return sprintf('%08s-%04s-%04s-%04s-%12s',
bin2hex(substr($data, 0, 4)),
bin2hex(substr($data, 4, 2)),
bin2hex(substr($data, 6, 2)),
bin2hex(substr($data, 8, 2)),
bin2hex(substr($data, 10, 6))
);
}
function generateShortUUID() {
// 더 짧은 고유 ID (22자)
return rtrim(strtr(base64_encode(random_bytes(16)), '+/', '-_'), '=');
}
echo "UUID: " . generateUUID() . "\n";
echo "Short UUID: " . generateShortUUID() . "\n";
?>
<?php
function generateUniqueId($pdo, $table, $column, $length = 8) {
$max_attempts = 10;
$attempt = 0;
do {
$id = generateRandomToken($length);
$stmt = $pdo->prepare("SELECT COUNT(*) FROM {$table} WHERE {$column} = ?");
$stmt->execute([$id]);
$exists = $stmt->fetchColumn() > 0;
$attempt++;
if ($attempt >= $max_attempts) {
throw new Exception("고유 ID 생성 실패: 최대 시도 횟수 초과");
}
} while ($exists);
return $id;
}
// 사용 예시
try {
$pdo = new PDO($dsn, $username, $password);
$unique_code = generateUniqueId($pdo, 'users', 'user_code', 10);
echo "고유 코드: " . $unique_code . "\n";
} catch (Exception $e) {
echo "오류: " . $e->getMessage() . "\n";
}
?>
<?php
function generateTimeBasedId() {
// 마이크로초 포함 타임스탬프 + 랜덤값
$timestamp = microtime(true);
$random = random_bytes(4);
return base_convert($timestamp * 1000000, 10, 36) . bin2hex($random);
}
function generateSnowflakeId() {
// Twitter Snowflake 방식 (간단화)
static $sequence = 0;
static $last_timestamp = 0;
$timestamp = round(microtime(true) * 1000);
if ($timestamp === $last_timestamp) {
$sequence = ($sequence + 1) & 0xFFF; // 12비트 시퀀스
if ($sequence === 0) {
// 같은 밀리초에 시퀀스 오버플로우 시 대기
usleep(1000);
$timestamp = round(microtime(true) * 1000);
}
} else {
$sequence = 0;
}
$last_timestamp = $timestamp;
// 타임스탬프(41비트) + 머신ID(10비트) + 시퀀스(12비트)
$machine_id = 1; // 실제로는 서버별 고유값
$id = ($timestamp << 22) | ($machine_id << 12) | $sequence;
return $id;
}
echo "시간 기반 ID: " . generateTimeBasedId() . "\n";
echo "Snowflake ID: " . generateSnowflakeId() . "\n";
?>
<?php
class RandomIdGenerator {
private $pdo;
private $charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
public function __construct($pdo = null) {
$this->pdo = $pdo;
}
public function generateCode($length = 8, $exclude_similar = true) {
$charset = $this->charset;
if ($exclude_similar) {
// 혼동하기 쉬운 문자 제외
$charset = str_replace(['0', 'O', '1', 'I'], '', $charset);
}
$code = '';
$max = strlen($charset) - 1;
for ($i = 0; $i < $length; $i++) {
$code .= $charset[random_int(0, $max)];
}
return $code;
}
public function generateUniqueCode($table, $column, $length = 8, $max_attempts = 20) {
if (!$this->pdo) {
throw new Exception("데이터베이스 연결이 필요합니다");
}
for ($i = 0; $i < $max_attempts; $i++) {
$code = $this->generateCode($length);
if (!$this->codeExists($table, $column, $code)) {
return $code;
}
}
throw new Exception("고유 코드 생성 실패");
}
private function codeExists($table, $column, $code) {
$stmt = $this->pdo->prepare("SELECT 1 FROM {$table} WHERE {$column} = ? LIMIT 1");
$stmt->execute([$code]);
return $stmt->fetchColumn() !== false;
}
public function generateNumericId($length = 6) {
$min = pow(10, $length - 1);
$max = pow(10, $length) - 1;
return random_int($min, $max);
}
}
// 사용 예시
$generator = new RandomIdGenerator($pdo);
echo "코드: " . $generator->generateCode(8) . "\n";
echo "숫자 ID: " . $generator->generateNumericId(6) . "\n";
?>
<?php
function generateSessionToken() {
// 32바이트 = 256비트 보안 강도
return bin2hex(random_bytes(32));
}
function generateCSRFToken() {
if (!isset($_SESSION)) {
session_start();
}
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;
$_SESSION['csrf_time'] = time();
return $token;
}
function validateCSRFToken($token) {
if (!isset($_SESSION)) {
session_start();
}
// 토큰 존재 확인
if (!isset($_SESSION['csrf_token']) || !isset($_SESSION['csrf_time'])) {
return false;
}
// 시간 만료 확인 (1시간)
if (time() - $_SESSION['csrf_time'] > 3600) {
unset($_SESSION['csrf_token'], $_SESSION['csrf_time']);
return false;
}
// 토큰 일치 확인 (타이밍 공격 방지)
return hash_equals($_SESSION['csrf_token'], $token);
}
// 사용 예시
$session_token = generateSessionToken();
$csrf_token = generateCSRFToken();
echo "세션 토큰: " . $session_token . "\n";
echo "CSRF 토큰: " . $csrf_token . "\n";
?>
<?php
function generateRandomFilename($original_filename, $preserve_extension = true) {
$random_name = bin2hex(random_bytes(16));
if ($preserve_extension && pathinfo($original_filename, PATHINFO_EXTENSION)) {
$extension = pathinfo($original_filename, PATHINFO_EXTENSION);
return $random_name . '.' . $extension;
}
return $random_name;
}
function generateSafeFilename($original_filename) {
// 원본 파일명 일부 + 타임스탬프 + 랜덤값
$name = pathinfo($original_filename, PATHINFO_FILENAME);
$extension = pathinfo($original_filename, PATHINFO_EXTENSION);
// 파일명 정리 (특수문자 제거)
$safe_name = preg_replace('/[^a-zA-Z0-9_-]/', '', $name);
$safe_name = substr($safe_name, 0, 20); // 길이 제한
$timestamp = date('YmdHis');
$random = bin2hex(random_bytes(4));
return $safe_name . '_' . $timestamp . '_' . $random . '.' . $extension;
}
// 사용 예시
$original = "사용자업로드파일.jpg";
echo "랜덤 파일명: " . generateRandomFilename($original) . "\n";
echo "안전한 파일명: " . generateSafeFilename($original) . "\n";
?>
<?php
function testRandomQuality($generator_func, $count = 10000) {
$values = [];
$duplicates = 0;
for ($i = 0; $i < $count; $i++) {
$value = $generator_func();
if (isset($values[$value])) {
$duplicates++;
} else {
$values[$value] = true;
}
}
$unique_rate = (($count - $duplicates) / $count) * 100;
echo "테스트 결과:\n";
echo "- 총 생성: {$count}개\n";
echo "- 중복: {$duplicates}개\n";
echo "- 고유율: " . number_format($unique_rate, 2) . "%\n";
return $unique_rate > 99.9; // 99.9% 이상이면 양호
}
// 테스트 실행
echo "8자리 코드 테스트:\n";
testRandomQuality(function() {
return bin2hex(random_bytes(4));
});
echo "\nUUID 테스트:\n";
testRandomQuality(function() {
return generateUUID();
});
?>