본문 바로가기

Cryptography/Cryptography

Padding Oracle Attack

Padding Oracle Attack

 

CBC 모드 암호화에서 padding 오류 메시지의 "다른 반응"을 이용해 복호화 키를 몰라도 평문을 역추론하는 공격이다.

 

CBC 복호화

$C_0 = IV$

$P_i = D_k(C_i) \oplus C_{i-1}$

 

XOR 연산 때문에 이전 블록을 조작하면 평문 결과도 달라지게 된다.

공격자가 이전 블록을 조작하면 복호화된 패딩 결과가 어떻게 달라지는지 서버가 알려준다.

 

 

$C_1, C_2$라는 블록을 CBC 모드에서 복호화하면 다음 두 블록을 얻을 수 있다.

$D_K(C_1) \oplus IV, D_K(C_2) \oplus C_1$

 

대부분의 Padding 알고리즘에서 Padding과 관련 있는 블록은 마지막 블록뿐이다.

즉, 공격자는 $ D_K(C_2) \oplus C_1$에만 신경 쓰면 된다.

 

 

Padding oracle attack은 복호화 과정에 블록 복호화를 진행한 후 패딩을 제거하는 기능이 포함되어있을 때,

패딩의 형식이 일치하는지의 여부를 통해 복호화 후의 블록 정보를 알아내는 공격이다.

 

PKCS#7 패딩을 사용해서 자세히 알아보자.

 

임의로 블록 사이즈를 8바이트라고 가정하면, 마지막 8바이트 블록의 올바른 패딩의 가능성은 8가지뿐이다.

|???? ???? ???? ??01|
|???? ???? ???? 0202|
|???? ???? ??03 0303|
|???? ???? 0404 0404|
|???? ??05 0505 0505|
|???? 0606 0606 0606|
|??07 0707 0707 0707|
|0808 0808 0808 0808|

 

따라서, 위 8가지 형식 중 단 하나의 형식과도 일치하지 않는다면 Unpad 에러를 낸다.

이 에러 여부를 통해서 $D_K(C_2)$의 모든 바이트를 알아낼 수 있다.

 

 

< 평문을 알아내는 과정>

 

우리가 알아내고 싶은 블록을 $C_i$라고 하고, 앞 블록을 $C_{i-1}$라고 하자.

 

복호화 과정 : $P_i = D_K(C_i) \oplus C_{i-1}$

 

평문을 알아내기 위해서 $C_{i-1}$을 조작한 값인 $C'_{i-1}$로 바꿔서 서버로 보낸다.

그러면 서버는 조작된 값으로 평문을 계산한다.

 

$P'_i = D_K(C_i) \oplus C'_{i-1}$

 

 

1. 마지막 바이트 알아내기

서버가 계산하는 $P'_i$의 마지막 바이트가 0x01이 되도록 $C'_{i-1}$를 조작한다.

 

마지막 바이트가 0x01이면 패딩 길이가 1이므로

서버가 유효한 패딩이라고 인식하게되어 패딩 OK 신호를 주게된다.

 

$P'_i[7] = D_K(C_i)[7] \oplus C'_{i-1}[7]$

 

우리는 $P'_i[7]$를 0x01로 만들고 싶은 것이니,

 

$P'_i[7] = 0x01= D_K(C_i)[7] \oplus C'_{i-1}[7]$

$D_K(C_i)[7] = 0x01 \oplus C'_{i-1}[7]$

 

 

[ 공격자가 하는 일 ]

1. $C_{i-1}[0..6]$ (앞 바이트)는 아무 값이나 설정 (보통 원래 값 유지)

2. $C'_{i-1}[7]$를 0~255까지 바꿔가면서 서버에 전송

3. 서버가 패딩 OK를 반환하면 그때의 $C'_{i-1}[7]$를 기록한다.

4. $D_K(C_i)[7] = 0x01 \oplus C'_{i-1}[7]$을 계산해 $D_K(C_i)$의 마지막 바이트를 계산한다.

 

 

 

2. 끝에서 두 번째 바이트 알아내기

 

이번에는 마지막 2바이트가 0x02 0x02가 되도록 $C'_{i-1}$을 조작한다.

 

$P'_i[7] = D_K(C_i)[7] \oplus C'_{i-1}[7] = 0x02$

$P'_i[6] = D_K(C_i)[6] \oplus C'_{i-1}[6] = 0x02$

 

우리는 이미 $D_K(C_i)[7]$ 값을 알고 있기 때문에

따라서 마지막 바이트를 $C'_{i-1}[7] = D_K(C_i)[7] \oplus 0x02$로 마지막 바이트를 고정하고,

앞서와 같은 방식으로 $C'_{i-1}[6]$만 0~255 범위로 시도해서

패딩 OK가 나오는 경우를 찾는다.

 

패딩 OK가 나오면  $D_K(C_i)[6] = 0x02 \oplus C'_{i-1}[6]$을 계산에 마지막에서 두 번째 바이트를 얻는다.

 

 

이와 같은 과정을 반복해 $D_K(C_i)$의 모든 바이트를 알아낸다.

 

 

<예시>

이해를 더 쉽게 하기 위해서 예시로 확인해보자.

 

먼저, $C_1$을 NULL바이트 7개와 마지막에 0~255의 존재하는 모든 바이트를 이어붙인

256가지의 경우의 수에 대해 패딩 성공 여부를 확인한다.

이 중에서 패딩 오류가 나지 않은 $C_1$의 마지막 바이트를 통해서 $D_K(C_2)의 마지막 바이트를 계산할 수 있다.

 

$D_K(C_2)$의 값이 b"AmoNando" = [65, 109, 111, 78, 97, 110, 100, 111]이라고 가정하자.

 

 

$C_1$이 [0,0,0,0,0,0,0,110]일 때, $D_K(C_2) \oplus C_1$의 마지막 바이트가 0x01임을 알 수 있다.

 

$D_K(C_1)$의 마지막 바이트 = 0x01 $\oplus$ $C_1$의 마지막 바이트

 

따라서 $D_K(C_1)$의 마지막 바이트는 110 $\oplus$ 1 = 111임을 알 수 있다.

 

 

$D_K(C_2)$의 마지막에서 두 번째 바이트를 구하기 위해서는

마지막 두 바이트가 0202와 일치하는 $D_K(C_2) \oplus C_1$를 찾아야한다.

 

마지막 바이트의 값이 111임을 이미 알고 있기 때문에 $C_1$의 마지막 바이트를 111 $\oplus$ 2 = 109로 설정하면

$D_K(C_2) \oplus C_1$의 마지막 바이트가 02가 된다.

따라서 마지막에서 두 번째 바이트만 앞에서와 동일하게 256가지 시도를 통해 02가 되는 경우를 찾는다.

 

 

$C_1$이 [0,0,0,0,0,0,102,109]일 때, $D_K(C_2) \oplus C_1$의 마지막 두 바이트가 02 02임을 알 수 있다.

이렇게 $D_K(C_2)$의 마지막에서 두 번째 바이트가 102 $\oplus$  2 = 100임을 알 수 있다.

 

이와 같은 과정을 반복해  $D_K(C_2)$의 모든 바이트를 복구한다.

 

 

[ 예외처리 ]

$D_K(C_2)$가 [10, 20, 30, 40, 50, 3, 3, 10]이라고 가정하고 Padding oracle attack을 해보면

 

마지막 바이트가 0x01인 것을 찾는 과정이었는데 마지막 3바이트가 3 3 3이 되면서 패딩 성공해서

두 가지의 경우가 성공하게 되었다.

 

이렇게 두 가지 경우가 성공할 경우가 높지는 않지만, 예외 상황을 완벽하게 방지하기 위해서는

마지막에서 두 번째 바이트를 다른 값으로 바꿔서 시도해보면 된다.

 

 

'Cryptography > Cryptography' 카테고리의 다른 글

RSA  (0) 2026.01.16
Fermat's little theorem  (0) 2026.01.15
블록 암호와 운영 모드  (0) 2025.11.15
AES(Advanced Encryption Standard) - 2  (0) 2025.11.08
AES(Advanced Encryption Standard) - 1  (0) 2025.11.05