|
1 #! /usr/bin/env python |
|
2 |
|
3 """RFC 3548: Base16, Base32, Base64 Data Encodings""" |
|
4 |
|
5 # Modified 04-Oct-1995 by Jack Jansen to use binascii module |
|
6 # Modified 30-Dec-2003 by Barry Warsaw to add full RFC 3548 support |
|
7 |
|
8 import re |
|
9 import struct |
|
10 import binascii |
|
11 |
|
12 |
|
13 __all__ = [ |
|
14 # Legacy interface exports traditional RFC 1521 Base64 encodings |
|
15 'encode', 'decode', 'encodestring', 'decodestring', |
|
16 # Generalized interface for other encodings |
|
17 'b64encode', 'b64decode', 'b32encode', 'b32decode', |
|
18 'b16encode', 'b16decode', |
|
19 # Standard Base64 encoding |
|
20 'standard_b64encode', 'standard_b64decode', |
|
21 # Some common Base64 alternatives. As referenced by RFC 3458, see thread |
|
22 # starting at: |
|
23 # |
|
24 # http://zgp.org/pipermail/p2p-hackers/2001-September/000316.html |
|
25 'urlsafe_b64encode', 'urlsafe_b64decode', |
|
26 ] |
|
27 |
|
28 _translation = [chr(_x) for _x in range(256)] |
|
29 EMPTYSTRING = '' |
|
30 |
|
31 |
|
32 def _translate(s, altchars): |
|
33 translation = _translation[:] |
|
34 for k, v in altchars.items(): |
|
35 translation[ord(k)] = v |
|
36 return s.translate(''.join(translation)) |
|
37 |
|
38 |
|
39 |
|
40 # Base64 encoding/decoding uses binascii |
|
41 |
|
42 def b64encode(s, altchars=None): |
|
43 """Encode a string using Base64. |
|
44 |
|
45 s is the string to encode. Optional altchars must be a string of at least |
|
46 length 2 (additional characters are ignored) which specifies an |
|
47 alternative alphabet for the '+' and '/' characters. This allows an |
|
48 application to e.g. generate url or filesystem safe Base64 strings. |
|
49 |
|
50 The encoded string is returned. |
|
51 """ |
|
52 # Strip off the trailing newline |
|
53 encoded = binascii.b2a_base64(s)[:-1] |
|
54 if altchars is not None: |
|
55 return _translate(encoded, {'+': altchars[0], '/': altchars[1]}) |
|
56 return encoded |
|
57 |
|
58 |
|
59 def b64decode(s, altchars=None): |
|
60 """Decode a Base64 encoded string. |
|
61 |
|
62 s is the string to decode. Optional altchars must be a string of at least |
|
63 length 2 (additional characters are ignored) which specifies the |
|
64 alternative alphabet used instead of the '+' and '/' characters. |
|
65 |
|
66 The decoded string is returned. A TypeError is raised if s were |
|
67 incorrectly padded or if there are non-alphabet characters present in the |
|
68 string. |
|
69 """ |
|
70 if altchars is not None: |
|
71 s = _translate(s, {altchars[0]: '+', altchars[1]: '/'}) |
|
72 try: |
|
73 return binascii.a2b_base64(s) |
|
74 except binascii.Error, msg: |
|
75 # Transform this exception for consistency |
|
76 raise TypeError(msg) |
|
77 |
|
78 |
|
79 def standard_b64encode(s): |
|
80 """Encode a string using the standard Base64 alphabet. |
|
81 |
|
82 s is the string to encode. The encoded string is returned. |
|
83 """ |
|
84 return b64encode(s) |
|
85 |
|
86 def standard_b64decode(s): |
|
87 """Decode a string encoded with the standard Base64 alphabet. |
|
88 |
|
89 s is the string to decode. The decoded string is returned. A TypeError |
|
90 is raised if the string is incorrectly padded or if there are non-alphabet |
|
91 characters present in the string. |
|
92 """ |
|
93 return b64decode(s) |
|
94 |
|
95 def urlsafe_b64encode(s): |
|
96 """Encode a string using a url-safe Base64 alphabet. |
|
97 |
|
98 s is the string to encode. The encoded string is returned. The alphabet |
|
99 uses '-' instead of '+' and '_' instead of '/'. |
|
100 """ |
|
101 return b64encode(s, '-_') |
|
102 |
|
103 def urlsafe_b64decode(s): |
|
104 """Decode a string encoded with the standard Base64 alphabet. |
|
105 |
|
106 s is the string to decode. The decoded string is returned. A TypeError |
|
107 is raised if the string is incorrectly padded or if there are non-alphabet |
|
108 characters present in the string. |
|
109 |
|
110 The alphabet uses '-' instead of '+' and '_' instead of '/'. |
|
111 """ |
|
112 return b64decode(s, '-_') |
|
113 |
|
114 |
|
115 |
|
116 # Base32 encoding/decoding must be done in Python |
|
117 _b32alphabet = { |
|
118 0: 'A', 9: 'J', 18: 'S', 27: '3', |
|
119 1: 'B', 10: 'K', 19: 'T', 28: '4', |
|
120 2: 'C', 11: 'L', 20: 'U', 29: '5', |
|
121 3: 'D', 12: 'M', 21: 'V', 30: '6', |
|
122 4: 'E', 13: 'N', 22: 'W', 31: '7', |
|
123 5: 'F', 14: 'O', 23: 'X', |
|
124 6: 'G', 15: 'P', 24: 'Y', |
|
125 7: 'H', 16: 'Q', 25: 'Z', |
|
126 8: 'I', 17: 'R', 26: '2', |
|
127 } |
|
128 |
|
129 _b32tab = _b32alphabet.items() |
|
130 _b32tab.sort() |
|
131 _b32tab = [v for k, v in _b32tab] |
|
132 _b32rev = dict([(v, long(k)) for k, v in _b32alphabet.items()]) |
|
133 |
|
134 |
|
135 def b32encode(s): |
|
136 """Encode a string using Base32. |
|
137 |
|
138 s is the string to encode. The encoded string is returned. |
|
139 """ |
|
140 parts = [] |
|
141 quanta, leftover = divmod(len(s), 5) |
|
142 # Pad the last quantum with zero bits if necessary |
|
143 if leftover: |
|
144 s += ('\0' * (5 - leftover)) |
|
145 quanta += 1 |
|
146 for i in range(quanta): |
|
147 # c1 and c2 are 16 bits wide, c3 is 8 bits wide. The intent of this |
|
148 # code is to process the 40 bits in units of 5 bits. So we take the 1 |
|
149 # leftover bit of c1 and tack it onto c2. Then we take the 2 leftover |
|
150 # bits of c2 and tack them onto c3. The shifts and masks are intended |
|
151 # to give us values of exactly 5 bits in width. |
|
152 c1, c2, c3 = struct.unpack('!HHB', s[i*5:(i+1)*5]) |
|
153 c2 += (c1 & 1) << 16 # 17 bits wide |
|
154 c3 += (c2 & 3) << 8 # 10 bits wide |
|
155 parts.extend([_b32tab[c1 >> 11], # bits 1 - 5 |
|
156 _b32tab[(c1 >> 6) & 0x1f], # bits 6 - 10 |
|
157 _b32tab[(c1 >> 1) & 0x1f], # bits 11 - 15 |
|
158 _b32tab[c2 >> 12], # bits 16 - 20 (1 - 5) |
|
159 _b32tab[(c2 >> 7) & 0x1f], # bits 21 - 25 (6 - 10) |
|
160 _b32tab[(c2 >> 2) & 0x1f], # bits 26 - 30 (11 - 15) |
|
161 _b32tab[c3 >> 5], # bits 31 - 35 (1 - 5) |
|
162 _b32tab[c3 & 0x1f], # bits 36 - 40 (1 - 5) |
|
163 ]) |
|
164 encoded = EMPTYSTRING.join(parts) |
|
165 # Adjust for any leftover partial quanta |
|
166 if leftover == 1: |
|
167 return encoded[:-6] + '======' |
|
168 elif leftover == 2: |
|
169 return encoded[:-4] + '====' |
|
170 elif leftover == 3: |
|
171 return encoded[:-3] + '===' |
|
172 elif leftover == 4: |
|
173 return encoded[:-1] + '=' |
|
174 return encoded |
|
175 |
|
176 |
|
177 def b32decode(s, casefold=False, map01=None): |
|
178 """Decode a Base32 encoded string. |
|
179 |
|
180 s is the string to decode. Optional casefold is a flag specifying whether |
|
181 a lowercase alphabet is acceptable as input. For security purposes, the |
|
182 default is False. |
|
183 |
|
184 RFC 3548 allows for optional mapping of the digit 0 (zero) to the letter O |
|
185 (oh), and for optional mapping of the digit 1 (one) to either the letter I |
|
186 (eye) or letter L (el). The optional argument map01 when not None, |
|
187 specifies which letter the digit 1 should be mapped to (when map01 is not |
|
188 None, the digit 0 is always mapped to the letter O). For security |
|
189 purposes the default is None, so that 0 and 1 are not allowed in the |
|
190 input. |
|
191 |
|
192 The decoded string is returned. A TypeError is raised if s were |
|
193 incorrectly padded or if there are non-alphabet characters present in the |
|
194 string. |
|
195 """ |
|
196 quanta, leftover = divmod(len(s), 8) |
|
197 if leftover: |
|
198 raise TypeError('Incorrect padding') |
|
199 # Handle section 2.4 zero and one mapping. The flag map01 will be either |
|
200 # False, or the character to map the digit 1 (one) to. It should be |
|
201 # either L (el) or I (eye). |
|
202 if map01: |
|
203 s = _translate(s, {'0': 'O', '1': map01}) |
|
204 if casefold: |
|
205 s = s.upper() |
|
206 # Strip off pad characters from the right. We need to count the pad |
|
207 # characters because this will tell us how many null bytes to remove from |
|
208 # the end of the decoded string. |
|
209 padchars = 0 |
|
210 mo = re.search('(?P<pad>[=]*)$', s) |
|
211 if mo: |
|
212 padchars = len(mo.group('pad')) |
|
213 if padchars > 0: |
|
214 s = s[:-padchars] |
|
215 # Now decode the full quanta |
|
216 parts = [] |
|
217 acc = 0 |
|
218 shift = 35 |
|
219 for c in s: |
|
220 val = _b32rev.get(c) |
|
221 if val is None: |
|
222 raise TypeError('Non-base32 digit found') |
|
223 acc += _b32rev[c] << shift |
|
224 shift -= 5 |
|
225 if shift < 0: |
|
226 parts.append(binascii.unhexlify('%010x' % acc)) |
|
227 acc = 0 |
|
228 shift = 35 |
|
229 # Process the last, partial quanta |
|
230 last = binascii.unhexlify('%010x' % acc) |
|
231 if padchars == 0: |
|
232 last = '' # No characters |
|
233 elif padchars == 1: |
|
234 last = last[:-1] |
|
235 elif padchars == 3: |
|
236 last = last[:-2] |
|
237 elif padchars == 4: |
|
238 last = last[:-3] |
|
239 elif padchars == 6: |
|
240 last = last[:-4] |
|
241 else: |
|
242 raise TypeError('Incorrect padding') |
|
243 parts.append(last) |
|
244 return EMPTYSTRING.join(parts) |
|
245 |
|
246 |
|
247 |
|
248 # RFC 3548, Base 16 Alphabet specifies uppercase, but hexlify() returns |
|
249 # lowercase. The RFC also recommends against accepting input case |
|
250 # insensitively. |
|
251 def b16encode(s): |
|
252 """Encode a string using Base16. |
|
253 |
|
254 s is the string to encode. The encoded string is returned. |
|
255 """ |
|
256 return binascii.hexlify(s).upper() |
|
257 |
|
258 |
|
259 def b16decode(s, casefold=False): |
|
260 """Decode a Base16 encoded string. |
|
261 |
|
262 s is the string to decode. Optional casefold is a flag specifying whether |
|
263 a lowercase alphabet is acceptable as input. For security purposes, the |
|
264 default is False. |
|
265 |
|
266 The decoded string is returned. A TypeError is raised if s were |
|
267 incorrectly padded or if there are non-alphabet characters present in the |
|
268 string. |
|
269 """ |
|
270 if casefold: |
|
271 s = s.upper() |
|
272 if re.search('[^0-9A-F]', s): |
|
273 raise TypeError('Non-base16 digit found') |
|
274 return binascii.unhexlify(s) |
|
275 |
|
276 |
|
277 |
|
278 # Legacy interface. This code could be cleaned up since I don't believe |
|
279 # binascii has any line length limitations. It just doesn't seem worth it |
|
280 # though. |
|
281 |
|
282 MAXLINESIZE = 76 # Excluding the CRLF |
|
283 MAXBINSIZE = (MAXLINESIZE//4)*3 |
|
284 |
|
285 def encode(input, output): |
|
286 """Encode a file.""" |
|
287 while True: |
|
288 s = input.read(MAXBINSIZE) |
|
289 if not s: |
|
290 break |
|
291 while len(s) < MAXBINSIZE: |
|
292 ns = input.read(MAXBINSIZE-len(s)) |
|
293 if not ns: |
|
294 break |
|
295 s += ns |
|
296 line = binascii.b2a_base64(s) |
|
297 output.write(line) |
|
298 |
|
299 |
|
300 def decode(input, output): |
|
301 """Decode a file.""" |
|
302 while True: |
|
303 line = input.readline() |
|
304 if not line: |
|
305 break |
|
306 s = binascii.a2b_base64(line) |
|
307 output.write(s) |
|
308 |
|
309 |
|
310 def encodestring(s): |
|
311 """Encode a string into multiple lines of base-64 data.""" |
|
312 pieces = [] |
|
313 for i in range(0, len(s), MAXBINSIZE): |
|
314 chunk = s[i : i + MAXBINSIZE] |
|
315 pieces.append(binascii.b2a_base64(chunk)) |
|
316 return "".join(pieces) |
|
317 |
|
318 |
|
319 def decodestring(s): |
|
320 """Decode a string.""" |
|
321 return binascii.a2b_base64(s) |
|
322 |
|
323 |
|
324 |
|
325 # Useable as a script... |
|
326 def test(): |
|
327 """Small test program""" |
|
328 import sys, getopt |
|
329 try: |
|
330 opts, args = getopt.getopt(sys.argv[1:], 'deut') |
|
331 except getopt.error, msg: |
|
332 sys.stdout = sys.stderr |
|
333 print msg |
|
334 print """usage: %s [-d|-e|-u|-t] [file|-] |
|
335 -d, -u: decode |
|
336 -e: encode (default) |
|
337 -t: encode and decode string 'Aladdin:open sesame'"""%sys.argv[0] |
|
338 sys.exit(2) |
|
339 func = encode |
|
340 for o, a in opts: |
|
341 if o == '-e': func = encode |
|
342 if o == '-d': func = decode |
|
343 if o == '-u': func = decode |
|
344 if o == '-t': test1(); return |
|
345 if args and args[0] != '-': |
|
346 func(open(args[0], 'rb'), sys.stdout) |
|
347 else: |
|
348 func(sys.stdin, sys.stdout) |
|
349 |
|
350 |
|
351 def test1(): |
|
352 s0 = "Aladdin:open sesame" |
|
353 s1 = encodestring(s0) |
|
354 s2 = decodestring(s1) |
|
355 print s0, repr(s1), s2 |
|
356 |
|
357 |
|
358 if __name__ == '__main__': |
|
359 test() |