
작은 데이터 조각의 안전한 전송, 키 교환, 인증서 서명 등에 주로 사용됨
SSL/TLS 인증서에서 클라이언트와 서버 간의 키 교환 시 RSA가 사용됨
※ 한편 SSL/TLS 에서도 데이터를 암호화할때는 AES를 사용
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import org.springframework.stereotype.Service;
@Service
public class AesEncryptionService {
private static final String ALGORITHM = "AES";
// 더 구체적으로 어떤 암호화 모드와 패딩 방식을 사용할지 지정
private static final String TRANSFORMATION = "AES"; // "AES/CBC/PKCS5Padding"
private static final String CUSTOM_SECRET_KEY = "mySecretKey!!";
public SecretKey generateKey(int n) throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
keyGenerator.init(n);
return keyGenerator.generateKey();
}
public String encrypt(String plainText, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public String decrypt(String cipherText, SecretKey secretKey) throws Exception {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(cipherText));
return new String(decryptedBytes);
}
public String encryptJson(Object jsonObject, SecretKey secretKey) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
String plainText = objectMapper.writeValueAsString(jsonObject);
return encrypt(plainText, secretKey);
}
public <T> T decryptJson(String jsonObject, SecretKey key, Class<T> valueType) throws Exception {
String decryptedJson = decrypt(jsonObject, key);
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(decryptedJson, valueType);
}
public static void main(String[] args) throws Exception {
// json 포맷의 평문 생성
Map<String, Object> licenseData = new HashMap<>();
licenseData.put("companyId", "company");
licenseData.put("tenantId", new String[]{"tenant01", "tenant02"});
licenseData.put("licenseCapacity", new int[]{100, 80, 200});
licenseData.put("expiryDate", "2025-12-31");
licenseData.put("issuedBy", "shchoice");
// RSA 암호화를 위한 Secret Key 생성
AesEncryptionService service = new AesEncryptionService();
SecretKey secretKey = service.generateKey(128);
// 암호화
String encryptedText = service.encryptJson(licenseData, secretKey);
System.out.println(encryptedText);
// wg6GLFQbpj6jQhWfwViqPEddKrQ2e1Z3Ru4ibcTE8NJhKbhJ2Z6I5A8W2Yu0MfJZSeh+iEehQqMfqz0EUPh5kecDoDcWdqxPUOK51QaC4Tx+rGZi0NeG3Fmilwe3QOh/ljOwv66z9ShHD4C+5O1njhWLpqx6n1h1oADVOHYzsaoPTYI6U24qJnfG+jNQiYej
// 복호화
Map<String, Object> plainText = service.decryptJson(encryptedText, secretKey, HashMap.class);
System.out.println(plainText);
// {expiryDate=2025-12-31, companyId=KB, licenseCapacity=[100, 80, 200], issuedBy=Diquest, tenantId=[tenant01, tenant02]}
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import org.springframework.stereotype.Service;
@Service
public class RsaEncryptionService {
private static final String ALGORITHM = "RSA";
private static final int KEY_SIZE = 2048;
public KeyPair generateKeyPair(int n) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
keyPairGenerator.initialize(KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
public String encrypt(String plainText, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public String decrypt(String cipherText, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(cipherText));
return new String(decryptedBytes);
}
public static void main(String[] args) throws Exception {
// json 포맷의 평문 생성
Map<String, Object> licenseData = new HashMap<>();
licenseData.put("companyId", "company");
licenseData.put("tenantId", new String[]{"tenant01", "tenant02"});
licenseData.put("licenseCapacity", new int[]{100, 80, 200});
licenseData.put("expiryDate", "2025-12-31");
licenseData.put("issuedBy", "shchoice");
ObjectMapper mapper = new ObjectMapper();
String jsonData = mapper.writeValueAsString(licenseData);
// RSA 암호화를 위한 Public Key 및 Private Key 생성
RsaEncryptionService service = new RsaEncryptionService();
KeyPair keyPair = service.generateKeyPair(2048);
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// Public Key를 통한 암호화
String encrypted = service.encrypt(jsonData, publicKey);
System.out.println("encrypted = " + encrypted);
/* encrypted = Tw09O0BewgdUkydStdwjYoZBGnAeh8CVqc/CX5N5cdL+L1GKtUxlWkAoX96pwjhMpwjkuxYvvCUfXiDDwN9R0Wbrd5KjBk4ksJbORTREKtagyssRXc5lr9DTIxt4vpRhmfu
812DaIhVFBe6SUt6O9eRgM6bGUmHlzyHcb91Hth2uRZZFb1ZYfW7q7n5hdkunZFTfJPEXeYcBmRmtkhqu/qwMTadJe4BAU5nP+0DFVMkd5nYar3t42+7xXdOXAaxM7IosgeFUUfzp+DsThPiG3
4DGUnNZ4xHz8FMC1fyiYDoeTNXB8UjP5hPYAeJSdutI9flJSVBxE+c2rpnXSgE70Q==
*/
// Private Key를 통한 암호화
String decrypted = service.decrypt(encrypted, privateKey);
System.out.println("decrypted = " + decrypted);
// decrypted = {"expiryDate":"2025-12-31","companyId":"KB","licenseCapacity":[100,80,200],"issuedBy":"Diquest","tenantId":["tenant01","tenant02"]}
Map<String, Object> decodedLicenseData = mapper.readValue(decrypted, HashMap.class);
System.out.println("Decoded JSON = " + decodedLicenseData);
// Decoded JSON = {expiryDate=2025-12-31, companyId=KB, licenseCapacity=[100, 80, 200], issuedBy=Diquest, tenantId=[tenant01, tenant02]}
}
}
설명
장점
단점
사용 사례
코드
암호화할 때 : IV를 생성하고, 암호화된 데이터 앞에 IV를 추가하여 저장함
public String encrypt(String plainText, SecretKey key) throws Exception {
byte[] ivBytes = new byte[16];
new SecureRandom().nextBytes(ivBytes); // 새로운 IV 생성
IvParameterSpec iv = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
// IV와 암호문을 결합하여 하나의 문자열로 저장
byte[] ivAndCipherText = new byte[ivBytes.length + encryptedBytes.length];
System.arraycopy(ivBytes, 0, ivAndCipherText, 0, ivBytes.length);
System.arraycopy(encryptedBytes, 0, ivAndCipherText, ivBytes.length, encryptedBytes.length);
return Base64.getEncoder().encodeToString(ivAndCipherText);
}
복호화할 때 : 암호문에서 IV를 추출하고, 이를 사용하여 복호화
public String decrypt(String cipherText, SecretKey key) throws Exception {
byte[] ivAndCipherText = Base64.getDecoder().decode(cipherText);
// IV를 추출
byte[] ivBytes = new byte[16];
System.arraycopy(ivAndCipherText, 0, ivBytes, 0, ivBytes.length);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
// 암호문 추출
byte[] encryptedBytes = new byte[ivAndCipherText.length - ivBytes.length];
System.arraycopy(ivAndCipherText, ivBytes.length, encryptedBytes, 0, encryptedBytes.length);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes);
}
설명
장점
단점
사용 사례
코드
암호화할 때 : 새로운 IV를 생성하고, 암호화된 데이터와 함께 DB에 저장
public void saveEncryptedData(int userId, String plainText) throws Exception {
SecretKey secretKey = getFixedKey(); // 고정된 비밀키
byte[] ivBytes = new byte[16];
new SecureRandom().nextBytes(ivBytes); // 새로운 IV 생성
IvParameterSpec iv = new IvParameterSpec(ivBytes);
// 데이터 암호화
String encryptedData = encrypt(plainText, secretKey, iv);
// DB에 암호화된 데이터와 IV를 저장
String sql = "INSERT INTO user_data (user_id, encrypted_data, iv) VALUES (?, ?, ?)";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setInt(1, userId);
ps.setString(2, encryptedData);
ps.setBytes(3, ivBytes);
ps.executeUpdate();
}
}
복호화할 때 : 해당 데이터를 복호화할 때, DB에서 저장된 IV와 암호화된 데이터를 함께 불러와 복호화
public String loadDecryptedData(int userId) throws Exception {
String sql = "SELECT encrypted_data, iv FROM user_data WHERE user_id = ?";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setInt(1, userId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
String encryptedData = rs.getString("encrypted_data");
byte[] ivBytes = rs.getBytes("iv");
IvParameterSpec iv = new IvParameterSpec(ivBytes);
SecretKey secretKey = getFixedKey(); // 고정된 비밀키
return decrypt(encryptedData, secretKey, iv);
}
}
}
return null;
}
DB 테이블 구조
CREATE TABLE user_data (
user_id INT PRIMARY KEY,
encrypted_data TEXT, // 암호화된 데이터를 저장
iv VARBINARY(16) // 초기화 벡터를 저장, 16바이트 고정 길이
);
실무에서는 IV를 암호문에 포함하는 방법이 더 많이 아용되는 것으로 보임 → 암호화된 데이터와 IV를 함께 관리할 수 있어 편리하고, 복호화 과정에서 필요한 정보를 한 곳에서 쉽게 접근할 수 있기 때문으로 생각됨
IV를 별도로 저장하는 방법은 특정한 요구 사항이 있을 때나 매우 정밀한 보안 관리가 필요한 경우에 사용되지만, 관리의 복잡성 때문에 일반적으로는 덜 사용되는 것으로 생각됨
| 특성 | 해싱 | 암호화 |
|---|---|---|
| 목적 | 데이터 무결성 검증, 빠른 검색, 비밀번호 저장 | 데이터 보호 및 기밀성 유지 |
| 복호화 가능 여부 | 불가능 | 가능 |
| 일방향성 | 있음 | 없음 |
| 출력 길이 | 고정 길이 | 가변 길이 |
AES와 RSA는 각각의 장점과 단점을 가지고 있으며, 서로 상호 보완적인 암호화 기술이다. AES는 대칭 키 암호화 방식으로, 대량의 데이터를 빠르게 암호화하는 데 적합하며, 기밀성과 성능 측면에서 우수한 선택이다. 반면에, RSA는 비대칭 키 암호화 방식으로, 작은 데이터나 세션 키의 안전한 전송, 그리고 디지털 서명과 같은 신뢰성 보장이 필요한 상황에서 중요한 역할을 한다.
두 기술은 각각의 강점을 발휘하여, 현대 암호화 시스템에서 함께 사용된다. 예를 들어, SSL/TLS 프로토콜에서는 RSA를 사용하여 초기 세션 키를 안전하게 교환하고, 이후의 데이터 전송은 AES를 사용하여 빠르게 암호화하는 방식이 채택된다. 이와 같은 방식으로, AES와 RSA는 보안과 성능을 모두 만족시키는 강력한 암호화 체계를 구축하는 데 필수적인 요소이다.
따라서, AES와 RSA는 단독으로 사용되기보다는 서로 보완하며 사용될 때, 더욱 안전하고 효율적인 암호화 솔루션을 제공한다. 이 둘의 조화는 현대의 보안 시스템이 직면한 다양한 요구 사항을 충족시키는 데 중요한 역할을 한다.