Post

AES

1. 개요

1997년 1월 2일 NIST(National Institute of Standards and Technology)는 DES를 대체할 목적으로 3DES와 같거나 더 나은 보안성을 갖고 개선된 암호 기법을 공모하였다. 선정될 암호의 정식 명칭은 1997년 9월 2일 AES(Advanced Encryption Standard)로 정해졌다. NIST는 암호론의 케르코프 원리(Kerckhoffs’ principle, 대칭 블록)에 의해 128비트 블록을 128혹은 192혹은 256비트 키 길이로 처리할 수 있고, 무료로 배포할 수 있어야 한다는 제한 조건이 있었다.

평가 항목으로는 보안, 계산적 효율성, 메모리 요구량, 하드웨어와 소프트웨어적 적합성, 유연성 5가지를 제시하였다. 마감일인 1998년 1월 15일까지 21개의 암호 알고리즘이 제안되었고 그 중 15개 암호 알고리즘이 AES 후보로 선정되어 안전성을 평가 받았다.

NIST는 15개의 암호 알고리즘에 대하여 제1차 AES 후보 대회(First AES Candidate Conference)를 1998년 8월 20일에 개최 하였고 제2차 AES 후보 대회(Second AES Candidate Conference)를 1999년 3월에 개최하였으며 이후, 1999년 8월에는 5개의 후보 알고리즘을 최종 후보로 선정하여 많은 암호학자들로 부터 안전성 평가를 받게 하였다. 이 때, 남은 5개 알고리즘의 이름은 MARS, RC6, 레인달(Rijndael), Serpent, Twofish 였다. 그리고 2000년 4월 3차 대회가 개최되고 2000년 10월 2일 AES 알고리즘으로 레인달을 선정 하였다. 이어 NIST는 2001년 2월 28일에 연방 정보 처리 표준으로 AES를 공개/리뷰/배포 하면서 기밀성있는 정보에 DES를 대체하여 AES를 사용하기 시작한다. 2001년 11월 16일에는 표준으로 채택되었으며 2001년 12월 4일에는 FIPS 197로 등록 되었다.

AES 선정과정에서 주목해야할 점은 개방성과 국제 흐름을 반영하였다는 점이다. 세 번의 대회와 많은 사람들(전문가/비전문가)의 의견을 공개/반영 하였으며 피드백의 기회를 부여하고 다함께 이슈에 대하여 토론하는 과정이 있었다. 뿐만 아니라 다양한 분야의 전문가들의 의견이 반영 되었다. 수학자, 컴퓨터 학자, 암호학자, 공학자등 다양한 분야의 전문가들이 의견을 적극적으로 반영하여 안전하면서도 훌륭한 성능을 갖춘 알고리즘을 선택할 수 있었다.

국제화 측면에서 AES는 15개의 후보 알고리즘의 저자들의 국적 만큼이나 다양한 국가의 평가를 받았다. 당시 알고리즘 안전성 평가에 적극적이었던 국가로는 대한민국을 비롯하여 일본, 이스라엘, 독일, 프랑스, 코스타리카, 캐나다, 벨기에, 호주, 노르웨이, 영국, 미국이 있었다. 레인달은 Daemen과 Rijmen이라는 두 명의 벨기에 연구원에 의해 설계 되었다. AES 선정과정에서 재밌는 점 중 다른 하나는 2차 대회가 미국이 아닌 이탈리아 로마에서 개최되었다는 점이다. AES 후보 알고리즘들은 다음과 같은 세가지 조건을 만족해야 했다.

  • 안전성(security)
  • 비용(cost)
  • 알고리즘 및 구현 특성(algorithm and implementation characteristics)

“안전성”은 절대적으로 갖춰야 하는 부분이었으며 특히 당시 대칭키 암호를 분석하는 방법인 ‘선형 공격(linear cryptanalysis)’과 ‘차분 공격(Differential cryptanalysis)’에 대한 안전성 증명이 주를 이루었다.

“비용”은 ‘스마트 카드, 하드웨어, 소프트웨어, 구현’을 위한 다양한 형태의 계산효율성(computational efficiency)을 참고하여 평가 되었다. 여기서 계산효율성이라고 하는 것은 속도 및 메모리 요구량(speed and memory requirement)등을 의미 하였다.

“알고리즘 및 구현 특성”은 유연성(Flexibility)과 알고리즘의 단순성(simplicity)을 주로 평가 하였다.

마지막 평가과정에서 남았던 5개의 알고리즘 중 4개의 알고리즘은 탈락했지만 안전성이 떨어지는 알고리즘은 절대 아니였다. 다만 Rijndael의 알고리즘이 안전성, 속도, 효율성, 구현 및 유연성이 다른 알고리즘들 보다 우수했을 뿐이다.

2. 알고리즘 개요

이 구조에서 주목할 만한 특징 중 하나는 이 구조가 파이스텔 구조가 아니라는 것이다. 전형적인 파이스텔 구조를 떠올려 보면 데이터 블록 반쪽을 다른 반쪽을 수정하는데 사용했고 그 두 반쪽을 교환하였다. AES는 파이스텔 구조를 사용하지 않고 각 라운드에서 대체(substitution)와 치환(permutation)을 이용해서 데이터 블록 전체를 병렬 처리한다.

입력으로 사용하는 키를 44개의 32비트 워드 배열 w[i]로 확장한다. 4개의 서로 다른 워드(128비트)를 각 라운드에서 라운드 키로 사용한다.

다음 네 가지 단계를 이용하는데, 한 번의 치환과 세 번의 대체이다.

  • 바이트 대체(Substitute bytes): S-box라는 표를 이용하여 바이트 단위(byte-to-byte) 형태로 블록을 교환한다.
  • 행 이동(Shift rows): 단순히 행과 행을 치환한다.
  • 열 섞기(Mix columns): 열에 속한 모든 바이트를 순환 행렬을 사용해 함수로 열에 있는 각 바이트를 대체하여 변화시킨다.
  • 라운드 키 더하기(Add round key): 확장된 키의 일부와 현재 블록을 비트별로 XOR한다.

암호와 복호를 위해서 라운드 키 더하기 단계에서 시작하고, 각 라운드에서는 4단계를 모두 포함하는 9라운드를 수행하고, 3단계로 구성된 10번째 라운드를 수행한다.

오직 라운드 키 더하기 단계에서만 키를 사용한다. 그래서 암호와 복호 과정의 시작과 끝은 항상 라운드 키 더하기 단계이다. 시작이나 끝에 수행되는 다른 단계는 키 없이 역방향 계산이 가능하기 때문에 보안을 강화시키는 데는 아무 역할도 하지 못한다.

라운드 키 더하기 단계는 그 자체가 강력하지 못대는 대신 다른 세 단계와 같이 작동하여 비트를 뒤섞는 역할을 한다. 하지만 각각은 키를 사용하지 않으므로 보안성을 제공하는 것은 아니다. 이 암호 단계를 살펴보면 블록에 XOR 암호화(라운드 키 더하기)를 하고, 그 다음 블록을 뒤섞고(다른 세 단계), 그 뒤에 다시 XOR 암호화를 하는 것으로 이를 번갈아서 적용하는 것을 볼 수 있다. 이 구조는 효과적이고 보안성을 매우 강화시킨다.

각 단계를 역으로 계산하기는 쉽다. 바이트 대체, 행이동, 열 섞기 단계는 복호 알고리즘에서 사용되는 역함수이다, 라운드 키 더하기 단계에서는 같은 라운드 키를 블록에 XOR 수행하여 역을 계산한다.

대부분 블록 암호가 그렇듯 복호 알고리즘에서는 확장 키를 순서를 뒤집어서 적용한다. 그러나 복호 알고리즘이 암호 알고리즘과 동일하지는 않다. 이것이 AES구조가 가지고 있는 특성이다.

4단계가 모두 역 계산이 가능하므로 복호화를 하면 평문을 얻을 수 있다는 것은 당연하다.

암호와 복호의 마지막 라운드는 오직 세 단계로만 구성된다. 이것이 AES구조가 가진 특성이고, 이 특성은 AES암호가 역으로 작동되기 위해 필요한 것이다.

  • AES 암호화 라운드

img001

  • AES 암호화 Table

img002

  • AES 복호화 Table

img003

3. 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AES128 {

    public static String encrypt(String planText, String key) {
        byte[] crypted = null;
        try {

            SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");

            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, skey);
            crypted = cipher.doFinal(planText.getBytes());
        } catch (Exception e) {
            System.out.println(e.toString());
        }

        BASE64Encoder encoder = new BASE64Encoder();

        String str = encoder.encode(crypted);

        return new String(str);
    }

    public static String decrypt(String input, String key) {
        byte[] output = null;
        try {
            BASE64Decoder decoder = new BASE64Decoder();

            SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");

            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, skey);
            output = cipher.doFinal(decoder.decodeBuffer(input));

        } catch (Exception e) {
            System.out.println(e.toString());
        }
        return new String(output);
    }

    public static void main(String[] args) {

        AES128 aes128 = new AES128();

        String planText = "Hello World";
        String key = "abcdefghijklmnop";

        String encrtpt = aes128.encrypt(planText, key);
        String decrypt = aes128.decrypt(encrtpt, key);

        System.out.println("AES128 암호화 : " + encrtpt);
        System.out.println("AES128 복호화 : " + decrypt);
    }
}

[출처 및 참고]

This post is licensed under CC BY 4.0 by the author.