1. 개념 설명: 단방향 vs 양방향 암호화
단방향 암호화란 한 번 암호화하면 복호화(원래대로 되돌리는 것)가 불가능한 방식입니다
대표적으로 해시(hash) 함수가 이에 해당하며, 임의 길이의 입력 데이터를 고정된 길이의 해시 값으로 변환합니다. 해시 값만으로는 원본 데이터를 추측하거나 복원할 수 없으며, 이러한 특성 때문에 주로 데이터의 무결성(integrity) 확인에 사용됩니다
예를 들어, 파일 다운로드 후 제공된 해시값과 다운로드한 파일의 해시값을 비교하여 데이터 위변조 여부를 검사할 수 있습니다
양방향 암호화는 암호화된 데이터를 키(Key)를 사용해 다시 복호화할 수 있는 방식입니다
즉, 암호화와 복호화를 모두 지원하며 데이터의 기밀성(confidentiality) 보호에 초점을 둡니다.
양방향 암호화에는 대칭키 암호화와 비대칭키 암호화 두 유형이 있는데, 대칭키 방식은 하나의 비밀 키로 암호화와 복호화를 모두 수행하고(예: AES) 키가 유출되면 보안에 치명적인 문제가 생길 수 있습니다
비대칭키 방식은 쌍으로 된 공개키/개인키를 사용하는 방식으로, 공개키로 암호화한 데이터는 대응되는 개인키로만 복호화할 수 있습니다 (예: RSA)
대칭키에 비해 비대칭키는 키 관리가 용이하고 키 교환 문제를 완화하지만, 연산 속도가 느린 단점이 있습니다.
2. 보안적 측면에서의 차이점
단방향 암호화 사용 사례 : 사용자 비밀번호 저장에 활용됩니다.
시스템은 비밀번호 원문 대신 해시값만 저장하여, 유출 시에도 원문 비밀번호를 알기 어렵도록 합니다. 실제로 웹 서비스에서 비밀번호를 분실했을 때 원문을 알려주지 않고 재설정하도록 하는데, 이는 복호화가 불가능한 단방향 해시로 비밀번호를 관리하기 때문입니다
단방향 암호화는 입력이 같으면 항상 동일한 해시가 나오므로, 공격자가 미리 해시 테이블(레인보우 테이블)을 준비해두면 일치하는 해시를 찾아 역으로 비밀번호를 알아낼 위험이 있습니다
이러한 공격을 막기 위해 해시에는 솔트(salt) 등 추가 데이터를 첨가하여 동일한 비밀번호라도 서로 다른 해시값이 나오게 만들어 보완합니다
양방향 암호화 사용 사례 : 주민등록번호, 신용카드 번호와 같이 복구가 필요한 개인정보 저장에 사용됩니다.
이러한 데이터는 업무상 원본을 다시 조회하거나 사용할 필요가 있으므로, 해시가 아닌 양방향 암호화를 적용합니다. 예를 들어, 대한민국 개인정보 보호법에서는 비밀번호는 일방향(단방향) 암호화로 저장하고, 주민등록번호 등의 고유식별정보나 신용카드번호는 양방향 암호화(복호화 가능한 안전한 알고리즘)로 저장하도록 요구하고 있습니다
양방향 암호화된 데이터는 적절한 키를 가지고 있으면 원본을 복원할 수 있기 때문에, 복구 가능성이 있다는 장점으로 필요한 경우 원본 데이터를 활용할 수 있습니다.
복구 가능성 비교 : 단방향 암호화는 설계상 원본 데이터 복원이 불가능합니다.
해시 함수를 통해 저장된 값으로부터는 수학적으로 원래 입력을 직접 얻을 수 없으며, 비밀번호처럼 절대로 평문이 유출되어서는 안 되는 데이터에 적합합니다
반면 양방향 암호화는 올바른 키를 알고 있다면 복호화가 가능하므로, 인가된 사용자나 시스템은 언제든 암호문을 풀어 원 데이터를 얻을 수 있습니다. 따라서 단방향 방식으로 저장된 정보(예: 해시된 비밀번호)는 분실 시에도 복구가 불가하지만, 양방향으로 암호화된 정보는 키만 확보하면 원본 복구가 가능합니다.
공격 가능성 비교
단방향 암호화된 데이터에 대한 공격은 주로 해시 충돌이나 사전 공격(dictionary attack), 브루트포스 등에 의존합니다.
해커는 추측한 입력값을 해시하여 데이터베이스의 해시와 비교하는 방식으로 원본을 알아내려고 시도하며, 앞서 언급한 레인보우 테이블도 이러한 사전 공격의 한 예입니다. 하지만 충분히 강력한 해시 알고리즘을 사용하고 솔트 및 키 스트레칭(반복 연산, 예: PBKDF2)을 적용하면 역추측을 매우 어렵게 만들어 공격 위험을 낮출 수 있습니다. 양방향 암호화의 경우 키 관리가 가장 큰 보안 요소입니다. 암호화 키가 유출되면 공격자는 암호문을 바로 복호화하여 모든 데이터를 탈취할 수 있으므로, 키를 안전한 HSM에 저장하거나 권한 있는 사용자만 접근하도록 관리해야 합니다. 또한 양방향 암호화 알고리즘 자체의 취약점(예를 들어, 취약한 암호화 방식이나 짧은 키 길이 사용)은 곧바로 원본 노출로 이어질 수 있으므로 최신의 안전한 알고리즘과 충분한 키 길이를 사용하는 것이 중요합니다
3. Java를 활용한 암호화 방법
자바에서는 java.security 및 javax.crypto 패키지를 통해 해시 함수와 양방향 암호화를 구현할 수 있습니다.
단방향 암호화 예제 (SHA-256 해시)
SHA-256 알고리즘을 이용해 문자열을 해시하는 코드입니다. 보안 강화를 위해 해시에 salt를 추가하는 예시를 포함합니다 (salt는 랜덤 바이트로 생성하여 원문에 붙인 후 해시합니다).
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class HashExample {
public static void main(String[] args) throws NoSuchAlgorithmException {
String password = "SecurePassword123";
// 1. Salt 생성 (16바이트 랜덤 값)
SecureRandom rand = new SecureRandom();
byte[] salt = new byte[16];
rand.nextBytes(salt);
System.out.println("Salt: " + Arrays.toString(salt));
// 2. SHA-256 해시 객체 생성
MessageDigest md = MessageDigest.getInstance("SHA-256");
// 3. 입력 문자열과 salt를 결합하여 해시 계산
md.update(password.getBytes());
md.update(salt);
byte[] hashBytes = md.digest();
// 4. 해시값을 16진수 문자열로 변환
StringBuilder sb = new StringBuilder();
for (byte b : hashBytes) {
sb.append(String.format("%02x", b));
}
String hashHex = sb.toString();
System.out.println("SHA-256 해시값: " + hashHex);
}
}
위 코드에서는 "SecurePassword123" 문자열에 대해 SHA-256 해시를 계산합니다. MessageDigest를 사용하여 해시를 수행하며, salt로 추가된 바이트 배열을 함께 해시함으로써 동일한 비밀번호에 대해서도 다른 해시 결과가 나오도록 합니다. 출력된 해시 문자열(16진수 형태)은 원본 입력을 알아볼 수 없게 한 방향으로 암호화된 결과입니다. 실제 비밀번호를 데이터베이스에 저장할 때는 생성한 salt도 함께 보관하여, 나중에 사용자가 로그인 시 입력한 비밀번호를 같은 방식으로 해시한 후 비교합니다. 추가로, bcrypt나 PBKDF2 같은 함수는 내부적으로 솔트와 키 스트레칭을 적용하여 해시를 반복 연산함으로써, 무차별 대입 공격에 대한 저항성을 높여줍니다 (Java에서는 Spring Security의 BCryptPasswordEncoder 혹은 javax.crypto.SecretKeyFactory를 사용한 PBKDF2 구현 등으로 활용 가능).
양방향 암호화 예제 (대칭키 AES 및 비대칭키 RSA)
다음으로 양방향 암호화의 예제로, 대표적인 대칭키 알고리즘인 AES와 비대칭키 알고리즘인 RSA를 이용한 암호화/복호화 코드를 보여드립니다.
AES 대칭키 암호화/복호화 예제: AES-128 비트 키를 사용하여 문자열을 암호화한 후 다시 복호화하는 코드입니다. 대칭키 방식이므로 암호화와 복호화에 동일한 비밀 키를 사용합니다.
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
String plainText = "Hello Java Encryption";
String key = "0123456789ABCDEF"; // 16-byte (128-bit) secret key
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
// AES 알고리즘으로 암호화
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes("UTF-8"));
String cipherTextBase64 = Base64.getEncoder().encodeToString(encryptedBytes);
System.out.println("AES로 암호화된 결과: " + cipherTextBase64);
// 동일한 키로 복호화
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decodedBytes = Base64.getDecoder().decode(cipherTextBase64);
byte[] decryptedBytes = cipher.doFinal(decodedBytes);
String decryptedText = new String(decryptedBytes, "UTF-8");
System.out.println("AES로 복호화한 결과: " + decryptedText);
위 AES 예제에서는 "0123456789ABCDEF"라는 16바이트 키를 사용하여 평문 "Hello Java Encryption"을 암호화합니다. Cipher.getInstance("AES/ECB/PKCS5Padding")은 AES 알고리즘을 ECB 모드로 동작시키며 패딩은 PKCS5를 사용한다는 의미입니다. 암호화 결과는 바이너리 데이터이므로 출력 편의를 위해 Base64 인코딩으로 문자열화하였고, 복호화 시에는 다시 Base64 디코딩하여 원문을 얻습니다. 최종적으로 decryptedText가 초기의 평문과 같은지 확인하면, 암호화-복호화 과정이 올바르게 수행된 것을 알 수 있습니다.
RSA 비대칭키 암호화/복호화 예제: RSA 알고리즘으로 키 쌍을 생성하고, 공개키로 암호화한 데이터를 개인키로 복호화하는 코드입니다. RSA는 대칭키에 비해 연산이 복잡하지만 키 교환이 필요 없고, 공개키를 자유롭게 배포할 수 있는 장점이 있습니다.
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.PrivateKey;
import javax.crypto.Cipher;
// 1. RSA 키 쌍 생성 (2048비트)
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keyPair = keyGen.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 2. 공개키로 암호화
String secretMessage = "Top Secret Data";
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedData = rsaCipher.doFinal(secretMessage.getBytes("UTF-8"));
// 3. 개인키로 복호화
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedData = rsaCipher.doFinal(encryptedData);
String result = new String(decryptedData, "UTF-8");
System.out.println("RSA 복호화 결과: " + result);
이 RSA 예제에서는 먼저 KeyPairGenerator를 통해 2048비트 RSA 키 쌍을 생성합니다. 생성된 publicKey로 암호화하고, 대응하는 privateKey로만 복호화할 수 있습니다. Cipher.getInstance("RSA/ECB/PKCS1Padding")을 사용하여 RSA 암호화를 수행하였으며, PKCS#1 v1.5 패딩을 적용하였습니다. 공개키로 "Top Secret Data"를 암호화한 후, 개인키로 복호화하여 result 문자열이 원본과 같은지 출력으로 확인합니다. RSA와 같은 비대칭키 암호화는 인터넷상에서 키 교환 없이도 안전하게 데이터 공유가 가능하며, 대칭키 암호화에 비해 키 관리 측면의 보안성을 높여줍니다
위와 같이, 단방향 암호화와 양방향 암호화는 그 목적과 방식에 차이가 있으며 상황에 맞게 사용됩니다. 비밀번호처럼 절대 복구될 필요가 없는 데이터는 안전한 해시 알고리즘으로 저장하고, 추후 사용을 위해 원본이 필요한 개인정보는 강력한 양방향 암호화 알고리즘과 철저한 키 관리 하에 보관하는 것이 보안상 최선의 방법입니다.
📌 단방향 암호화 vs 양방향 암호화 비교 정리
항목 | 단방향 암호화 (One-way Encryption) | 양방향 암호화 (Two-way Encryption) |
정의 | 암호화 후 복호화가 불가능한 방식 | 암호화 후 복호화가 가능한 방식 |
목적 | 데이터 무결성(Integrity) 유지 | 데이터 기밀성(Confidentiality) 보장 |
암호화 방식 | 해시 함수 (Hash Function) 사용 | 대칭키 암호화 또는 비대칭키 암호화 |
사용 알고리즘 | SHA-256, SHA-512, bcrypt, PBKDF2 등 | AES(대칭키), RSA(비대칭키) 등 |
복호화 가능 여부 | ❌ 복호화 불가능 | ✅ 복호화 가능 (키 필요) |
보안성 | 비밀번호 유출 방지, 원본 데이터 복구 불가능 | 키 관리가 중요, 키 유출 시 데이터 해독 가능 |
사용 사례 | - 비밀번호 저장 (ex: 웹사이트 로그인) - 데이터 무결성 검증 (ex: 파일 해시) |
- 주민등록번호, 신용카드 정보 저장 - 안전한 데이터 전송 (ex: HTTPS, 메신저) |
공격 가능성 | 레인보우 테이블 공격, 브루트포스 공격 | 키 유출 시 복호화 가능, 키 길이 짧으면 취약 |
주요 단점 | 원본 데이터 복원이 불가능하여 필요 시 재입력해야 함 | 암호화 키가 유출되면 보안이 취약해짐 |
보안 강화 기법 | 솔트(Salt), 키 스트레칭(PBKDF2, bcrypt) | 키 관리 강화 (HSM, KMS), 충분한 키 길이 사용 |
🔹 단방향 암호화: 비밀번호와 같이 복구할 필요가 없는 데이터에 적합
🔹 양방향 암호화: 개인정보(주민번호, 카드번호) 등 나중에 복호화가 필요한 데이터에 적합
💡 결론:
- 단방향 암호화는 원본 복원이 불가능하여 비밀번호 저장 등에 사용
- 양방향 암호화는 원본이 필요할 수 있는 주민등록번호, 신용카드 정보 보호에 사용
- 보안 강화를 위해서는 적절한 암호화 알고리즘과 보안 기법(솔트, 키 관리 등)을 적용해야 함 ✅
연관된 글 :
참고:
'개발' 카테고리의 다른 글
[DB] Oracle rownum vs mysql limit (0) | 2024.09.03 |
---|