1class InvalidPaddingError(Exception):
2 pass
3
4
5class Padding:
6 """Base class for padding and unpadding."""
7
8 def __init__(self, block_size):
9 self.block_size = block_size
10
11 def pad(self, value):
12 raise NotImplementedError('Subclasses must implement this!')
13
14 def unpad(self, value):
15 raise NotImplementedError('Subclasses must implement this!')
16
17
18class PKCS5Padding(Padding):
19 """Provide PKCS5 padding and unpadding."""
20
21 def pad(self, value):
22 if not isinstance(value, bytes):
23 value = value.encode()
24 padding_length = self.block_size - len(value) % self.block_size
25 padding_sequence = padding_length * bytes((padding_length,))
26 value_with_padding = value + padding_sequence
27
28 return value_with_padding
29
30 def unpad(self, value):
31 # Perform some input validations.
32 # In case of error, we throw a generic InvalidPaddingError()
33 if not value or len(value) < self.block_size:
34 # PKCS5 padded output will always be at least 1 block size
35 raise InvalidPaddingError()
36 if len(value) % self.block_size != 0:
37 # PKCS5 padded output will be a multiple of the block size
38 raise InvalidPaddingError()
39 if isinstance(value, bytes):
40 padding_length = value[-1]
41 if isinstance(value, str):
42 padding_length = ord(value[-1])
43 if padding_length == 0 or padding_length > self.block_size:
44 raise InvalidPaddingError()
45
46 def convert_byte_or_char_to_number(x):
47 return ord(x) if isinstance(x, str) else x
48
49 if any(
50 [
51 padding_length != convert_byte_or_char_to_number(x)
52 for x in value[-padding_length:]
53 ]
54 ):
55 raise InvalidPaddingError()
56
57 value_without_padding = value[0:-padding_length]
58
59 return value_without_padding
60
61
62class OneAndZeroesPadding(Padding):
63 """Provide the one and zeroes padding and unpadding.
64
65 This mechanism pads with 0x80 followed by zero bytes.
66 For unpadding it strips off all trailing zero bytes and the 0x80 byte.
67 """
68
69 BYTE_80 = 0x80
70 BYTE_00 = 0x00
71
72 def pad(self, value):
73 if not isinstance(value, bytes):
74 value = value.encode()
75 padding_length = self.block_size - len(value) % self.block_size
76 one_part_bytes = bytes((self.BYTE_80,))
77 zeroes_part_bytes = (padding_length - 1) * bytes((self.BYTE_00,))
78 padding_sequence = one_part_bytes + zeroes_part_bytes
79 value_with_padding = value + padding_sequence
80
81 return value_with_padding
82
83 def unpad(self, value):
84 value_without_padding = value.rstrip(bytes((self.BYTE_00,)))
85 value_without_padding = value_without_padding.rstrip(bytes((self.BYTE_80,)))
86
87 return value_without_padding
88
89
90class ZeroesPadding(Padding):
91 """Provide zeroes padding and unpadding.
92
93 This mechanism pads with 0x00 except the last byte equals
94 to the padding length. For unpadding it reads the last byte
95 and strips off that many bytes.
96 """
97
98 BYTE_00 = 0x00
99
100 def pad(self, value):
101 if not isinstance(value, bytes):
102 value = value.encode()
103 padding_length = self.block_size - len(value) % self.block_size
104 zeroes_part_bytes = (padding_length - 1) * bytes((self.BYTE_00,))
105 last_part_bytes = bytes((padding_length,))
106 padding_sequence = zeroes_part_bytes + last_part_bytes
107 value_with_padding = value + padding_sequence
108
109 return value_with_padding
110
111 def unpad(self, value):
112 if isinstance(value, bytes):
113 padding_length = value[-1]
114 if isinstance(value, str):
115 padding_length = ord(value[-1])
116 value_without_padding = value[0:-padding_length]
117
118 return value_without_padding
119
120
121class NaivePadding(Padding):
122 """Naive padding and unpadding using '*'.
123
124 The class is provided only for backwards compatibility.
125 """
126
127 CHARACTER = b'*'
128
129 def pad(self, value):
130 num_of_bytes = self.block_size - len(value) % self.block_size
131 value_with_padding = value + num_of_bytes * self.CHARACTER
132
133 return value_with_padding
134
135 def unpad(self, value):
136 value_without_padding = value.rstrip(self.CHARACTER)
137
138 return value_without_padding
139
140
141PADDING_MECHANISM = {
142 'pkcs5': PKCS5Padding,
143 'oneandzeroes': OneAndZeroesPadding,
144 'zeroes': ZeroesPadding,
145 'naive': NaivePadding,
146}