'Java 양방향 암호화 AES-256 제대로 알고 사용하기'
/*
프로젝트에서 양방향 암호화를 하기 위해서 AES-256 코드를 구글링 하여 사용하였는데, 나중에 알고 보니 사용하던 것이 AES-256이 아닌 AES-128인 경우가 있었습니다.
코드를 가져와서 사용하더라도 기본적인 원리는 알고 사용해야겠다는 생각에 AES-256 암호화를 정리하게 되었습니다.
*/
본격적인 시작에 앞서 단방향 암호화 알고리즘과 양방향 암호화 알고리즘의 차이점에 대해서 간단하게 알아보겠습니다.
먼저 단방향 암호화는 평문을 암호화 했을 때 다시 평문으로 되돌리는 것(복호화)을 할 수 없는 암호화 방식으로 패스워드 암호화에 사용되며 대표적으로 'SHA-256'이 있습니다.
양방향 암호화는 평문에서 암호문으로, 암호문에서 평문으로 변환할 수 있는 방식으로 대표적으로 아래에서 살펴볼 'AES-256'이 있습니다.
(개인정보 보호법에 따라 비밀번호, 바이오정보, 그리고 주민번호, 여권번호, 운전면허번호 같은 고유 식별정보를 저장하게 될 경우 반드시 암호화하여 저장해야 합니다.)
'AES(Advanced Encryption Standard) 고급 암호화 표준'
AES는 암호화 및 복호화에 동일한 키를 사용하는 대칭키 알고리즘으로 높은 안전성과 빠른 속도로 인해 양방향 암호화에서 가장 많이 사용되는 방식입니다. AES 암호화의 종류는 AES-128, AES-192, AES-256이 있으며 뒤에 붙은 숫자는 키의 길이를 의미합니다.
(128bit = 16byte, 192bit = 24byte, 256bit = 32byte)
* 대칭키(Symmetric Key)는 암호화하고 복호화 하는데 사용되는 키가 동일한 것임을 의미하며, 반대로 비대칭키(Asymmetric Key)는 암호화와 복호화에 각각 다른 키가 사용되는 것입니다.
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Encrypt {
public static String alg = "AES/CBC/PKCS5Padding";
private final String key = "abcdefghabcdefghabcdefghabcdefgh"; // 32byte
private String iv = "0123456789abcdef"; // 16byte
// 암호화
public String encryptAES256(String text) throws Exception {
Cipher cipher = Cipher.getInstance(alg);
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivParamSpec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParamSpec);
byte[] encrypted = cipher.doFinal(text.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(encrypted);
}
// 복호화
public String decryptAES256(String cipherText) throws Exception {
Cipher cipher = Cipher.getInstance(alg);
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivParamSpec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParamSpec);
byte[] decodedBytes = Base64.getDecoder().decode(cipherText);
byte[] decrypted = cipher.doFinal(decodedBytes);
return new String(decrypted, "UTF-8");
}
}
'AES-256 사용 방법'
AES-256 암호화, 복호화 코드 예시입니다. 그리고 아래는 암호화와 복호화의 과정입니다.
암호화 : 'plain text -> pain bytes -> encrypt -> encrypted bytes -> encrypted base64 test'
복호화 : 'encrypted base64 text -> encrypted bytes -> decrypt -> plain bytes -> plain text'
AES 암호화에는 아래 네가지가 꼭 필요합니다.
SecretKey, IV(Initialize Vector), Cipher Mode(EBC/CBC/...), Padding Mode(PKCS5/PKCS7/...)
Java 기본 라이브러리 (javax.crypto.spec.SecretKeySpec, javax.crypto.spec.IvParameterSpec, javax.crypto.Cihper)
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Cipher 클래스는 암호화, 복호화 기능을 제공하며 JCE famework의 핵심을 구성합니다.
(JCE - Java Cryptography Extension)
Cipher 객체를 인스턴스화 하기 위해서는 static getInstance() 메서드를 호출하고 예시와 같이 원하는 변환 형태의 이름을 전달해야 합니다.
("AES/CBC/PKCS5Padding" => AES, CBC operation mode, PKCS5 padding scheme으로 초기화된 Cipher 객체)
***
패딩(padding)이란, 블록의 암호화를 진행하기 위해 필요한 기법으로 데이터를 특정 크기로 맞추기 위해서 사용되며, 특정 크기보다 부족한 부분의 공간을 의미 없는 문자들로 채워서 비트수를 맞추는 것입니다. (암호화 시에는 반드시 필요)
(자바에서는 PKCS5 패딩모드를 사용해도 PKCS7이 적용됩니다.)
***
AES/CBC 방식의 경우 128bit의 고정된 블록 단위로 암호화를 수행하게 되는데, CBC mode는 위 이미지에서 볼 수 있는 것처럼 이전에 암호화했던 블록화 XOR 연산을 한 다음에 암호화를 수행합니다.
그런데 첫 블록은 이전 암호화가 없기 때문에 이를 위해 IV(초기화 벡터)를 이용합니다. AES는 128bit(16byte) 단위로 암호화하기 때문에 IV도 16byte의 크기가 필요합니다.
***
IV(Initialize Vector), 초기화 벡터입니다.
위와 같은 원리로 매번 다른 IV를 생성하면 같은 평문이라도 다른 암호문을 생성할 수 있습니다. 또한 IV는 암호를 복호화할 사람에게 미리 제공되어야 하고 키와 달리 공개되어도 상관없습니다.
(미리 송신자와 수신자가 IV를 어떤 것으로 사용할지 정해두거나 파일에 IV를 기록해두는 방식으로 공유합니다.)
IvParameterSpec ivSpec = new IvParameterSpec(keyBytes);
// 여기서 keyBytes는 key값을 byte로 변환하여 16byte의 길이만큼 복사한 값입니다.
어떤 코드에서는 IV 값을 따로 주지 않고, key를 byte로 변환하여 16byte의 길이만큼 복사한 값을 IV로 사용하는 경우도 있습니다.
( 참고 ) IvParameterSpec Class
public class IvParameterSpec implements AlgorithmParameterSpec {
private byte[] iv;
public IvParameterSpec(byte[] var1) {
this(var1, 0, var1.length);
}
public IvParameterSpec(byte[] var1, int var2, int var3) {
if (var1 == null) {
throw new IllegalArgumentException("IV missing");
} else if (var1.length - var2 < var3) {
throw new IllegalArgumentException("IV buffer too short for given offset/length combination");
} else if (var3 < 0) {
throw new ArrayIndexOutOfBoundsException("len is negative");
} else {
this.iv = new byte[var3];
System.arraycopy(var1, var2, this.iv, 0, var3);
}
}
public byte[] getIV() {
return (byte[])this.iv.clone();
}
}
< 참고 자료 >
Illegal key size 오류가 발생하는 경우 참고 (Java가 기본적으로 키 길이를 128bit로 제한하기 때문입니다.)
'Programming > Java' 카테고리의 다른 글
Java Generic 제네릭 기본적인 개념 이해하기 (0) | 2021.12.24 |
---|---|
역할 분리를 위한 Entity, DTO 개념과 차이점 (2) | 2021.12.21 |
Java 직렬화, 역직렬화 방법 Serializable interface (0) | 2021.12.13 |
자바 Java 상속, 부모 클래스와 자식 클래스(extends, super) (0) | 2021.12.11 |
Java Optional Class 기본적인 이해 (0) | 2021.12.06 |