1*0e16a31dSJim Jagielski#************************************************************** 2*0e16a31dSJim Jagielski# 3*0e16a31dSJim Jagielski# Licensed to the Apache Software Foundation (ASF) under one 4*0e16a31dSJim Jagielski# or more contributor license agreements. See the NOTICE file 5*0e16a31dSJim Jagielski# distributed with this work for additional information 6*0e16a31dSJim Jagielski# regarding copyright ownership. The ASF licenses this file 7*0e16a31dSJim Jagielski# to you under the Apache License, Version 2.0 (the 8*0e16a31dSJim Jagielski# "License"); you may not use this file except in compliance 9*0e16a31dSJim Jagielski# with the License. You may obtain a copy of the License at 10*0e16a31dSJim Jagielski# 11*0e16a31dSJim Jagielski# http://www.apache.org/licenses/LICENSE-2.0 12*0e16a31dSJim Jagielski# 13*0e16a31dSJim Jagielski# Unless required by applicable law or agreed to in writing, 14*0e16a31dSJim Jagielski# software distributed under the License is distributed on an 15*0e16a31dSJim Jagielski# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16*0e16a31dSJim Jagielski# KIND, either express or implied. See the License for the 17*0e16a31dSJim Jagielski# specific language governing permissions and limitations 18*0e16a31dSJim Jagielski# under the License. 19*0e16a31dSJim Jagielski# 20*0e16a31dSJim Jagielski#************************************************************** 21*0e16a31dSJim Jagielski 22*0e16a31dSJim Jagielski# usage: decrypt.py <in-dir> <out-dir> <password> (decrypt.py --selftest runs the cipher known-answer tests) 23*0e16a31dSJim Jagielski# Writes a plain copy of every package in <in-dir> into <out-dir> for the schema checkers: encrypted 24*0e16a31dSJim Jagielski# streams are decrypted and inflated, the manifest loses its encryption-data elements and the manifest:size 25*0e16a31dSJim Jagielski# of those entries. Do not run pkgcheck.py on the copies. A checksum mismatch (wrong password) or any other 26*0e16a31dSJim Jagielski# failure writes nothing for that package and makes the exit status 1. 27*0e16a31dSJim Jagielski# Implements what main/package writes and reads: PBKDF2-HMAC-SHA1 over the start key (SHA1 or SHA256 of the 28*0e16a31dSJim Jagielski# UTF-8 password), Blowfish with 64 bit feedback (rtl_cipher_CFB over 8 byte blocks) or AES-CBC with W3C padding. 29*0e16a31dSJim Jagielskiimport sys, os, io, re, zlib, zipfile, hashlib, base64, struct 30*0e16a31dSJim Jagielskifrom xml.etree import ElementTree as ET 31*0e16a31dSJim Jagielski 32*0e16a31dSJim JagielskiM = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" 33*0e16a31dSJim JagielskiQ = "{%s}" % M 34*0e16a31dSJim JagielskiET.register_namespace("manifest", M) 35*0e16a31dSJim JagielskiMASK = 0xFFFFFFFF 36*0e16a31dSJim Jagielski 37*0e16a31dSJim Jagielski 38*0e16a31dSJim Jagielskiclass DecryptError(Exception): 39*0e16a31dSJim Jagielski pass 40*0e16a31dSJim Jagielski 41*0e16a31dSJim Jagielski 42*0e16a31dSJim Jagielskidef pi_hex_words(n): 43*0e16a31dSJim Jagielski bits = 32 * n + 64 44*0e16a31dSJim Jagielski one = 1 << (bits + 16) 45*0e16a31dSJim Jagielski 46*0e16a31dSJim Jagielski def arctan_inv(x): 47*0e16a31dSJim Jagielski x2, term, total, k = x * x, one // x, 0, 0 48*0e16a31dSJim Jagielski while term: 49*0e16a31dSJim Jagielski total += term // (2 * k + 1) if k % 2 == 0 else -(term // (2 * k + 1)) 50*0e16a31dSJim Jagielski term //= x2 51*0e16a31dSJim Jagielski k += 1 52*0e16a31dSJim Jagielski return total 53*0e16a31dSJim Jagielski pi = 16 * arctan_inv(5) - 4 * arctan_inv(239) 54*0e16a31dSJim Jagielski frac = (pi - (3 * one)) >> 16 55*0e16a31dSJim Jagielski frac >>= 64 56*0e16a31dSJim Jagielski return [(frac >> (32 * (n - 1 - i))) & MASK for i in range(n)] 57*0e16a31dSJim Jagielski 58*0e16a31dSJim Jagielski 59*0e16a31dSJim Jagielski_BF_INIT = [] 60*0e16a31dSJim Jagielski 61*0e16a31dSJim Jagielski 62*0e16a31dSJim Jagielskiclass Blowfish: 63*0e16a31dSJim Jagielski def __init__(self, key): 64*0e16a31dSJim Jagielski if not _BF_INIT: 65*0e16a31dSJim Jagielski _BF_INIT.extend(pi_hex_words(18 + 1024)) 66*0e16a31dSJim Jagielski assert _BF_INIT[0] == 0x243F6A88 and _BF_INIT[18] == 0xD1310BA6 and _BF_INIT[-1] == 0x3AC372E6 67*0e16a31dSJim Jagielski self.P = _BF_INIT[:18] 68*0e16a31dSJim Jagielski self.S = [_BF_INIT[18 + 256 * i:18 + 256 * (i + 1)] for i in range(4)] 69*0e16a31dSJim Jagielski k = 0 70*0e16a31dSJim Jagielski for i in range(18): 71*0e16a31dSJim Jagielski d = 0 72*0e16a31dSJim Jagielski for _ in range(4): 73*0e16a31dSJim Jagielski d = (d << 8) | key[k] 74*0e16a31dSJim Jagielski k = (k + 1) % len(key) 75*0e16a31dSJim Jagielski self.P[i] ^= d 76*0e16a31dSJim Jagielski l = r = 0 77*0e16a31dSJim Jagielski for i in range(0, 18, 2): 78*0e16a31dSJim Jagielski l, r = self.encrypt(l, r) 79*0e16a31dSJim Jagielski self.P[i], self.P[i + 1] = l, r 80*0e16a31dSJim Jagielski for i in range(4): 81*0e16a31dSJim Jagielski for j in range(0, 256, 2): 82*0e16a31dSJim Jagielski l, r = self.encrypt(l, r) 83*0e16a31dSJim Jagielski self.S[i][j], self.S[i][j + 1] = l, r 84*0e16a31dSJim Jagielski 85*0e16a31dSJim Jagielski def encrypt(self, l, r): 86*0e16a31dSJim Jagielski P, S0, S1, S2, S3 = self.P, *self.S 87*0e16a31dSJim Jagielski for i in range(16): 88*0e16a31dSJim Jagielski l ^= P[i] 89*0e16a31dSJim Jagielski r ^= ((((S0[l >> 24] + S1[(l >> 16) & 255]) & MASK) ^ S2[(l >> 8) & 255]) + S3[l & 255]) & MASK 90*0e16a31dSJim Jagielski l, r = r, l 91*0e16a31dSJim Jagielski l, r = r, l 92*0e16a31dSJim Jagielski return l ^ P[17], r ^ P[16] 93*0e16a31dSJim Jagielski 94*0e16a31dSJim Jagielski def encrypt_block(self, b): 95*0e16a31dSJim Jagielski return struct.pack(">II", *self.encrypt(*struct.unpack(">II", b))) 96*0e16a31dSJim Jagielski 97*0e16a31dSJim Jagielski 98*0e16a31dSJim Jagielskidef blowfish_cfb64_decrypt(key, iv, data): 99*0e16a31dSJim Jagielski bf = Blowfish(key) 100*0e16a31dSJim Jagielski reg = (iv + bytes(8))[:8] 101*0e16a31dSJim Jagielski out = bytearray() 102*0e16a31dSJim Jagielski for i in range(0, len(data), 8): 103*0e16a31dSJim Jagielski ct = data[i:i + 8] 104*0e16a31dSJim Jagielski ks = bf.encrypt_block(reg) 105*0e16a31dSJim Jagielski out += bytes(a ^ b for a, b in zip(ct, ks)) 106*0e16a31dSJim Jagielski reg = ct 107*0e16a31dSJim Jagielski return bytes(out) 108*0e16a31dSJim Jagielski 109*0e16a31dSJim Jagielski 110*0e16a31dSJim Jagielskidef _aes_tables(): 111*0e16a31dSJim Jagielski def xt(a): 112*0e16a31dSJim Jagielski a <<= 1 113*0e16a31dSJim Jagielski return (a ^ 0x11B) & 255 if a & 256 else a 114*0e16a31dSJim Jagielski 115*0e16a31dSJim Jagielski def mul(a, b): 116*0e16a31dSJim Jagielski r = 0 117*0e16a31dSJim Jagielski while b: 118*0e16a31dSJim Jagielski if b & 1: 119*0e16a31dSJim Jagielski r ^= a 120*0e16a31dSJim Jagielski a, b = xt(a), b >> 1 121*0e16a31dSJim Jagielski return r 122*0e16a31dSJim Jagielski inv = [0] * 256 123*0e16a31dSJim Jagielski for a in range(1, 256): 124*0e16a31dSJim Jagielski for b in range(1, 256): 125*0e16a31dSJim Jagielski if mul(a, b) == 1: 126*0e16a31dSJim Jagielski inv[a] = b 127*0e16a31dSJim Jagielski break 128*0e16a31dSJim Jagielski sbox = [] 129*0e16a31dSJim Jagielski for a in range(256): 130*0e16a31dSJim Jagielski x = inv[a] 131*0e16a31dSJim Jagielski s = x ^ (x << 1 | x >> 7) & 255 ^ (x << 2 | x >> 6) & 255 ^ (x << 3 | x >> 5) & 255 ^ (x << 4 | x >> 4) & 255 ^ 0x63 132*0e16a31dSJim Jagielski sbox.append(s & 255) 133*0e16a31dSJim Jagielski isbox = [0] * 256 134*0e16a31dSJim Jagielski for i, s in enumerate(sbox): 135*0e16a31dSJim Jagielski isbox[s] = i 136*0e16a31dSJim Jagielski return sbox, isbox, {n: [mul(a, n) for a in range(256)] for n in (2, 9, 11, 13, 14)} 137*0e16a31dSJim Jagielski 138*0e16a31dSJim Jagielski 139*0e16a31dSJim Jagielski_AES = [] 140*0e16a31dSJim Jagielski 141*0e16a31dSJim Jagielski 142*0e16a31dSJim Jagielskidef aes_schedule(key): 143*0e16a31dSJim Jagielski if not _AES: 144*0e16a31dSJim Jagielski _AES.extend(_aes_tables()) 145*0e16a31dSJim Jagielski sbox, mt = _AES[0], _AES[2] 146*0e16a31dSJim Jagielski if len(key) not in (16, 24, 32): 147*0e16a31dSJim Jagielski raise DecryptError("AES key size %d" % len(key)) 148*0e16a31dSJim Jagielski nk = len(key) // 4 149*0e16a31dSJim Jagielski nr = nk + 6 150*0e16a31dSJim Jagielski w = [list(key[4 * i:4 * i + 4]) for i in range(nk)] 151*0e16a31dSJim Jagielski rcon = 1 152*0e16a31dSJim Jagielski for i in range(nk, 4 * (nr + 1)): 153*0e16a31dSJim Jagielski t = list(w[i - 1]) 154*0e16a31dSJim Jagielski if i % nk == 0: 155*0e16a31dSJim Jagielski t = [sbox[b] for b in t[1:] + t[:1]] 156*0e16a31dSJim Jagielski t[0] ^= rcon 157*0e16a31dSJim Jagielski rcon = mt[2][rcon] 158*0e16a31dSJim Jagielski elif nk > 6 and i % nk == 4: 159*0e16a31dSJim Jagielski t = [sbox[b] for b in t] 160*0e16a31dSJim Jagielski w.append([a ^ b for a, b in zip(w[i - nk], t)]) 161*0e16a31dSJim Jagielski return [sum(w[4 * r:4 * r + 4], []) for r in range(nr + 1)] 162*0e16a31dSJim Jagielski 163*0e16a31dSJim Jagielski 164*0e16a31dSJim Jagielskidef aes_decrypt_block(rk, blk): 165*0e16a31dSJim Jagielski isbox, mt = _AES[1], _AES[2] 166*0e16a31dSJim Jagielski m9, m11, m13, m14 = mt[9], mt[11], mt[13], mt[14] 167*0e16a31dSJim Jagielski nr = len(rk) - 1 168*0e16a31dSJim Jagielski s = [a ^ b for a, b in zip(blk, rk[nr])] 169*0e16a31dSJim Jagielski for r in range(nr - 1, -1, -1): 170*0e16a31dSJim Jagielski s = [s[0], s[13], s[10], s[7], s[4], s[1], s[14], s[11], s[8], s[5], s[2], s[15], s[12], s[9], s[6], s[3]] 171*0e16a31dSJim Jagielski s = [isbox[b] ^ k for b, k in zip(s, rk[r])] 172*0e16a31dSJim Jagielski if r: 173*0e16a31dSJim Jagielski t = [] 174*0e16a31dSJim Jagielski for c in range(0, 16, 4): 175*0e16a31dSJim Jagielski a0, a1, a2, a3 = s[c:c + 4] 176*0e16a31dSJim Jagielski t += [m14[a0] ^ m11[a1] ^ m13[a2] ^ m9[a3], m9[a0] ^ m14[a1] ^ m11[a2] ^ m13[a3], 177*0e16a31dSJim Jagielski m13[a0] ^ m9[a1] ^ m14[a2] ^ m11[a3], m11[a0] ^ m13[a1] ^ m9[a2] ^ m14[a3]] 178*0e16a31dSJim Jagielski s = t 179*0e16a31dSJim Jagielski return bytes(s) 180*0e16a31dSJim Jagielski 181*0e16a31dSJim Jagielski 182*0e16a31dSJim Jagielskidef aes_cbc_decrypt(key, iv, data): 183*0e16a31dSJim Jagielski rk = aes_schedule(key) 184*0e16a31dSJim Jagielski if len(data) % 16 or not data: 185*0e16a31dSJim Jagielski raise DecryptError("AES data is not a multiple of 16 bytes") 186*0e16a31dSJim Jagielski out, prev = bytearray(), iv 187*0e16a31dSJim Jagielski for off in range(0, len(data), 16): 188*0e16a31dSJim Jagielski blk = data[off:off + 16] 189*0e16a31dSJim Jagielski out += bytes(a ^ b for a, b in zip(aes_decrypt_block(rk, blk), prev)) 190*0e16a31dSJim Jagielski prev = blk 191*0e16a31dSJim Jagielski n = out[-1] 192*0e16a31dSJim Jagielski if not 1 <= n <= 16: 193*0e16a31dSJim Jagielski raise DecryptError("bad AES padding byte %d" % n) 194*0e16a31dSJim Jagielski return bytes(out[:-n]) 195*0e16a31dSJim Jagielski 196*0e16a31dSJim Jagielski 197*0e16a31dSJim Jagielskidef sha1_rtl(data): 198*0e16a31dSJim Jagielski """SHA-1 as main/sal rtl_digest_SHA1 computes it: input lengths of 52..55 mod 64 get one extra padding block.""" 199*0e16a31dSJim Jagielski h = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0] 200*0e16a31dSJim Jagielski r = len(data) % 64 201*0e16a31dSJim Jagielski m = data + b"\x80" + bytes((55 - r) % 64) + (bytes(64) if 52 <= r <= 55 else b"") + struct.pack(">Q", len(data) * 8) 202*0e16a31dSJim Jagielski 203*0e16a31dSJim Jagielski def rol(x, n): 204*0e16a31dSJim Jagielski return ((x << n) | (x >> (32 - n))) & MASK 205*0e16a31dSJim Jagielski for o in range(0, len(m), 64): 206*0e16a31dSJim Jagielski w = list(struct.unpack(">16I", m[o:o + 64])) 207*0e16a31dSJim Jagielski for i in range(16, 80): 208*0e16a31dSJim Jagielski w.append(rol(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1)) 209*0e16a31dSJim Jagielski a, b, c, d, e = h 210*0e16a31dSJim Jagielski for i in range(80): 211*0e16a31dSJim Jagielski if i < 20: 212*0e16a31dSJim Jagielski f, k = (b & c) | (~b & d), 0x5A827999 213*0e16a31dSJim Jagielski elif i < 40: 214*0e16a31dSJim Jagielski f, k = b ^ c ^ d, 0x6ED9EBA1 215*0e16a31dSJim Jagielski elif i < 60: 216*0e16a31dSJim Jagielski f, k = (b & c) | (b & d) | (c & d), 0x8F1BBCDC 217*0e16a31dSJim Jagielski else: 218*0e16a31dSJim Jagielski f, k = b ^ c ^ d, 0xCA62C1D6 219*0e16a31dSJim Jagielski a, b, c, d, e = (rol(a, 5) + f + e + k + w[i]) & MASK, a, rol(b, 30), c, d 220*0e16a31dSJim Jagielski h = [(x + y) & MASK for x, y in zip(h, (a, b, c, d, e))] 221*0e16a31dSJim Jagielski return struct.pack(">5I", *h) 222*0e16a31dSJim Jagielski 223*0e16a31dSJim Jagielski 224*0e16a31dSJim Jagielskidef b64(v, what): 225*0e16a31dSJim Jagielski try: 226*0e16a31dSJim Jagielski return base64.b64decode(v or "", validate=True) 227*0e16a31dSJim Jagielski except Exception: 228*0e16a31dSJim Jagielski raise DecryptError("%s is not base64: %r" % (what, v)) 229*0e16a31dSJim Jagielski 230*0e16a31dSJim Jagielski 231*0e16a31dSJim Jagielskidef entry_params(fe): 232*0e16a31dSJim Jagielski ed = fe.find(Q + "encryption-data") 233*0e16a31dSJim Jagielski alg, kd, sk = ed.find(Q + "algorithm"), ed.find(Q + "key-derivation"), ed.find(Q + "start-key-generation") 234*0e16a31dSJim Jagielski if alg is None or kd is None: 235*0e16a31dSJim Jagielski raise DecryptError("encryption-data lacks algorithm or key-derivation") 236*0e16a31dSJim Jagielski p = {"checksum": b64(ed.get(Q + "checksum"), "checksum") if ed.get(Q + "checksum") else None} 237*0e16a31dSJim Jagielski ctype = ed.get(Q + "checksum-type") 238*0e16a31dSJim Jagielski p["check"] = {"SHA1/1K": "sha1", M + "#sha1-1k": "sha1", M + "#sha256-1k": "sha256"}.get(ctype) 239*0e16a31dSJim Jagielski if p["check"] is None: 240*0e16a31dSJim Jagielski raise DecryptError("checksum-type %r" % ctype) 241*0e16a31dSJim Jagielski name = alg.get(Q + "algorithm-name") 242*0e16a31dSJim Jagielski keylen = {"http://www.w3.org/2001/04/xmlenc#aes128-cbc": 16, "http://www.w3.org/2001/04/xmlenc#aes192-cbc": 24, 243*0e16a31dSJim Jagielski "http://www.w3.org/2001/04/xmlenc#aes256-cbc": 32}.get(name) 244*0e16a31dSJim Jagielski if name in ("Blowfish CFB", M + "#blowfish"): 245*0e16a31dSJim Jagielski p["cipher"], keylen = "blowfish", 16 246*0e16a31dSJim Jagielski elif keylen: 247*0e16a31dSJim Jagielski p["cipher"] = "aes" 248*0e16a31dSJim Jagielski else: 249*0e16a31dSJim Jagielski raise DecryptError("algorithm-name %r" % name) 250*0e16a31dSJim Jagielski p["iv"] = b64(alg.get(Q + "initialisation-vector"), "initialisation-vector") 251*0e16a31dSJim Jagielski if kd.get(Q + "key-derivation-name") not in ("PBKDF2", M + "#pbkdf2"): 252*0e16a31dSJim Jagielski raise DecryptError("key-derivation-name %r" % kd.get(Q + "key-derivation-name")) 253*0e16a31dSJim Jagielski p["salt"] = b64(kd.get(Q + "salt"), "salt") 254*0e16a31dSJim Jagielski p["iter"] = int(kd.get(Q + "iteration-count")) 255*0e16a31dSJim Jagielski p["keylen"] = int(kd.get(Q + "key-size") or keylen) 256*0e16a31dSJim Jagielski sname = sk.get(Q + "start-key-generation-name") if sk is not None else "SHA1" 257*0e16a31dSJim Jagielski p["start"] = {"SHA1": "sha1", "http://www.w3.org/2000/09/xmldsig#sha1": "sha1", 258*0e16a31dSJim Jagielski "http://www.w3.org/2000/09/xmldsig#sha256": "sha256"}.get(sname) 259*0e16a31dSJim Jagielski if p["start"] is None: 260*0e16a31dSJim Jagielski raise DecryptError("start-key-generation-name %r" % sname) 261*0e16a31dSJim Jagielski return p 262*0e16a31dSJim Jagielski 263*0e16a31dSJim Jagielski 264*0e16a31dSJim Jagielskidef raw_entry(path, zi): 265*0e16a31dSJim Jagielski with open(path, "rb") as f: 266*0e16a31dSJim Jagielski f.seek(zi.header_offset) 267*0e16a31dSJim Jagielski h = f.read(30) 268*0e16a31dSJim Jagielski if h[:4] != b"PK\x03\x04": 269*0e16a31dSJim Jagielski raise DecryptError("no local header for %s" % zi.filename) 270*0e16a31dSJim Jagielski n, e = struct.unpack("<HH", h[26:30]) 271*0e16a31dSJim Jagielski f.seek(zi.header_offset + 30 + n + e) 272*0e16a31dSJim Jagielski return f.read(zi.compress_size) 273*0e16a31dSJim Jagielski 274*0e16a31dSJim Jagielski 275*0e16a31dSJim Jagielskidef decrypt_entry(raw, p, password, name, notes): 276*0e16a31dSJim Jagielski start = hashlib.new(p["start"], password.encode("utf-8")).digest() 277*0e16a31dSJim Jagielski key = hashlib.pbkdf2_hmac("sha1", start, p["salt"], p["iter"], p["keylen"]) 278*0e16a31dSJim Jagielski try: 279*0e16a31dSJim Jagielski plain = blowfish_cfb64_decrypt(key, p["iv"], raw) if p["cipher"] == "blowfish" else aes_cbc_decrypt(key, p["iv"], raw) 280*0e16a31dSJim Jagielski except DecryptError as e: 281*0e16a31dSJim Jagielski raise DecryptError("%s: %s (wrong password?)" % (name, e)) 282*0e16a31dSJim Jagielski if p["checksum"] is not None and hashlib.new(p["check"], plain[:1024]).digest() != p["checksum"]: 283*0e16a31dSJim Jagielski if p["check"] == "sha1" and sha1_rtl(plain[:1024]) == p["checksum"]: 284*0e16a31dSJim Jagielski notes.append("%s: checksum is the non-standard SHA-1 of main/sal (hashed length %d, mod 64 = %d)" 285*0e16a31dSJim Jagielski % (name, len(plain[:1024]), len(plain[:1024]) % 64)) 286*0e16a31dSJim Jagielski else: 287*0e16a31dSJim Jagielski raise DecryptError("%s: checksum mismatch (wrong password?)" % name) 288*0e16a31dSJim Jagielski try: 289*0e16a31dSJim Jagielski d = zlib.decompressobj(-15) 290*0e16a31dSJim Jagielski out = d.decompress(plain) + d.flush() 291*0e16a31dSJim Jagielski except zlib.error as e: 292*0e16a31dSJim Jagielski raise DecryptError("%s: decrypted data does not inflate (%s)" % (name, e)) 293*0e16a31dSJim Jagielski if not d.eof: 294*0e16a31dSJim Jagielski raise DecryptError("%s: deflate stream is truncated" % name) 295*0e16a31dSJim Jagielski return out 296*0e16a31dSJim Jagielski 297*0e16a31dSJim Jagielski 298*0e16a31dSJim Jagielskidef decrypt_package(src, dst, password, notes): 299*0e16a31dSJim Jagielski """Returns the number of encrypted entries decrypted; 0 means the package was copied unchanged.""" 300*0e16a31dSJim Jagielski z = zipfile.ZipFile(src) 301*0e16a31dSJim Jagielski mani = ET.fromstring(z.read("META-INF/manifest.xml")) 302*0e16a31dSJim Jagielski enc = {} 303*0e16a31dSJim Jagielski for fe in mani.findall(Q + "file-entry"): 304*0e16a31dSJim Jagielski if fe.find(Q + "encryption-data") is not None: 305*0e16a31dSJim Jagielski enc[fe.get(Q + "full-path")] = fe 306*0e16a31dSJim Jagielski data = {} 307*0e16a31dSJim Jagielski for zi in z.infolist(): 308*0e16a31dSJim Jagielski n = zi.filename 309*0e16a31dSJim Jagielski if n in enc: 310*0e16a31dSJim Jagielski if zi.compress_type != zipfile.ZIP_STORED: 311*0e16a31dSJim Jagielski raise DecryptError("%s: encrypted entry is not STORED" % n) 312*0e16a31dSJim Jagielski plain = decrypt_entry(raw_entry(src, zi), entry_params(enc[n]), password, n, notes) 313*0e16a31dSJim Jagielski size = enc[n].get(Q + "size") 314*0e16a31dSJim Jagielski if size is not None and int(size) != len(plain): 315*0e16a31dSJim Jagielski raise DecryptError("%s: manifest:size %s, decrypted %d bytes" % (n, size, len(plain))) 316*0e16a31dSJim Jagielski data[n] = plain 317*0e16a31dSJim Jagielski else: 318*0e16a31dSJim Jagielski data[n] = z.read(n) 319*0e16a31dSJim Jagielski for fe in enc.values(): 320*0e16a31dSJim Jagielski fe.remove(fe.find(Q + "encryption-data")) 321*0e16a31dSJim Jagielski fe.attrib.pop(Q + "size", None) 322*0e16a31dSJim Jagielski fe.text = None 323*0e16a31dSJim Jagielski missing = [p for p in enc if p not in data] 324*0e16a31dSJim Jagielski if missing: 325*0e16a31dSJim Jagielski raise DecryptError("manifest lists encrypted entries not in the zip: %s" % missing) 326*0e16a31dSJim Jagielski if enc: 327*0e16a31dSJim Jagielski data["META-INF/manifest.xml"] = ET.tostring(mani, encoding="UTF-8", xml_declaration=True) 328*0e16a31dSJim Jagielski tmp = dst + ".tmp" 329*0e16a31dSJim Jagielski with zipfile.ZipFile(tmp, "w") as o: 330*0e16a31dSJim Jagielski names = [zi.filename for zi in z.infolist()] 331*0e16a31dSJim Jagielski for n in sorted(names, key=lambda n: n != "mimetype"): 332*0e16a31dSJim Jagielski zi = zipfile.ZipInfo(n, date_time=z.getinfo(n).date_time) 333*0e16a31dSJim Jagielski zi.compress_type = zipfile.ZIP_STORED if n == "mimetype" or n.endswith("/") else zipfile.ZIP_DEFLATED 334*0e16a31dSJim Jagielski zi.external_attr = z.getinfo(n).external_attr 335*0e16a31dSJim Jagielski o.writestr(zi, data[n]) 336*0e16a31dSJim Jagielski os.replace(tmp, dst) 337*0e16a31dSJim Jagielski return len(enc) 338*0e16a31dSJim Jagielski 339*0e16a31dSJim Jagielski 340*0e16a31dSJim Jagielskidef selftest(): 341*0e16a31dSJim Jagielski bad = 0 342*0e16a31dSJim Jagielski 343*0e16a31dSJim Jagielski def check(name, got, want): 344*0e16a31dSJim Jagielski nonlocal bad 345*0e16a31dSJim Jagielski print(("ok " if got == want else "FAIL ") + name) 346*0e16a31dSJim Jagielski bad += got != want 347*0e16a31dSJim Jagielski for k, pt, ct in (("0000000000000000", "0000000000000000", "4ef997456198dd78"), 348*0e16a31dSJim Jagielski ("ffffffffffffffff", "ffffffffffffffff", "51866fd5b85ecb8a"), 349*0e16a31dSJim Jagielski ("3000000000000000", "1000000000000001", "7d856f9a613063f2")): 350*0e16a31dSJim Jagielski check("blowfish key " + k, Blowfish(bytes.fromhex(k)).encrypt_block(bytes.fromhex(pt)).hex(), ct) 351*0e16a31dSJim Jagielski pt = "00112233445566778899aabbccddeeff" 352*0e16a31dSJim Jagielski for k, ct in (("000102030405060708090a0b0c0d0e0f", "69c4e0d86a7b0430d8cdb78070b4c55a"), 353*0e16a31dSJim Jagielski ("000102030405060708090a0b0c0d0e0f1011121314151617", "dda97ca4864cdfe06eaf70a0ec0d7191"), 354*0e16a31dSJim Jagielski ("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "8ea2b7ca516745bfeafc49904b496089")): 355*0e16a31dSJim Jagielski check("aes-%d block" % (len(k) * 2), aes_decrypt_block(aes_schedule(bytes.fromhex(k)), bytes.fromhex(ct)).hex(), pt) 356*0e16a31dSJim Jagielski msgs = [(bytes(range(256)) * 2)[:n] for n in range(0, 300) if not 52 <= n % 64 <= 55] 357*0e16a31dSJim Jagielski check("sha1_rtl equals SHA-1 outside 52..55 mod 64", all(sha1_rtl(m) == hashlib.sha1(m).digest() for m in msgs), True) 358*0e16a31dSJim Jagielski check("sha1_rtl differs from SHA-1 at 52..55 mod 64", 359*0e16a31dSJim Jagielski all(sha1_rtl(bytes(n)) != hashlib.sha1(bytes(n)).digest() for n in (52, 53, 54, 55, 116, 886)), True) 360*0e16a31dSJim Jagielski check("pbkdf2-hmac-sha1 RFC 6070", hashlib.pbkdf2_hmac("sha1", b"password", b"salt", 2, 20).hex(), 361*0e16a31dSJim Jagielski "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957") 362*0e16a31dSJim Jagielski return bad == 0 363*0e16a31dSJim Jagielski 364*0e16a31dSJim Jagielski 365*0e16a31dSJim Jagielskidef main(argv): 366*0e16a31dSJim Jagielski if len(argv) == 2 and argv[1] == "--selftest": 367*0e16a31dSJim Jagielski return 0 if selftest() else 1 368*0e16a31dSJim Jagielski if len(argv) != 4: 369*0e16a31dSJim Jagielski print("usage: decrypt.py <in-dir> <out-dir> <password>", file=sys.stderr) 370*0e16a31dSJim Jagielski return 2 371*0e16a31dSJim Jagielski src, dst, password = argv[1:] 372*0e16a31dSJim Jagielski os.makedirs(dst, exist_ok=True) 373*0e16a31dSJim Jagielski done = copied = 0 374*0e16a31dSJim Jagielski failed, quirks = [], 0 375*0e16a31dSJim Jagielski for name in sorted(os.listdir(src)): 376*0e16a31dSJim Jagielski path = os.path.join(src, name) 377*0e16a31dSJim Jagielski if name.startswith(".") or not os.path.isfile(path) or not zipfile.is_zipfile(path): 378*0e16a31dSJim Jagielski print("skipped (not a zip package): %s" % name) 379*0e16a31dSJim Jagielski continue 380*0e16a31dSJim Jagielski notes = [] 381*0e16a31dSJim Jagielski try: 382*0e16a31dSJim Jagielski n = decrypt_package(path, os.path.join(dst, name), password, notes) 383*0e16a31dSJim Jagielski except (DecryptError, zipfile.BadZipFile, KeyError, ET.ParseError, ValueError) as e: 384*0e16a31dSJim Jagielski failed.append(name) 385*0e16a31dSJim Jagielski print("FAILED %s: %s" % (name, e), file=sys.stderr) 386*0e16a31dSJim Jagielski continue 387*0e16a31dSJim Jagielski for note in notes: 388*0e16a31dSJim Jagielski quirks += 1 389*0e16a31dSJim Jagielski print("note %s: %s" % (name, note)) 390*0e16a31dSJim Jagielski if n: 391*0e16a31dSJim Jagielski done += 1 392*0e16a31dSJim Jagielski print("decrypted %s (%d encrypted entries)" % (name, n)) 393*0e16a31dSJim Jagielski else: 394*0e16a31dSJim Jagielski copied += 1 395*0e16a31dSJim Jagielski print("copied %s (no encrypted entries)" % name) 396*0e16a31dSJim Jagielski print("%d decrypted, %d copied unchanged, %d failed, %d non-standard SHA-1 checksums" % (done, copied, len(failed), quirks)) 397*0e16a31dSJim Jagielski return 1 if failed else 0 398*0e16a31dSJim Jagielski 399*0e16a31dSJim Jagielski 400*0e16a31dSJim Jagielskiif __name__ == "__main__": 401*0e16a31dSJim Jagielski sys.exit(main(sys.argv)) 402