PKCS 패딩이란 PKCS 산업 표준에서 권고하는 표준 패딩 방법이며, 이곳에 가면 더 많은 패딩 방법들을 볼 수 있다.
패딩을 내부에서 처리해주지 않는 PyCrypto로 AES와 같은 블럭 암호 알고리즘을 사용할때에는 패딩을 직접 구현해야 한다. 하지만 M2Crypto를 사용하면 모듈이 다 알아서 해준다.
Thankfully, there's another options: Me Too Crypto. Unlike PyCrypto, M2Crypto is a Python wrapper around OpenSSL which is nice, as my code will be running under mod_wsgi embedded in Apache along with mod_ssl.
PKCS 표준 패딩 규칙
The rules for PKCS padding are very simple:
- Padding bytes are always added to the clear text before it is encrypted.
- Each padding byte has a value equal to the total number of padding bytes that are added. For example, if 6 padding bytes must be added, each of those bytes will have the value 0x06.
- The total number of padding bytes is at least one, and is the number that is required in order to bring the data length up to a multiple of the cipher algorithm block size.
Some content-encryption algorithms assume the input length is a multiple of k octets, where k > 1, and let the application define a method for handling inputs whose lengths are not a multiple of k octets. For such algorithms, the method shall be to pad the input at the trailing end with k - (l mod k) octets all having value k - (l mod k), where l is the length of the input. In other words, the input is padded at the trailing end with one of the following strings: 01 -- if l mod k = k-1 02 02 -- if l mod k = k-2 . . . k k ... k k -- if l mod k = 0
이렇듯 패딩이 필요하지 않은(l mod k == 0) 마지막 부분에도 무조건(always) 패딩을 추가하는것을 볼 수 있다. 이때 패딩의 개수는 블럭 알고리즘의 블록 크기가 된다.
예시
EXAMPLE 1 Clear text consists of the following18 bytes: F14ADBDA019D6DB7 EFD91546E3FF8444 9BCB In order to make this a multiple of 16 bytes (the AES block size), we must add 14 bytes. Each byte will contain the value 0x0E, which is 14, the total number of padding bytes added. The result is that the padded clear text is as follows: F14ADBDA019D6DB7 EFD91546E3FF8444 9BCB0E0E0E0E0E0E 0E0E0E0E0E0E0E0E The padded value is 32 bytes in length, which is two AES blocks. This padded string is encrypted in CBC mode, and the resulting ciphertext will also be 32 bytes in length. EXAMPLE 1 Clear text consists of the following16 bytes: 971ACD01C9C7ADEA CC83257926F490FF This is already a multiple of the AES block size, but PKCS padding rules say that padding is always applied. Thus, we add 16 bytes of padding to bring the total length to 32, the next multiple of the AES block size. Each pad byte has the value 0x10, which is 16, the total number of padding bytes added. The result is that the padded clear text is as follows: 971ACD01C9C7ADEA CC83257926F490FF 1010101010101010 1010101010101010 The padded value is 32 bytes in length, which is two AES blocks. This padded string is encrypted in CBC mode, and the resulting cipher text will also be 32 bytes in length.
왠지 낭비인것 같지만 표준이 그러니 표준을 따르자면 표준대로 해야겠다.
표준과의 관계
Note that the PKCS standards that define this padding method describe it in a way that limits the maximum padding length to 8 bytes. This is a consequence of the fact that the algorithms at that time used 8-byte blocks. We extend the definition to apply to 16-byte AES cipher blocks.
PKCS 표준에는 패딩의 최대크기가 8바이트이다. 이것은 해당 표준이 제정된 시기에는 블록 알고리즘들의 블록크기가 8바이트였기 때문이다. AES에 적용하기 위해 16바이트로 확장한다.
결국 패딩 방법은 입맛에 맞게 쓰면 되는것 같다.
PKCS#5와 PKCS#7
PKCS#5 padding and PKCS#7 padding are the same (adding bytes 01, or 0202, or 0303 etc up to the length of the block size of the algorithm, 16 bytes in this case).
Method 1 - Pad with bytes all of the same value as the number of padding bytes This is the method recommended in [PKCS5], [PKCS7], and [CMS].
다른 문서에서 같은 메쏘드를 권고하는것 같다.
Python에서 구현하기
pad = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16) unpad = lambda s: s[0:-ord(s[-1])]
16은 블럭 알고리즘의 한 블럭의 크기이며 AES사용시 블럭크기가 16이다.
직접 구현해서 테스트해보았다.
import sys
def pad(s):
pad = 16 - (len(s) % 16)
pad = chr(pad) * pad
print 'PADDING:\t', len(pad), [pad]
return s + pad
def unpad(s):
i = 1
print 'PADSIZE:\t', ord(s[-1])
while i < ord(s[-1]):
if s[-i] != s[-1]:
print 'WARNING'
break
i += 1
print 'DATAEND:\t', len(s) - i
return s[:-i]
print
data = sys.argv[1]
print 'ORIGINAL:\t', len(data), data
padded = pad(data)
print 'PADDED:\t\t', len(padded), [padded]
unpadded = unpad(padded)
print 'UNPADDED:\t', len(unpadded), [unpadded]
print
print unpadded == data
$ python aesfile.py apsdbua0w934bawklbamf.basnbl3wn4balk34bals125 ORIGINAL: 45 apsdbua0w934bawklbamf.basnbl3wn4balk34bals125 PADDING: 3 ['\x03\x03\x03'] PADDED: 48 ['apsdbua0w934bawklbamf.basnbl3wn4balk34bals125\x03\x03\x03'] PADSIZE: 3 DATAEND: 45 UNPADDED: 45 ['apsdbua0w934bawklbamf.basnbl3wn4balk34bals125'] True $ python aesfile.py apsdbua0w934bawklbamf.basnbl3wn4balk34bals123456 ORIGINAL: 48 apsdbua0w934bawklbamf.basnbl3wn4balk34bals123456 PADDING: 16 ['\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'] PADDED: 64 ['apsdbua0w934bawklbamf.basnbl3wn4balk34bals123456\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'] PADSIZE: 16 DATAEND: 48 UNPADDED: 48 ['apsdbua0w934bawklbamf.basnbl3wn4balk34bals123456'] True
언패딩 방법이 상당히 집요한데 뒤에서부터 앞으로 마지막 패딩값만큼 반복하면서 자료의 끝(패딩의 시작점 - 1)을 구한다음 짤라 출력한다. 중간에 값이 마지막 패딩값과 다르면 패딩이 잘못 추가된 것이므로 에러(WARNING)을 출력하고 루프를 빠져나온다. 하지만 궂이 이렇게까지 해야할까 하는 의문이 드는데 어차피 마지막 패딩부분에서만 연산하기때문에 속도는 문제될것이 없지만 나중에 잘 구현된 알고리즘을 참고하거나 패딩부분이 구현된 모듈을 써야겠다.