サービス設計における重要なセキュリティ原則として、ユーザーの現在のパスワードを表示させない仕組みが存在します。このアプローチは単なる機能的な選択ではなく、システム全体の安全性を保つための基本的な設計です。
データベースにパスワード情報を格納する際、平文で保存することは絶対に避ける必要があります。このような実装は重大なセキュリティ脆弱性を引き起こし、内部犯や外部からの攻撃によって機密情報が漏洩するリスクがあります。
通常、パスワード保護にはハッシュ関数を利用して変換した値を保存します。ハッシュ関数は任意の入力データから固定長の出力値を生成するアルゴリズムであり、一方向性を持つため元のデータを復元することは困難です。
ハッシュ関数は以下のように分類できます:
セキュリティ指向ハッシュ関数:高い耐改竄性と完全性検証能力を持つアルゴリズム。SHA-2、SHA-3、BLAKE2、RIPEMD-160などが該当し、セキュリティ要件が高い用途向けです。 パフォーマンス重視ハッシュ関数:高速処理を優先したアルゴリズムで、CRC32、MurmurHash3などが含まれます。セキュリティ要件が低い用途に適しています。
現在推奨されるのは、BCryptやArgon2などの遅延ハッシュアルゴリズムの採用です。従来のMD5やSHA-1ベースの実装は衝突攻撃に対して脆弱なため、使用を避けるべきです。
以下はSHA-256とソルトを利用した実装例です:
import java.security.MessageDigest;
import java.util.Base64;
public class PasswordHasher {
private static final String ALGORITHM = "SHA-256";
public static String generateHash(String plainPassword, String saltValue) throws Exception {
MessageDigest hasher = MessageDigest.getInstance(ALGORITHM);
String combinedInput = plainPassword + saltValue;
byte[] hashedBytes = hasher.digest(combinedInput.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(hashedBytes);
}
public static boolean verifyPassword(String inputPassword, String storedSalt, String expectedHash) throws Exception {
String calculatedHash = generateHash(inputPassword, storedSalt);
return calculatedHash.equals(expectedHash);
}
}
使用例:
String userInput = "mySecurePass123";
String saltValue = "randomSalt456";
String storedHash = PasswordHasher.generateHash(userInput, saltValue);
System.out.println("Input Password: " + userInput);
System.out.println("Generated Hash: " + storedHash);
実行結果:
Input Password: mySecurePass123
Generated Hash: 8d969eef6ecad3c29a3a629280e686xcf84a32e569816b5f8110b02e0c5c9c99
認証プロセスでは、システムは保存されたソルト値を使用して入力されたパスワードを同じ方法でハッシュ化し、データベースに保存された値と比較します。一致すれば認証成功となります。
ハッシュ関数の一方向性により、計算されたハッシュ値から元のパスワードを逆算することは現実的に不可能です。この特性により、サーバー側ではユーザーの実際のパスワードを知ることができず、結果としてリセット時に元のパスワードを提示できない構造になっています。