전공 서적 내용과 강의 내용을 바탕으로 개념을 정리하고, Java로 aes 알고리즘을 구현해보았습니다.
AES 알고리즘이란?
: AES(Advanced Encryption Standard)는 DES를 대체하는 대칭 키 블록 암호화 알고리즘으로, 128비트 블록 크기와 다양한 키 크기(128비트, 192비트, 256비트)를 지원한다. AES는 여러 라운드를 거쳐 암호화를 수행하며, 각 라운드에서 비트, 바이트, 열 등의 변환을 통해 입력 데이터가 점차 암호화된다. (round의 횟수는 10 / 12 / 14 round로 key size에 따라 정해진다.)
블럭암호란?
평문 블록 전체를 가지고 동일한 길이의 암호문 블록을 생성하는 암/복호화 방식이다.
전형적으로 64 bit 또는 128 bit를 사용한다.
DES(Data Encryption Standard) 암호 알고리즘이란?
- 2000년 직전까지 가장 널리 사용되었던 암호 알고리즘으로, 차등/선형 암호분석 공격 유형에 매우 강하다.
- 1977 미국 표준국에서 미 연방 정보처리 표준 46으로 채택했던 암호화 방식이다.
- 64 bit 블럭 암호 알고리즘이다. (56 bit의 key를 사용하고, 8 bit는 Parity Check 용으로 사용)
- 기본적으로 16 라운드로 진행되고, 복호화는 암호화의 역순으로 진행된다.
- 최근에는 은행권에서 DES 암호화를 3개의 key로 3번 반복함으로써 암호의 강도를 높인 triple DES를 사용한다.
- 치환과 전치 방식을 혼합해서 사용하며, 암호 key와 복호 key가 같은 관용암호(대칭키)방식을 사용한다.
*치환(substitution): 평문의 각 원소 또는 원소의 그룹을 다른 원소에 mapping
*순열(전치 = transposition): 평문 원소의 순서는 순열의 순서대로 재배치
AES가 DES를 대체하는 암호화 방식이라고 소개했는데, AES가 어떻길래 DES를 대체할 수 있었는지 알아보자.
4단계 암호화 과정
각 라운드는 4단계 암호화 과정으로 이루어져있다.
1. (sub) S-Box 사용하여 block의 byte대 byte로 치환 변환 수행
2. (trans) 행렬의 행 이동 통한 순열 변환 수행
3. (sub) 열단위로 선형 변환(ex 행렬을 곱하여)하는 치환 변환 수행
4. (sub) 현재의 block과 확장된 key의 단순 bit 단위의 XOR 과정 수행
첫번째 과정에서 S-BOX를 이용하여 치환 변환을 수행한다고 했는데, S-Box란 아래와 같다.
S-box(Substitution box)는 대칭키 암호 알고리즘, 특히 블록 암호에서 중요한 역할을 하는 비선형 함수다. S-box는 입력 값을 다른 값으로 대체(substitute)하는 테이블이나 함수로, 일반적으로 고정된 크기의 비트 입력을 받아 비트 출력을 반환한다.
위와 같은 s-box에 '110110'이라는 6비트를 넣어 4비트 '0111'로 치환할 수 있다.
그림을 보면 이해가 쉬운데, 6비트의 1,6번째 비트를 행으로, 2~5번째 비트를 열로 판단해서, 표의 해당 행렬값을 알아내면 된다. 입력값이었던 110110에서 1,6번째 비트인 10은 십진수로 2를 의미하고, 2~5번째 비트인 1011은 십진수로 11을 의미한다. 그렇게 행이 2, 열이 11인 행렬값을 찾으면 7이 나오고, 이를 4비트로 표현하면 0111이 된다.
AES 알고리즘 시각적으로 확인해보기
1. 아래 사이트의 프로그램을 다운 받아 aes 알고리즘을 직접 실행시켜 볼 수 있다. (첫 화면이 빨개서 무섭긴 한데..)
https://www.formaestudio.com/rijndaelinspector/
해당 프로그램을 이용한 예시는 아래에서 확인 가능하다!
2. 아래 프로그램을 다운 받아 aes 알고리즘의 동작과정을 애니메이션으로 볼 수 있다. 진짜 너무너무 좋다.
이제 코드를 통해 AES를 구현해보고, 각 단계를 그림을 통해 알아보자!
AES, Java로 구현하기
학번과 이름을 통해 암호키를 만들었고, 알파벳으로 되어있는 평문을 암호화, 복호화하는 프로그램을 구현해봤다.
암호키
("2022126061" + "YANGJW"): `3230323231323630363159414E474A57`
평문
("ABCDEFGHIJKLMNOP"): `4142434445464748494A4B4C4D4E4F50`
프로그램 ( 한파일로 구성 )
흐름
1. 학번과 이름을 이진수로 변환하여 aes 128 비트 암호키 생성
2. 고정된 Iv 사용하여 평문 암호화 -> 출력
3. 복호화 -> 출력
Avalance Effect 알아보기 위한 추가 작업
4. 평문 첫 번째 비트 변경 후 암호화 -> 기존 암호화 결과와 비트 차이 출력
5. 암호키의 첫 번째 비트 변경 후 암호화 -> 기존 암호화 결과와 비트 차이 출력
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.util.Arrays;
public class AESAlgorithm {
// 학번과 이름을 이진수로 변환하여 AES 128비트 키 생성
public static SecretKey generateKey(String studentId, String name) {
// 학번 10문자 + 이름 6문자 = 16문자
String studentIdBinary = stringToBinary(studentId); // 학번을 이진수로 변환
String nameBinary = stringToBinary(name); // 이름을 이진수로 변환
// 128비트(16바이트) AES 키 만들기
String keyBinary = (studentIdBinary + nameBinary).substring(0, 128); // 128비트로 자름
byte[] keyBytes = new byte[16]; // 128비트 = 16바이트
for (int i = 0; i < 16; i++) {
keyBytes[i] = (byte) Integer.parseInt(keyBinary.substring(i * 8, (i + 1) * 8), 2);
}
return new SecretKeySpec(keyBytes, "AES");
}
// 문자열을 이진수로 변환
public static String stringToBinary(String input) {
StringBuilder result = new StringBuilder();
for (char c : input.toCharArray()) {
result.append(String.format("%8s", Integer.toBinaryString(c)).replaceAll(" ", "0"));
}
return result.toString();
}
// AES 암호화
public static byte[] aesEncrypt(byte[] plaintext, SecretKey key, IvParameterSpec iv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); // NoPadding 사용
if (plaintext.length % 16 != 0) {
throw new IllegalArgumentException("Plaintext length must be a multiple of 16 bytes (128 bits) with NoPadding.");
}
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
return cipher.doFinal(plaintext); // 평문을 바이트 배열로 전달
}
// AES 복호화
public static String aesDecrypt(byte[] ciphertext, SecretKey key, IvParameterSpec iv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); // NoPadding 사용
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedBytes = cipher.doFinal(ciphertext);
return new String(decryptedBytes).trim(); // 복호화된 결과 반환
}
// 고정된 IvParameterSpec 생성 (고정된 초기화 벡터)
public static IvParameterSpec generateFixedIv() {
byte[] iv = new byte[16];
Arrays.fill(iv, (byte) 0x00); // 고정된 값으로 초기화
return new IvParameterSpec(iv);
}
// 암호키의 첫 번째 비트를 변경 (AES 128비트 키는 항상 128비트로 유지)
public static byte[] flipFirstBitInKey(byte[] key) {
byte[] modifiedKey = key.clone();
modifiedKey[0] = (byte) (modifiedKey[0] ^ 0x80); // 첫 번째 비트 변경 (XOR)
return modifiedKey;
}
// 평문의 첫 번째 비트를 변경
public static byte[] flipFirstBitInPlaintext(byte[] plaintext) {
byte[] modifiedPlaintext = plaintext.clone();
modifiedPlaintext[0] = (byte) (modifiedPlaintext[0] ^ 0x80); // 첫 번째 비트 변경 (XOR)
return modifiedPlaintext;
}
// 바이트 배열을 16진수로 변환하는 메소드
public static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b)); // 각 바이트를 16진수 2자리로 변환
}
return sb.toString();
}
// 바이트 배열을 128비트 2진수 문자열로 변환하는 메소드
public static String byteArrayToBinaryString(byte[] bytes) {
StringBuilder binaryString = new StringBuilder();
for (byte b : bytes) {
binaryString.append(String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0')); // 8자리 2진수로 변환
}
return binaryString.toString();
}
// 128비트짜리 2진수 문자열 간의 비트 차이 계산
public static int calculateBitDifferenceFromBinaryStrings(String binaryStr1, String binaryStr2) {
int diffCount = 0;
for (int i = 0; i < binaryStr1.length(); i++) {
if (binaryStr1.charAt(i) != binaryStr2.charAt(i)) {
diffCount++;
}
}
return diffCount;
}
public static void main(String[] args) throws Exception {
// 학번과 이름
String studentId = "2022126061";
String name = "YANGJW";
// 평문
String plaintext = "ABCDEFGHIJKLMNOP";
byte[] plaintextBytes = plaintext.getBytes();
if (plaintextBytes.length != 16) {
throw new IllegalArgumentException("Plaintext must be exactly 16 bytes (128 bits) long.");
}
// AES 암호키 생성
SecretKey key = generateKey(studentId, name);
// 평문 암호화 (고정된 IV 사용)
IvParameterSpec iv = generateFixedIv();
byte[] ciphertext = aesEncrypt(plaintextBytes, key, iv);
// 암호화 결과 출력
System.out.println("암호화된 텍스트 (16진수): " + bytesToHex(ciphertext));
// 복호화
String decryptedText = aesDecrypt(ciphertext, key, iv);
System.out.println("복호화된 텍스트: " + decryptedText);
// 평문 첫 번째 비트 변경 후 암호화
byte[] flippedPlaintextBytes = flipFirstBitInPlaintext(plaintextBytes);
byte[] flippedCiphertext = aesEncrypt(flippedPlaintextBytes, key, iv);
System.out.println("첫 번째 비트 바꾼 평문 암호화 텍스트 (16진수): " + bytesToHex(flippedCiphertext));
// 암호키의 첫 번째 비트 변경 후 암호화
byte[] keyBytes = key.getEncoded();
byte[] flippedKeyBytes = flipFirstBitInKey(keyBytes);
SecretKey flippedKey = new SecretKeySpec(flippedKeyBytes, "AES");
byte[] flippedKeyCiphertext = aesEncrypt(plaintextBytes, flippedKey, iv);
System.out.println("첫 번째 비트 바꾼 키로 암호화된 텍스트 (16진수): " + bytesToHex(flippedKeyCiphertext));
// 바이트 배열을 2진수 문자열로 변환
String binaryCiphertext = byteArrayToBinaryString(ciphertext);
String binaryFlippedCiphertext = byteArrayToBinaryString(flippedCiphertext);
String binaryFlippedKeyCiphertext = byteArrayToBinaryString(flippedKeyCiphertext);
// 비트 차이 계산
int plaintextBitDiff = calculateBitDifferenceFromBinaryStrings(binaryCiphertext, binaryFlippedCiphertext);
System.out.println("평문 첫 비트 변경 후 비트 차이: " + plaintextBitDiff + " 비트");
int keyBitDiff = calculateBitDifferenceFromBinaryStrings(binaryCiphertext, binaryFlippedKeyCiphertext);
System.out.println("암호키 첫 비트 변경 후 비트 차이: " + keyBitDiff + " 비트");
}
}
암호키 생성 위한 입력값이 항상 16문자로 가정했기에, 128비트로 딱 맞아 떨어지게 키를 구성할 수 있어 padding을 사용하지 않았다. 만일, 입력값의 크기가 달라지게 된다면, padding을 넣어줘야 한다.
1. 학번과 이름을 이진수로 변환하여 aes 128 비트 암호키 생성
CBC 모드에서 첫 번째 블록을 암호화할 때 IvParameterSpec를 사용했는데, iv는 암호화의 무작위성을 증가시키는 기능을 한다. 동일한 평문이라도 서로 다른 IV를 사용하면, 암호문이 달라지기에 고정된 iv를 생성해주었다.
public static IvParameterSpec generateFixedIv() {
byte[] iv = new byte[16];
Arrays.fill(iv, (byte) 0x00); // 고정된 값으로 초기화
return new IvParameterSpec(iv);
}
2. 고정된 Iv 사용하여 평문 암호화
암호화 결과: 4911697c41e0f2c065fc7300efafb990
3. 복호화
원래의 평문: 4142434445464748494A4B4C4D4E4F50
복호화한 결과: 4142434445464748494A4B4C4D4E4F50
⇒ 동일
4. 평문 첫 번째 비트 변경 후 암호화
기존의 평문: 4142434445464748494A4B4C4D4E4F50
2진수 변환에서 첫 번째 한 비트를 바꾼 평문: C142434445464748494A4B4C4D4E4F50
바꾼 평문을 기존키를 통해 암호화한 결과: 9261bbf64e41f18cfa5b2c4e0cd0ce92
기존의 암호화 결과는 , 4911697c41e0f2c065fc7300efafb990
바꾼 평문을 기존키로 암호화 결과는 , 9261bbf64e41f18cfa5b2c4e0cd0ce92
⇒ 두 결과는 ‘68’ 비트 차이
5. 암호키의 첫 번째 비트 변경 후 암호화
평문: 4142434445464748494A4B4C4D4E4F50
첫 번째 비트 바꾼 암호키: B2303231323630363159414E474A57
바꾼 암호키로 암호화한 결과: 8d24cf4c10051794448f6fe34a8babd5
기존 암호키로 암호화한 결과는, 4911697c41e0f2c065fc7300efafb990
바꾼 암호키로 암호화한 결과는, 8d24cf4c10051794448f6fe34a8babd5
⇒ 두 결과는 ‘55’ 비트 차이
Avalance Effect
Avalanche Effect는 암호화 알고리즘에서 중요한 특성으로, 입력의 작은 변화(예: 단일 비트 변경)가 출력에 큰 변화를 일으키는 현상이다. 즉, 입력의 아주 작은 차이가 암호문 전체에 큰 변화를 가져와야 한다는 개념이다.
→ 보안 측면에서 암호화의 강력함을 평가하는 데 중요한 요소로 여겨지고 있다.
AES 알고리즘에서 Avalanche Effect는 특히 잘 관찰된다. 평문의 작은 변경(ex: 1비트 차이)이 전체 암호문에 큰 영향을 미치는 이유는 AES의 내부 구조(대치, 행 이동, 열 혼합, 키 추가) 덕분이다. 각 라운드가 데이터를 복잡하게 변형하여 Avalanche Effect를 증폭시켜, 최종 암호문이 원래 평문과는 매우 다르게 나타난다.
- SubBytes (대치) AES의 첫 번째 단계는 SubBytes로, 바이트 단위로 S-box에서 대체 변환을 수행한다.
- ShiftRows (행 이동) ShiftRows는 AES 블록 내 행들을 순환 이동하여 데이터를 더 복잡하게 섞는다.
- MixColumns (열 혼합) MixColumns는 열 단위로 선형 변환을 수행하여, 데이터를 한층 더 복잡하게 변환한다.
- AddRoundKey (라운드 키 추가) 각 라운드에서 키가 블록에 XOR 연산을 통해 추가된다.
위의 실습을 통해 알 수 있다 싶이, 2진수 변환에서 첫 번째 한 비트만 바꾸어 암호화했을 뿐인데, 해당 결과와 기존의 결과의 차이가 ‘68’비트나 났고, 암호키 첫번째 비트만 바꾸어 암호화한 결과와 기존의 결과 또한 ‘55’ 비트 차이가 났다. 평문과 암호키가 모두 128 비트밖에 되지 않았는데, 평문이나 암호키 한 비트의 차이가 결과에 이토록 큰 차이를 가져왔다.
매우 이상적인 암호는 암호문에 모든 통계적 정보가 사용된 key와 독립적이어야 한다.
- Shannon -
Claude Shannon은 암호의 보안성을 높이기 위해 확산(Diffusion)과 혼돈(Confusion) 개념을 강조했다.
- 확산(Diffusion): 평문에서 작은 변화가 생기면 다수의 암호문 요소에 영향을 주어야 한다. 즉, 평문과 암호문 간의 관계가 복잡해야 한다. 이를 통해 평문이 암호문에 어떻게 반영되는지를 추적하기 어렵게 만든다.
- 혼돈(Confusion): 암호문 생성 과정에서 키를 사용하는 방식이 복잡해야 하며, 이를 통해 암호문만으로는 키를 추론하기 어렵게 만들어야 한다. 즉, 암호문의 통계적 구조와 키 사이의 관계가 복잡해야 한다.
Github Repo
https://github.com/persi0815/AES-algorithm
'Security' 카테고리의 다른 글
[Security] Kali 이용한 Webcam snap 실습 (0) | 2024.11.21 |
---|---|
[Security] Kali 이용한 Password Cracking 실습 (0) | 2024.11.21 |
[Security] Kali 이용한 Phishing 실습 (0) | 2024.11.20 |
[Security] The five stages of penetration testing (0) | 2024.11.08 |