이용기관에게 서비스하고있는 카카오페이 서비스들을 카카오인증 서비스로 이관 작업하던 중 연동테스트를 위해 파라미터들을 암호화해 넘겨주어야 했었다.
암호화에 대해 알아보자
암호화 : 평문을 암호화로...
복호화 : 암호화를 평문으로...
단방향 암호화 : 평문을 암호화 했을때, 다시 평문으로 되돌리는 것(복호화)을 할 수 없음 (대표적인 'SHA-256')
양방향 암호화 : 평문 -> 암호화, 암호화 -> 평문 사이 변환 가능 (대표적인 'AES-256')
AES256 : 대표적인 양방향 암호화 알고리즘
대칭키 : 암호화하고 복호화 하는데 사용되는 키가 동일한 것임을 의미
비대칭키 : 암호화와 복호화에 각각 다른 키가 사용되는 것
암호화, 복호화에 동일한 키를 사용하는 대칭키 알고리즘
대표적인 양방향 알고리즘
뒤에 붙은 숫자는 키의 길이를 의미합니다. (128bit = 16byte, 192bit = 24byte, 256bit = 32byte)
암호화
plain text(원본 데이터) -> pain bytes -> encrypt -> encrypted bytes -> encrypted base64 test
복호화
encrypted base64 text -> encrypted bytes -> decrypt -> plain bytes -> plain text
PlainText(평문) : 암호화되기 전 원본 데이터
Key(키) : 암호화 및 복호화를 수행하는 데 사용되는 비밀 값이며, AES256에서는 256비트의 키를 사용
Initialization Vector, IV(초기화 벡터) : 암호화 과정에서 블록 암호화를 수행하는 데 사용되는 비밀 값으로, 암호화된 데이터를 복호화할 때도 사용
CipherText(암호문) : 평문을 암호화한 결과
암호화 알고리즘 : AES256에서는 Advanced Encryption Standard(AES) 암호화 알고리즘을 사용, AES는 블록 암호화 알고리즘으로, 128비트 블록 단위로 데이터를 처리
Padding(패딩) : 데이터 블록의 길이가 블록 크기의 배수가 아닐 경우, 마지막 블록에 패딩을 추가하여 블록 크기의 배수로 맞추는 작업입니다
Operation Mode(운영 모드) : 데이터 블록을 처리하는 방법으로, AES256에서는 CTR(Counter) 운영 모드를 사용합니다. CTR 운영 모드는 블록 암호화 함수를 스트림 암호화 함수처럼 사용하여 데이터를 암호화
블록 암호화란 고정된 길이의 블록 단위로 데이터를 나누어 암호화하는 대칭키 암호화 기법
블록 단위로 암호화를 수행하기 때문에 블록 암호화라고 부름
대표적으로 DES, AES, Blowfish 등이 블록 암호화 알고리즘
블록 암호화에서는 평문을 블록 단위로 나누어 암호화를 수행하고, 이를 다시 이어붙여 전체 암호문을 생성
이때 각 블록은 독립적으로 암호화되며, 이전 블록의 암호문을 사용하지 않음
이렇게 블록 단위로 암호화를 수행함으로써, 전체 데이터를 한번에 암호화하는 것보다 더 안전한 암호화를 수행할 수 있음
블록 암호화의 가장 큰 장점은 강력한 보안성이다.
블록 단위로 암호화를 수행하기 때문에, 전체 데이터를 한번에 암호화하는 것보다 더욱 안전하게 암호화할 수 있다.
또한 블록 단위로 나누어 암호화를 수행하기 때문에, 데이터의 일부만 수정되더라도 수정된 부분 뿐만 아니라 전체 데이터의 암호화가 새롭게 수행된다.
이는 블록 암호화가 무결성을 보호하는 데 도움이 된다.
암호화 알고리즘에서 블록 암호를 사용할 때, 평문을 작은 블록으로 분할하고, 블록 단위로 암호화를 수행하는방법
이 방법을 사용하면 전체 평문을 한번에 암호화할 필요가 없으며, 블록을 분할하여 작은 블록 단위로 암호화함으로써 성능 및 보안 측면에서 이점이 있다.
대표적인 mode of operation 으로 ECB, CBC, CTR, OFB, CFB 등이 있다.
AES256 암호화 알고리즘은 대표적인 블록 암호화 알고리즘 중 하나로, 128비트 블록 크기와 256비트 키 크기를 사용한다.
AES256 암호화 알고리즘에서는 블록 크기가 128비트(16바이트)로 고정되어 있다.
입력 평문의 길이가 128비트 보다 작은 경우에는 패딩(padding)을 사용하여 블록 크기에 맞추어줘야 한다.
패딩은 입력 평문의 길이가 블록 크기의 배수가 되도록 빈 공간을 채워주는 방식이다. (대표적으로 PKCS#7과 PKCS#5 패딩이 있음)
이 패딩 방식들은 블록 크기가 16바이트일 경우, 추가해야 하는 바이트 수를 구한 뒤 그 수만큼 해당 바이트 값을 추가하는 방식으로 동작한다.
예를 들어, 10바이트의 입력 평문이 있을 경우, 6바이트의 패딩 값을 추가해야 하는데, 이 때 각 바이트에는 06이라는 값이 들어갑니다. 따라서 패딩 된 입력 평문은 16바이트가 됩니다.
반대로, 입력 평문이 128비트보다 큰 경우에는 평문을 여러 블록으로 나누어 처리한다
이때, 각 블록은 서로 독립적으로 암호화되며, 이전 블록의 결과를 사용하여 다음 블록을 암호화합니다.
이러한 방식으로 전체 평문을 암호화하면, 최종적으로 암호문이 생성된다.
예제를 통해 알아보자
// Java나 다른언어의 암호화 라이브러리를 import
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AES256 {
// 사용할 알고리즘/블록암호화 운용 모드/패딩 선택(패딩사용안함, AES기본패딩방식 = PKCS#7)
public static String alg = "AES/CTR/NoPadding";
// 암호화 기능 수행 : 입력받은 문자열을 AES256 알고리즘으로 암호화한 뒤, Base64 인코딩하여 암호화된 문자열을 반환
public static String encrypt(String text, String key, String iv) throws Exception {
// Base64로 인코딩된 문자열을 바이트 배열로 디코딩하는 부분
byte[] key_byte = Base64.getDecoder().decode(key);
byte[] iv_byte = Base64.getDecoder().decode(iv);
// 암호화 과정에서 사용되는 암호화 인스턴스 얻음
Cipher cipher = Cipher.getInstance(alg);
// 바이트배열로부터 SecretKey 생성
SecretKeySpec keySpec = new SecretKeySpec(key_byte, "AES");
// 바이트배열로부터 초기화벡터 생성
IvParameterSpec ivParamSpec = new IvParameterSpec(iv_byte);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParamSpec); // 인스턴스 초기화 (암호화 모드 : Cipher.ENCRYPT_MODE)
// 입력받은 문자열을 'UTF-8'인코딩으로 변환한 후, doFinal()메서드를 통해 데이터를 암,복호화
byte[] encrypted = cipher.doFinal(text.getBytes("UTF-8"));
// byte[] 타입의 암호화된 데이터를 Base64 인코딩을 수행한 후, String 형태로 반환
return Base64.getEncoder().encodeToString(encrypted);
}
// 복호화 기능 수행 : 암호화된 문자열을 입력받아, Base64 디코딩 및 AES256 알고리즘으로 복호화한 후, 원래 문자열을 반환
public static String decrypt(String cipherText, String key, String iv) throws Exception {
byte[] key_byte = Base64.getDecoder().decode(key);
byte[] iv_byte = Base64.getDecoder().decode(iv);
Cipher cipher = Cipher.getInstance(alg);
SecretKeySpec keySpec = new SecretKeySpec(key_byte, "AES");
IvParameterSpec ivParamSpec = new IvParameterSpec(iv_byte);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParamSpec);
byte[] decodedBytes = Base64.getDecoder().decode(cipherText);
byte[] decrypted = cipher.doFinal(decodedBytes);
return new String(decrypted, "UTF-8");
}
}