서버 운영 중 AWS에서 Aurora MySQL 2버전에서의 지원이 2024.10.31 이후로 지원이 종료된다는 공지를 받았다.
이에 따라 3버전으로 Aurora MySQL을 업데이트해야 하는데, 기존에 서비스에서 사용하던 MySQL 버전은 5.6 버전이었고, 안 그래도 조만간 버전 업데이트가 필요하다고 생각했다.
다른 서비스에는 문제가 없었으나, 마이크로 서비스로 내부 관리자 사이트가 자바로 되어 있는데 이중 계정 테이블의 password 컬럼의 암호화 방식이 MySQL의 PASSWORD() 함수였다.
PASSWORD()함수는 이전부터 암호화 방식에 문제가 있어 다른 암호화 방식으로 변경할 것을 권고하였고, 8.0.11 버전부터는 사용할 수 없다고 한다.
PASSWORD()를 대체할 함수로 여러가지가 있는데, 그중 하나가 SHA2 함수다.
이름처럼 SHA-2 알고리즘을 지원하며, 여느 해시 함수가 그렇듯 해시 길이에 따라 보안성이 강화되는 특징이 있다.
SHA-256, SHA-384, SHA-512 등의 다양한 해시 길이가 있는데, 어드민 사이트는 내부 VPN에 접속하여야 로그인이 가능하도록 되어 있어 SHA-256으로도 충분할
기존 password() 함수로 사용하던 암호화 방식을 SHA2 방식으로 마이그레이션이 필요하다.
다만, 아무리 검색해봐도 마이그레이션 하는 방법이 없고, 최종적으로 GPT에게도 물어봤는데.. 별도 방법은 없다고 한다.
어드민 사이트는 사내 직원들만 사용하는 사이트이기에 일괄적으로 패스워드를 변경 후 공지한 뒤에, 패스워드 변경이 필요한 경우 직원들에게 직접 기존 기능을 통해 패스워드 변경을 요청하는 식으로 넘어갔으나,
만약 일반 유저들이 이용하는 사이트가 변경되었다면 다음 방법 중 하나를 사용해야 할 것 같다.
가장 일반적이고 깔끔한 방법이다. 하지만 기존 패스워드와 일치하는지 확인하기 위해서는 사용자가 입력한 비밀번호를 password() 함수로 확인한 후, sha2() 함수로 비교해야 하는데, MySQL 최신 버전에서는 password() 함수 자체가 없기 때문에 이 경우에는 별도로 구현이 필요하다. 참고로, 아래는 password() 함수의 구현 버전이다.
function mysqlPasswordHash($password) {
// MySQL 4.1 이후 PASSWORD() 함수의 해시 방식 구현 (41자 해시)
return '*' . strtoupper(sha1(sha1($password, true)));
}
그다음에는 다음의 간단한 절차로 진행하면 된다.
1. 유저 테이블의 패스워드 변경 일자를 저장하는 컬럼 추가
2. 사용자가 로그인
3. MySQL 버전 업데이트 시간보다 이전이라면 password() -> sha2 버전으로 패스워드 변경
환경에 따라 이 방법도 쓸만할 것 같다.
각 사이트의 비밀번호 규칙에 맞는 패스워드를 임의로 생성 및 변경한 후, 이메일 등으로 안내하는 방법이다.
사용자 입장에서는 가장 좋은 방법이나, password() 함수는 디코딩이 불가능하기에 사용자의 패스워드를 다 알아낸 후 변경하는 방법이라 거의 일반적으로 시도하지 않을 법한 방법 뿐이다..
규모가 있는 사이트에서는 사실상 불가능한 방법이지만, 코드는 한번 드리도록 하겠다.
function mysqlPasswordHash($password) {
return '*' . strtoupper(sha1(sha1($password, true)));
}
function generatePossiblePasswords($length) {
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$characterLength = strlen($characters);
$passwords = [];
// 모든 조합 생성 (예를 들어, 길이가 3인 경우)
for ($i = 0; $i < pow($characterLength, $length); $i++) {
$password = '';
$number = $i;
for ($j = 0; $j < $length; $j++) {
$password = $characters[$number % $characterLength] . $password;
$number = floor($number / $characterLength);
}
$passwords[] = $password;
}
return $passwords;
}
// 예시: 길이가 3인 모든 패스워드 조합 생성
$passwords = generatePossiblePasswords(3);
print_r($passwords);
function bruteForcePasswordRecovery($storedHash, $maxLength) {
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$characterLength = strlen($characters);
for ($length = 1; $length <= $maxLength; $length++) {
for ($i = 0; $i < pow($characterLength, $length); $i++) {
$password = '';
$number = $i;
for ($j = 0; $j < $length; $j++) {
$password = $characters[$number % $characterLength] . $password;
$number = floor($number / $characterLength);
}
// 기존 PASSWORD() 해시와 비교
if (mysqlPasswordHash($password) == $storedHash) {
return $password;
}
}
}
return null; // 일치하는 패스워드가 없으면 null 반환
}
// 예시: 기존 해시 값과 비교하여 원래 패스워드 찾기
$storedHash = "*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19"; // 예: PASSWORD('abc123') 해시 값
$recoveredPassword = bruteForcePasswordRecovery($storedHash, 3);
if ($recoveredPassword) {
echo "Recovered password: $recoveredPassword\n";
} else {
echo "Password not found\n";
}
if ($recoveredPassword) {
// SHA2-256 해시 생성
$newHash = hash('sha256', $recoveredPassword);
// 데이터베이스 업데이트 (가정: MySQL 연결 및 user_id 설정)
$connection = new mysqli("localhost", "username", "password", "database");
$user_id = 1; // 예시 사용자 ID
$updateQuery = "UPDATE users SET password = '$newHash' WHERE user_id = $user_id";
if ($connection->query($updateQuery) === TRUE) {
echo "Password updated successfully\n";
} else {
echo "Error updating password: " . $connection->error . "\n";
}
$connection->close();
}
결론은 1번 방법 추천드립니다.