-
Salt 암호화란?
Salt 암호화는 비밀번호 해싱(Password Hashing) 과정에서 추가적인 보안 강화를 위해 임의의 데이터(Salt) 를 추가하는 기법입니다. 단순 해싱만 수행하면 동일한 비밀번호는 항상 같은 해시 값이 생성되지만, Salt를 사용하면 같은 비밀번호라도 서로 다른 해시 값을 만들 수 있습니다.
1. 왜 Salt가 필요한가?
비밀번호를 안전하게 저장하기 위해 해시 함수를 사용하지만, 단순 해싱에는 몇 가지 보안 문제가 있습니다.
(1) 무차별 대입 공격(Brute Force Attack)
- 공격자가 가능한 모든 비밀번호를 해싱하여 미리 저장한 후, 이 값을 비교하여 비밀번호를 찾는 방법.
- 해시 함수만 사용하면 공격자는 사전(Dictionary) 공격을 통해 쉽게 비밀번호를 알아낼 수 있음.
(2) 레인보우 테이블 공격(Rainbow Table Attack)
- 해시 값을 미리 계산한 거대한 테이블을 만들어놓고, 이를 통해 비밀번호를 역추적하는 기법.
- 미리 만들어진 해시 값을 이용하므로 공격 속도가 매우 빠름.
- 같은 비밀번호라면 항상 같은 해시 값이 나오기 때문에 쉽게 크랙 가능.
해결책: Salt를 추가하여 해시 값을 다르게 만들면, 레인보우 테이블을 사용한 공격이 무력화됨.
4. Salt 사용 시 고려해야 할 사항
- Salt 길이는 충분히 길어야 함
- 최소 16바이트(128비트) 이상 추천.
- 짧은 Salt는 보안성이 낮아질 수 있음.
- Salt는 랜덤하게 생성해야 함
- 정적(Static) Salt를 사용하면 보안성이 낮아짐.
- os.urandom() 또는 secrets.token_bytes()를 사용하여 안전한 난수를 생성해야 함.
- 각 사용자마다 다른 Salt 사용
- 모든 사용자가 동일한 Salt를 사용하면 보안성이 떨어짐.
- 개별 사용자의 비밀번호마다 새로운 Salt를 생성해야 함.
- 보안성을 높이려면 KDF(PBKDF2, bcrypt, Argon2) 사용
- 단순 해싱보다 강력한 보안 제공.
- 반복 연산을 통해 공격자가 해싱을 빠르게 수행하는 것을 방지.
1. SHA-256 + Salt 적용 예시
기본적인 SHA-256 해싱과 Salt를 적용하는 방법입니다.
예제 코드 (SHA-256 + Salt)
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Base64; public class SaltedHashExample { // 랜덤 Salt 생성 public static byte[] generateSalt() { SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; // 16바이트 Salt random.nextBytes(salt); return salt; } // SHA-256을 사용한 비밀번호 해싱 public static String hashPassword(String password, byte[] salt) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(salt); // Salt 추가 byte[] hashedPassword = md.digest(password.getBytes()); // Base64로 인코딩하여 문자열로 변환 return Base64.getEncoder().encodeToString(hashedPassword); } public static void main(String[] args) throws NoSuchAlgorithmException { String password = "securepassword"; // Salt 생성 byte[] salt = generateSalt(); System.out.println("Salt: " + Base64.getEncoder().encodeToString(salt)); // 비밀번호 해싱 String hashedPassword = hashPassword(password, salt); System.out.println("Hashed Password: " + hashedPassword); } }- SecureRandom을 사용해 16바이트 길이의 Salt를 생성.
- MessageDigest를 활용해 SHA-256 해시 알고리즘을 적용.
- Base64.getEncoder().encodeToString()을 사용해 해시 값을 문자열로 변환.
하지만! SHA-256은 반복 연산이 없기 때문에 해킹이 쉬운 편입니다.
PBKDF2 또는 BCrypt 같은 더 강력한 알고리즘을 사용하는 것이 좋습니다.2. PBKDF2 적용 예시
PBKDF2는 100,000번 이상의 반복 연산을 통해 보안을 강화하는 KDF(Key Derivation Function)입니다.
예제 코드 (PBKDF2 + Salt)
import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.util.Base64; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; public class PBKDF2Example { // 랜덤 Salt 생성 public static byte[] generateSalt() { SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; // 16바이트 Salt random.nextBytes(salt); return salt; } // PBKDF2를 사용한 비밀번호 해싱 public static String hashPassword(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException { int iterations = 100000; // 반복 횟수 (보안성 ↑) int keyLength = 256; // 256비트 해시 생성 PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keyLength); SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); byte[] hash = skf.generateSecret(spec).getEncoded(); return Base64.getEncoder().encodeToString(hash); } public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException { String password = "securepassword"; // Salt 생성 byte[] salt = generateSalt(); System.out.println("Salt: " + Base64.getEncoder().encodeToString(salt)); // 비밀번호 해싱 String hashedPassword = hashPassword(password, salt); System.out.println("Hashed Password: " + hashedPassword); } }- PBKDF2WithHmacSHA256을 사용하여 100,000번 반복 연산을 수행.
- SecretKeyFactory를 활용해 비밀번호 해싱.
- Base64.getEncoder().encodeToString()을 사용해 해시 값을 문자열로 변환.
PBKDF2는 보안성이 뛰어나지만 연산 비용이 상대적으로 높습니다.
따라서 BCrypt를 사용하는 것이 더 효과적일 수 있습니다.3. BCrypt 적용 예시
BCrypt는 내장된 Salt 자동 관리 기능을 제공하며, 가변적인 연산 비용(Work Factor) 을 지원하여 보안성이 뛰어납니다.
예제 코드 (BCrypt)
import org.mindrot.jbcrypt.BCrypt; public class BCryptExample { public static void main(String[] args) { String password = "securepassword"; // 비밀번호 해싱 (Salt 자동 포함) String hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt(12)); // Work factor = 12 System.out.println("Hashed Password: " + hashedPassword); // 비밀번호 검증 boolean isMatch = BCrypt.checkpw(password, hashedPassword); System.out.println("Password Match: " + isMatch); } }- BCrypt.gensalt(12): Salt를 자동 생성하고 Work Factor(연산 비용) 을 12로 설정.
- BCrypt.hashpw(password, salt): 자동으로 Salt를 포함한 해시 생성.
- BCrypt.checkpw(password, hashedPassword): 사용자가 입력한 비밀번호가 일치하는지 검증.
BCrypt의 장점
- Salt 자동 관리 → 따로 Salt를 저장할 필요 없음.
- Work Factor 설정 가능 → 높은 값일수록 공격이 어려워짐.
- 적응형 해싱 → 시간이 지날수록 연산 비용을 증가시켜 보안성을 유지.BCrypt는 보안성이 뛰어나기 때문에 비밀번호 해싱에 가장 추천되는 방법입니다.
'CS > 웹 보안' 카테고리의 다른 글
SSL, HTTPS, SSH (0) 2025.03.28 로그인 인증 방식 - 쿠키&세션, JWT토큰 (0) 2024.07.04