Java AES256 Sample 코드.
06 Dec 2018 | Java AES256 암호화AES 란?
Advanced Encryption Standard를 줄인 말이다. 한국어로 번역하면 ‘고급 암호화 표준’이다. 대칭키를 쓰는 블럭 암호이다. 높은 안전성과 속도로 인해 인기를 얻어 전 세계적으로 많이 사용되고 있다.
특징
대칭형, 블럭 암호화 알고리즘이다.
대칭형 암호화 알고리즘 중 가장 유명하다.
암호화 키는 128, 192, 256의 세 가지 중 하나가 될 수 있으며, 각각 AES-128, AES-192, AES-256으로 불린다.
[2026년 추가] 몇 년 지나 다시 읽어보니 아래 예제에 보안상 문제가 있어서 짚고 넘어간다. 원본 코드는 “당시 작성한 그대로” 남겨두고, 아래쪽에 고친 버전을 추가했다.
- IV(초기화 벡터)를 고정값으로 쓰고 있음:
IV = secretKey.substring(0, 16)— 매번 같은 IV를 재사용한다. IV의 목적 자체가 “같은 평문을 여러 번 암호화해도 매번 다른 암호문이 나오게” 하는 것인데, 고정 IV를 쓰면 이 목적이 완전히 무너진다. 같은 값을 두 번 암호화하면 결과도 항상 똑같이 나오고, 이는 공격자가 패턴을 분석할 실마리를 준다.AES/CBC/PKCS5Padding조합은 패딩 오라클 공격(Padding Oracle Attack)에 취약할 수 있음: CBC 모드는 인증(무결성 검증)을 해주지 않아서, 암호문이 변조돼도 복호화 시 에러 메시지 차이로 원문을 유추당할 위험이 있다. 요즘은 암호화와 동시에 인증까지 해주는 AES/GCM 모드를 기본으로 권장한다.- 비밀키를 소스코드에 하드코딩: 이건 AES256 자체의 문제는 아니지만, 실무에서는 키를 소스에 두지 않고 환경변수나 AWS KMS/Vault 같은 시크릿 매니저로 분리해야 한다.
AES256Cipher.java (예전 예제 - 위에서 지적한 문제가 있음)
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidAlgorithmParameterException;
import org.apache.commons.codec.binary.Base64;
public class AES256Cipher {
private static volatile AES256Cipher INSTANCE;
final static String secretKey = "jmlim12345bbbbbaaaaa123456789066"; //32bit
static String IV = ""; //16bit
public static AES256Cipher getInstance() {
if (INSTANCE == null) {
synchronized (AES256Cipher.class) {
if (INSTANCE == null)
INSTANCE = new AES256Cipher();
}
}
return INSTANCE;
}
private AES256Cipher() {
IV = secretKey.substring(0, 16);
}
//암호화
public static String AES_Encode(String str) throws java.io.UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] keyData = secretKey.getBytes();
SecretKey secureKey = new SecretKeySpec(keyData, "AES");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, secureKey, new IvParameterSpec(IV.getBytes()));
byte[] encrypted = c.doFinal(str.getBytes("UTF-8"));
String enStr = new String(Base64.encodeBase64(encrypted));
return enStr;
}
//복호화
public static String AES_Decode(String str) throws java.io.UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] keyData = secretKey.getBytes();
SecretKey secureKey = new SecretKeySpec(keyData, "AES");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE, secureKey, new IvParameterSpec(IV.getBytes("UTF-8")));
byte[] byteStr = Base64.decodeBase64(str.getBytes());
return new String(c.doFinal(byteStr), "UTF-8");
}
}
AES256CipherTest.java
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
public class AES256CipherTest {
String id = "hackerljm";
String custrnmNo = "1234";
String custNm = "정묵테스트";
@Test
public void encDesTest() throws InvalidKeyException, UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
AES256Cipher a256 = AES256Cipher.getInstance();
String enId = a256.AES_Encode(id);
String enYyyymmdd = a256.AES_Encode(custrnmNo);
String enCustNm = a256.AES_Encode(custNm);
String desId = a256.AES_Decode(enId);
String desYyyymmdd = a256.AES_Decode(enYyyymmdd);
String desCustNm = a256.AES_Decode(enCustNm);
assertThat(id, is(desId));
assertThat(custrnmNo, is(desYyyymmdd));
assertThat(custNm, is(desCustNm));
}
}
AES256GcmCipher.java (수정된 예제 - GCM 모드 + 매 암호화마다 랜덤 IV)
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
public class AES256GcmCipher {
private static final int GCM_TAG_LENGTH_BIT = 128;
private static final int GCM_IV_LENGTH_BYTE = 12; // GCM 권장 IV 길이
private final SecretKey secretKey;
// 비밀키는 생성자로 주입받는다 - 소스에 하드코딩하지 않고 환경변수/시크릿 매니저에서 가져온 값을 넘겨줄 것
public AES256GcmCipher(byte[] key32Byte) {
this.secretKey = new SecretKeySpec(key32Byte, "AES");
}
// 암호화 - 호출할 때마다 새 IV를 생성하고, IV를 암호문 앞에 붙여서 함께 반환
public String encrypt(String plainText) throws Exception {
byte[] iv = new byte[GCM_IV_LENGTH_BYTE];
new SecureRandom().nextBytes(iv); // 매번 랜덤 IV - 이게 원본 예제의 가장 큰 문제를 고치는 부분
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(GCM_TAG_LENGTH_BIT, iv));
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
// IV(12바이트) + 암호문을 이어붙여 하나의 값으로 저장 -> 복호화 시 앞 12바이트를 다시 IV로 꺼내 씀
byte[] combined = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length);
return Base64.getEncoder().encodeToString(combined);
}
// 복호화 - 앞 12바이트를 IV로 분리한 뒤 나머지를 복호화
public String decrypt(String cipherTextBase64) throws Exception {
byte[] combined = Base64.getDecoder().decode(cipherTextBase64);
byte[] iv = new byte[GCM_IV_LENGTH_BYTE];
byte[] encrypted = new byte[combined.length - GCM_IV_LENGTH_BYTE];
System.arraycopy(combined, 0, iv, 0, iv.length);
System.arraycopy(combined, iv.length, encrypted, 0, encrypted.length);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(GCM_TAG_LENGTH_BIT, iv));
return new String(cipher.doFinal(encrypted), "UTF-8");
}
}
// 사용 예 - 키는 실제로는 환경변수/시크릿 매니저에서 로드
byte[] key = System.getenv("AES_SECRET_KEY").getBytes("UTF-8"); // 32byte(256bit)
AES256GcmCipher cipher = new AES256GcmCipher(key);
String encrypted = cipher.encrypt("정묵테스트");
String decrypted = cipher.decrypt(encrypted);
GCM은 IV를 암호문마다 다르게 쓰는 대신, 그 IV 자체는 비밀로 감출 필요가 없다(공개돼도 안전하도록 설계됨) — 그래서 위처럼 IV를 암호문 앞에 그냥 붙여서 저장해도 된다. 다만 같은 키로 같은 IV를 두 번 쓰는 것만큼은 절대 피해야 하며,
SecureRandom으로 매번 새로 생성하는 것이 이를 보장하는 가장 간단한 방법이다.
참고:
- https://dukeom.wordpress.com/2013/01/08/aes256-%EC%95%94%ED%98%B8%ED%99%94-java-%EC%83%98%ED%94%8C/
- https://namu.wiki/w/AES
- OWASP Cryptographic Storage Cheat Sheet
Comments