6 ===============================================================================
7 crypto -- Encryption Layer for the Deltatar Backup
8 ===============================================================================
12 - AES-GCM for the symmetric encryption;
17 - NIST Recommendation for Block Cipher Modes of Operation: Galois/Counter
19 http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
22 https://cryptome.org/2014/01/aes-gcm-v1.pdf
24 - Authentication weaknesses in GCM
25 http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/comments/CWC-GCM/Ferguson2.pdf
28 -------------------------------------------------------------------------------
30 Errors fall into roughly three categories:
32 - Cryptographical errors or invalid data.
34 - ``InvalidGCMTag`` (decryption failed on account of an invalid GCM
36 - ``InvalidIVFixedPart`` (IV fixed part of object not found in list),
37 - ``DuplicateIV`` (the IV of an object encrypted earlier was reused),
38 - ``NonConsecutiveIV`` (IVs of two encrypted objects are not
40 - ``DecryptionError`` (used in CLI decryption for presenting error
41 conditions to the user).
43 - Incorrect usage of the library.
45 - ``InvalidParameter`` (non-conforming user supplied parameter),
46 - ``InvalidHeader`` (data passed for reading not parsable into header),
47 - ``FormatError`` (cannot handle header or parameter version),
50 - Bad internal state. If one of these is encountered it means that a state
51 was reached that shouldn’t occur during normal processing.
56 Also, ``EndOfFile`` is used as a sentinel to communicate that a stream supplied
57 for reading is exhausted.
59 Initialization Vectors
60 -------------------------------------------------------------------------------
62 Initialization vectors are checked for reuse during the lifetime of a decryptor.
63 The fixed counters for metadata files cannot be reused and attempts to do so
64 will cause a DuplicateIV error. This means the length of objects encrypted with
65 a metadata counter is capped at 63 GB.
67 For ordinary, non-metadata payload, there is an optional mode with strict IV
68 checking that causes a crypto context to fail if an IV encountered or created
69 was already used for decrypting or encrypting, respectively, an earlier object.
70 Note that this mode can trigger false positives when decrypting non-linearly,
71 e. g. when traversing the same object multiple times. Since the crypto context
72 has no notion of a position in a PDT encrypted archive, this condition must be
73 sorted out downstream.
75 When encrypting with more than one Encrypt context special care must be taken
76 to prevent accidental reuse of IVs. The builtin protection against reuse is
77 only effective for objects encrypted with the same Encrypt handle. If multiple
78 Encrypt handles are used to encrypt with the same combination of password and
79 salt, the encryption becomes susceptible to birthday attacks (bound = 2^32 due
80 to the 64-bit random iv). Thus the use of multiple handles is discouraged.
84 -------------------------------------------------------------------------------
86 ``crypto.py`` may be invoked as a script for decrypting, validating, and
87 splitting PDT encrypted files. Consult the usage message for details.
91 Decrypt from stdin using the password ‘foo’: ::
93 $ crypto.py process foo -i - -o - <some-file.tar.gz.pdtcrypt >some-file.tar.gz
95 Output verbose information about the encrypted objects in the archive: ::
97 $ crypto.py process foo -v -i some-file.tar.gz.pdtcrypt -o /dev/null
98 PDT: decrypt from some-file.tar.gz.pdtcrypt
99 PDT: decrypt to /dev/null
100 PDT: source: file some-file.tar.gz.pdtcrypt
101 PDT: sink: file /dev/null
103 PDT: · version = 1 : 0100
104 PDT: · paramversion = 1 : 0100
105 PDT: · nacl : d270 b031 00d1 87e2 c946 610d 7b7f 7e5f
106 PDT: · iv : 02ee 3dd7 a963 1eb1 0100 0000
107 PDT: · ctsize = 591 : 4f02 0000 0000 0000
108 PDT: · tag : 5b2d 6d8b 8f82 4842 12fd 0b10 b6e3 369b
109 PDT: 64 decrypt obj no. 1, 591 B
110 PDT: · [64] 0% done, read block (591 B of 591 B remaining)
111 PDT: · decrypt ciphertext 591 B
112 PDT: · decrypt plaintext 591 B
116 Also, the mode *scrypt* allows deriving encryption keys. To calculate the
117 encryption key from the password ‘foo’ and the salt of the first object in a
118 PDT encrypted file: ::
120 $ crypto.py scrypt foo -i some-file.pdtcrypt
121 {"paramversion": 1, "salt": "Cqzbk48e3peEjzWto8D0yA==", "key": "JH9EkMwaM4x9F5aim5gK/Q=="}
123 The computed 16 byte key is given in hexadecimal notation in the value to
124 ``hash`` and can be fed into Python’s ``binascii.unhexlify()`` to obtain the
125 corresponding binary representation.
127 Note that in Scrypt hashing mode, no data integrity checks are being performed.
128 If the wrong password is given, a wrong key will be derived. Whether the password
129 was indeed correct can only be determined by decrypting. Note that since PDT
130 archives essentially consist of a stream of independent objects, the salt and
131 other parameters may change. Thus a key derived using above method from the
132 first object doesn’t necessarily apply to any of the subsequent objects.
141 from functools import reduce, partial
152 except ImportError as exn:
155 if __name__ == "__main__": ## Work around the import mechanism lest Python’s
156 pwd = os.getcwd() ## preference for local imports causes a cyclical
157 ## import (crypto → pylibscrypt → […] → ./tarfile → crypto).
158 sys.path = [ p for p in sys.path if p.find ("deltatar") < 0 ]
161 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
162 from cryptography.hazmat.backends import default_backend
166 __all__ = [ "hdr_make", "hdr_read", "hdr_fmt", "hdr_fmt_pretty"
168 , "PDTCRYPT_HDR_SIZE", "AES_GCM_IV_CNT_DATA"
169 , "AES_GCM_IV_CNT_INFOFILE", "AES_GCM_IV_CNT_INDEX"
173 ###############################################################################
175 ###############################################################################
177 class EndOfFile (Exception):
181 def __init__ (self, n=None, msg=None):
187 class InvalidParameter (Exception):
188 """Inputs not valid for PDT encryption."""
192 class InvalidHeader (Exception):
193 """Header not valid."""
197 class InvalidGCMTag (Exception):
199 The GCM tag calculated during decryption differs from that in the object
205 class InvalidIVFixedPart (Exception):
207 IV fixed part not in supplied list: either the backup is corrupt or the
208 current object does not belong to it.
213 class IVFixedPartError (Exception):
215 Error creating a unique IV fixed part: repeated calls to system RNG yielded
216 the same sequence of bytes as the last IV used.
221 class InvalidFileCounter (Exception):
223 When encrypting, an attempted reuse of a dedicated counter (info file,
224 index file) was caught.
229 class DuplicateIV (Exception):
231 During encryption, the current IV fixed part is identical to an already
232 existing IV (same prefix and file counter). This indicates tampering or
233 programmer error and cannot be recovered from.
238 class NonConsecutiveIV (Exception):
240 IVs not numbered consecutively. This is a hard error with strict IV
241 checking. Precludes random access to the encrypted objects.
246 class CiphertextTooLong (Exception):
248 An attempt was made to decrypt more data than the ciphertext size declared
249 in the object header.
254 class FormatError (Exception):
255 """Unusable parameters in header."""
259 class DecryptionError (Exception):
260 """Error during decryption with ``crypto.py`` on the command line."""
264 class Unreachable (Exception):
266 Makeshift __builtin_unreachable(); always a programmer error if
272 class InternalError (Exception):
273 """Errors not ascribable to bad user inputs or cryptography."""
277 ###############################################################################
278 ## crypto layer version
279 ###############################################################################
281 ENCRYPTION_PARAMETERS = \
283 { "kdf": ("dummy", 16)
284 , "enc": "passthrough" }
292 , "enc": "aes-gcm" } }
294 # Mode zero is unencrypted and only provided for testing purposes. nless
295 # the encryptor / decryptor are explicitly instructed to do so.
296 MIN_SECURE_PARAMETERS = 1
298 ###############################################################################
300 ###############################################################################
302 PDTCRYPT_HDR_MAGIC = b"PDTCRYPT"
304 PDTCRYPT_HDR_SIZE_MAGIC = 8 # 8
305 PDTCRYPT_HDR_SIZE_VERSION = 2 # 10
306 PDTCRYPT_HDR_SIZE_PARAMVERSION = 2 # 12
307 PDTCRYPT_HDR_SIZE_NACL = 16 # 28
308 PDTCRYPT_HDR_SIZE_IV = 12 # 40
309 PDTCRYPT_HDR_SIZE_CTSIZE = 8 # 48
310 PDTCRYPT_HDR_SIZE_TAG = 16 # 64 GCM auth tag
312 PDTCRYPT_HDR_SIZE = PDTCRYPT_HDR_SIZE_MAGIC + PDTCRYPT_HDR_SIZE_VERSION \
313 + PDTCRYPT_HDR_SIZE_PARAMVERSION + PDTCRYPT_HDR_SIZE_NACL \
314 + PDTCRYPT_HDR_SIZE_IV + PDTCRYPT_HDR_SIZE_CTSIZE \
315 + PDTCRYPT_HDR_SIZE_TAG # = 64
317 # precalculate offsets since Python can’t do constant folding over names
318 HDR_OFF_VERSION = PDTCRYPT_HDR_SIZE_MAGIC
319 HDR_OFF_PARAMVERSION = HDR_OFF_VERSION + PDTCRYPT_HDR_SIZE_VERSION
320 HDR_OFF_NACL = HDR_OFF_PARAMVERSION + PDTCRYPT_HDR_SIZE_PARAMVERSION
321 HDR_OFF_IV = HDR_OFF_NACL + PDTCRYPT_HDR_SIZE_NACL
322 HDR_OFF_CTSIZE = HDR_OFF_IV + PDTCRYPT_HDR_SIZE_IV
323 HDR_OFF_TAG = HDR_OFF_CTSIZE + PDTCRYPT_HDR_SIZE_CTSIZE
327 FMT_I2N_IV = "<8sL" # 8 random bytes ‖ 32 bit counter
328 FMT_I2N_HDR = ("<" # host byte order
332 "16s" # sodium chloride
338 AES_KEY_SIZE = 16 # b"0123456789abcdef"
339 AES_KEY_SIZE_B64 = 24 # b'MDEyMzQ1Njc4OWFiY2RlZg=='
341 AES_GCM_MAX_SIZE = (1 << 36) - (1 << 5) # 2^39 - 2^8 b ≅ 64 GB.
342 # Source: NIST SP 800-38D section 5.2.1.1
343 # https://crypto.stackexchange.com/questions/31793/plain-text-size-limits-for-aes-gcm-mode-just-64gb
345 PDTCRYPT_MAX_OBJ_SIZE_DEFAULT = 63 * (1 << 30) # 63 GB
346 PDTCRYPT_MAX_OBJ_SIZE = PDTCRYPT_MAX_OBJ_SIZE_DEFAULT
348 # index and info files are written on-the fly while encrypting so their
349 # counters must be available in advance
350 AES_GCM_IV_CNT_INFOFILE = 1 # constant
351 AES_GCM_IV_CNT_INDEX = AES_GCM_IV_CNT_INFOFILE + 1
352 AES_GCM_IV_CNT_DATA = AES_GCM_IV_CNT_INDEX + 1 # also for multivolume
353 AES_GCM_IV_CNT_MAX_DEFAULT = 0xffFFffFF
354 AES_GCM_IV_CNT_MAX = AES_GCM_IV_CNT_MAX_DEFAULT
356 # IV structure and generation
357 PDTCRYPT_IV_GEN_MAX_RETRIES = 10 # ×
358 PDTCRYPT_IV_FIXEDPART_SIZE = 8 # B
359 PDTCRYPT_IV_COUNTER_SIZE = 4 # B
361 # secret type: PW of string | KEY of char [16]
362 PDTCRYPT_SECRET_PW = 0
363 PDTCRYPT_SECRET_KEY = 1
365 ###############################################################################
367 ###############################################################################
373 # , paramversion : u16
379 # fn hdr_read (f : handle) -> hdrinfo;
380 # fn hdr_make (f : handle, h : hdrinfo) -> IOResult<usize>;
381 # fn hdr_fmt (h : hdrinfo) -> String;
386 Read bytes as header structure.
388 If the input could not be interpreted as a header, fail with
393 mag, version, paramversion, nacl, iv, ctsize, tag = \
394 struct.unpack (FMT_I2N_HDR, data)
395 except Exception as exn:
396 raise InvalidHeader ("error unpacking header from [%r]: %s"
397 % (binascii.hexlify (data), str (exn)))
399 if mag != PDTCRYPT_HDR_MAGIC:
400 raise InvalidHeader ("bad magic in header: expected [%s], got [%s]"
401 % (PDTCRYPT_HDR_MAGIC, mag))
404 { "version" : version
405 , "paramversion" : paramversion
413 def hdr_read_stream (instr):
415 Read header from stream at the current position.
417 Fail with ``InvalidHeader`` if insufficient bytes were read from the
418 stream, or if the content could not be interpreted as a header.
420 data = instr.read(PDTCRYPT_HDR_SIZE)
424 elif ldata != PDTCRYPT_HDR_SIZE:
425 raise InvalidHeader ("hdr_read_stream: expected %d B, received %d B"
426 % (PDTCRYPT_HDR_SIZE, ldata))
427 return hdr_read (data)
430 def hdr_from_params (version, paramversion, nacl, iv, ctsize, tag):
432 Assemble the necessary values into a PDTCRYPT header.
434 :type version: int to fit uint16_t
435 :type paramversion: int to fit uint16_t
436 :type nacl: bytes to fit uint8_t[16]
437 :type iv: bytes to fit uint8_t[12]
438 :type size: int to fit uint64_t
439 :type tag: bytes to fit uint8_t[16]
441 buf = bytearray (PDTCRYPT_HDR_SIZE)
442 bufv = memoryview (buf)
445 struct.pack_into (FMT_I2N_HDR, bufv, 0,
447 version, paramversion, nacl, iv, ctsize, tag)
448 except Exception as exn:
449 return False, "error assembling header: %s" % str (exn)
451 return True, bytes (buf)
454 def hdr_make_dummy (s):
456 Create a header sized block of bytes initialized to a value derived from a
457 string. Used to verify we’ve jumped back correctly to the actual position
458 of the object header.
460 c = reduce (lambda a, c: a + ord(c), s, 0) % 0xFF
461 return bytes (bytearray (struct.pack ("B", c)) * PDTCRYPT_HDR_SIZE)
466 Assemble a header from the given header structure.
468 return hdr_from_params (version=hdr.get("version"),
469 paramversion=hdr.get("paramversion"),
470 nacl=hdr.get("nacl"), iv=hdr.get("iv"),
471 ctsize=hdr.get("ctsize"), tag=hdr.get("tag"))
474 HDR_FMT = "I2n_header { version: %d, paramversion: %d, nacl: %s[%d]," \
475 " iv: %s[%d], ctsize: %d, tag: %s[%d] }"
478 """Format a header structure into readable output."""
479 return HDR_FMT % (h["version"], h["paramversion"],
480 binascii.hexlify (h["nacl"]), len(h["nacl"]),
481 binascii.hexlify (h["iv"]), len(h["iv"]),
483 binascii.hexlify (h["tag"]), len(h["tag"]))
486 def hex_spaced_of_bytes (b):
487 """Format bytes object, hexdump style."""
488 return " ".join ([ "%.2x%.2x" % (c1, c2)
489 for c1, c2 in zip (b[0::2], b[1::2]) ]) \
490 + (len (b) | 1 == len (b) and " %.2x" % b[-1] or "") # odd lengths
493 def hdr_iv_counter (h):
494 """Extract the variable part of the IV of the given header."""
495 _fixed, cnt = struct.unpack (FMT_I2N_IV, h ["iv"])
499 def hdr_iv_fixed (h):
500 """Extract the fixed part of the IV of the given header."""
501 fixed, _cnt = struct.unpack (FMT_I2N_IV, h ["iv"])
505 hdr_dump = hex_spaced_of_bytes
509 """version = %-4d : %s
510 paramversion = %-4d : %s
517 def hdr_fmt_pretty (h):
519 Format header structure into multi-line representation of its contents and
520 their raw representation. (Omit the implicit “PDTCRYPT” magic bytes that
521 precede every header.)
523 return HDR_FMT_PRETTY \
525 hex_spaced_of_bytes (struct.pack (FMT_UINT16_LE, h["version"])),
527 hex_spaced_of_bytes (struct.pack (FMT_UINT16_LE, h["paramversion"])),
528 hex_spaced_of_bytes (h["nacl"]),
529 hex_spaced_of_bytes (h["iv"]),
531 hex_spaced_of_bytes (struct.pack (FMT_UINT64_LE, h["ctsize"])),
532 hex_spaced_of_bytes (h["tag"]))
534 IV_FMT = "((f %s) (c %d))"
537 """Format the two components of an IV in a readable fashion."""
538 fixed, cnt = struct.unpack (FMT_I2N_IV, iv)
539 return IV_FMT % (binascii.hexlify (fixed).decode (), cnt)
542 ###############################################################################
544 ###############################################################################
546 class Location (object):
550 def restore_loc_fmt (loc):
552 % (loc.n, loc.offset)
554 def locate_hdr_candidates (fd):
556 Walk over instances of the magic string in the payload, collecting their
557 positions. If the offset of the first found instance is not zero, the file
558 begins with leading garbage. Used by desaster recovery.
560 :return: The list of offsets in the file.
564 mm = mmap.mmap(fd, 0, mmap.MAP_SHARED, mmap.PROT_READ)
567 pos = mm.find (PDTCRYPT_HDR_MAGIC, pos)
576 HDR_CAND_GOOD = 0 # header marks begin of valid object
577 HDR_CAND_FISHY = 1 # inconclusive (tag mismatch, obj overlap etc.)
578 HDR_CAND_JUNK = 2 # not a header / object unreadable
581 { HDR_CAND_GOOD : "valid"
582 , HDR_CAND_FISHY : "fishy"
583 , HDR_CAND_JUNK : "junk"
587 def verdict_fmt (vdt):
588 return HDR_VERDICT_NAME [vdt]
591 def inspect_hdr (fd, off):
593 Attempt to parse a header in *fd* at position *off*.
595 Returns a verdict about the quality of that header plus the parsed header
599 _ = os.lseek (fd, off, os.SEEK_SET)
601 if os.lseek (fd, 0, os.SEEK_CUR) != off:
602 if PDTCRYPT_VERBOSE is True:
603 noise ("PDT: %d → dismissed (lseek() past EOF)" % off)
604 return HDR_CAND_JUNK, None
606 raw = os.read (fd, PDTCRYPT_HDR_SIZE)
607 if len (raw) != PDTCRYPT_HDR_SIZE:
608 if PDTCRYPT_VERBOSE is True:
609 noise ("PDT: %d → dismissed (EOF inside header)" % off)
610 return HDR_CAND_JUNK, None
614 except InvalidHeader as exn:
615 if PDTCRYPT_VERBOSE is True:
616 noise ("PDT: %d → dismissed (invalid: [%s])" % (off, str (exn)))
617 return HDR_CAND_JUNK, None
619 obj0 = off + PDTCRYPT_HDR_SIZE
620 objX = obj0 + hdr ["ctsize"]
622 eof = os.lseek (fd, 0, os.SEEK_END)
624 if PDTCRYPT_VERBOSE is True:
625 noise ("PDT: %d → EOF inside object (%d≤%d≤%d); adjusting size to "
626 "%d" % (off, obj0, eof, objX, (eof - obj0)))
627 # try reading up to the end
628 hdr ["ctsize"] = eof - obj0
629 return HDR_CAND_FISHY, hdr
631 return HDR_CAND_GOOD, hdr
634 def try_decrypt (ifd, off, hdr, secret, ofd=-1):
636 Attempt to decrypt the object in the (seekable) descriptor *ifd* starting
637 at *off* using the metadata in *hdr* and *secret*. An output fd can be
638 specified with *ofd*; if it is *-1* – the default –, the decrypted payload
641 Always creates a fresh decryptor, so validation steps across objects don’t
644 Errors during GCM tag validation are ignored. Used by desaster recovery.
646 ctleft = hdr ["ctsize"]
650 if ks == PDTCRYPT_SECRET_PW:
651 decr = Decrypt (password=secret [1])
652 elif ks == PDTCRYPT_SECRET_KEY:
654 decr = Decrypt (key=key)
661 os.lseek (ifd, pos, os.SEEK_SET)
664 cnksiz = min (ctleft, PDTCRYPT_BLOCKSIZE)
665 cnk = os.read (ifd, cnksiz)
668 pt = decr.process (cnk)
673 except InvalidGCMTag:
674 noise ("PDT: GCM tag mismatch for object %d–%d"
675 % (off, off + hdr ["ctsize"]))
676 if len (pt) > 0 and ofd != -1:
679 except Exception as exn:
680 noise ("PDT: error decrypting object %d–%d@%d, %d B remaining [%s]"
681 % (off, off + hdr ["ctsize"], pos, ctleft, exn))
687 def readable_objects_offsets (ifd, secret, cands):
689 From a list of candidates, locate the ones that mark the start of actual
690 readable PDTCRYPT objects.
694 for i, cand in enumerate (cands):
695 vdt, hdr = inspect_hdr (ifd, cand)
696 if vdt == HDR_CAND_JUNK:
697 pass # ignore unreadable ones
698 elif vdt in [HDR_CAND_GOOD, HDR_CAND_FISHY]:
699 ctsize = hdr ["ctsize"]
700 off0 = cand + PDTCRYPT_HDR_SIZE
701 ok = try_decrypt (ifd, off0, hdr, secret) == ctsize
703 good.append ((cand, off0 + ctsize))
705 overlap = find_overlaps (good)
707 return [ g [0] for g in good ]
710 def reconstruct_offsets (fname, secret):
711 ifd = os.open (fname, os.O_RDONLY)
714 cands = locate_hdr_candidates (ifd)
715 return readable_objects_offsets (ifd, secret, cands)
720 ###############################################################################
722 ###############################################################################
724 def make_secret (password=None, key=None):
726 Safely create a “secret” value that consists either of a key or a password.
727 Inputs are validated: the password is accepted as (UTF-8 encoded) bytes or
728 string; for the key only a bytes object of the proper size or a base64
729 encoded string thereof is accepted.
731 If both are provided, the key is preferred over the password; no checks are
732 performed whether the key is derived from the password.
734 :returns: secret value if inputs were acceptable | None otherwise.
737 if isinstance (key, str) is True:
738 key = key.encode ("utf-8")
739 if isinstance (key, bytes) is True:
740 if len (key) == AES_KEY_SIZE:
741 return (PDTCRYPT_SECRET_KEY, key)
742 if len (key) == AES_KEY_SIZE * 2:
744 key = binascii.unhexlify (key)
745 return (PDTCRYPT_SECRET_KEY, key)
746 except binascii.Error: # garbage in string
748 if len (key) == AES_KEY_SIZE_B64:
750 key = base64.b64decode (key)
751 # the base64 processor is very tolerant and allows for
752 # arbitrary trailing and leading data thus the data obtained
753 # must be checked for the proper length
754 if len (key) == AES_KEY_SIZE:
755 return (PDTCRYPT_SECRET_KEY, key)
756 except binascii.Error: # “incorrect padding”
758 elif password is not None:
759 if isinstance (password, str) is True:
760 return (PDTCRYPT_SECRET_PW, password)
761 elif isinstance (password, bytes) is True:
763 password = password.decode ("utf-8")
764 return (PDTCRYPT_SECRET_PW, password)
765 except UnicodeDecodeError:
771 ###############################################################################
772 ## passthrough / null encryption
773 ###############################################################################
775 class PassthroughCipher (object):
777 tag = struct.pack ("<QQ", 0, 0)
779 def __init__ (self) : pass
781 def update (self, b) : return b
783 def finalize (self) : return b""
785 def finalize_with_tag (self, _) : return b""
787 ###############################################################################
788 ## convenience wrapper
789 ###############################################################################
792 def kdf_dummy (klen, password, _nacl):
794 Fake KDF for testing purposes that is called when parameter version zero is
797 q, r = divmod (klen, len (password))
798 if isinstance (password, bytes) is False:
799 password = password.encode ()
800 return password * q + password [:r], b""
803 SCRYPT_KEY_MEMO = { } # static because needed for both the info file and the archive
806 def kdf_scrypt (params, password, nacl):
808 Wrapper for the Scrypt KDF, corresponds to parameter version one. The
809 computation result is memoized based on the inputs to facilitate spawning
810 multiple encryption contexts.
815 dkLen = params["dkLen"]
818 nacl = os.urandom (params["NaCl_LEN"])
820 key_parms = (password, nacl, N, r, p, dkLen)
821 global SCRYPT_KEY_MEMO
822 if key_parms not in SCRYPT_KEY_MEMO:
823 SCRYPT_KEY_MEMO [key_parms] = \
824 pylibscrypt.scrypt (password, nacl, N, r, p, dkLen)
825 return SCRYPT_KEY_MEMO [key_parms], nacl
828 def kdf_by_version (paramversion=None, defs=None):
830 Pick the KDF handler corresponding to the parameter version or the
833 :rtype: function (password : str, nacl : str) -> str
835 if paramversion is not None:
836 defs = ENCRYPTION_PARAMETERS.get(paramversion, None)
838 raise InvalidParameter ("no encryption parameters for version %r"
840 (kdf, params) = defs["kdf"]
842 if kdf == "scrypt" : fn = kdf_scrypt
843 elif kdf == "dummy" : fn = kdf_dummy
845 raise ValueError ("key derivation method %r unknown" % kdf)
846 return partial (fn, params)
849 ###############################################################################
851 ###############################################################################
853 def scrypt_hashsource (pw, ins):
855 Calculate the SCRYPT hash from the password and the information contained
856 in the first header found in ``ins``.
858 This does not validate whether the first object is encrypted correctly.
860 if isinstance (pw, str) is True:
862 elif isinstance (pw, bytes) is False:
863 raise InvalidParameter ("password must be a string, not %s"
865 if isinstance (ins, io.BufferedReader) is False and \
866 isinstance (ins, io.FileIO) is False:
867 raise InvalidParameter ("file to hash must be opened in “binary” mode")
870 hdr = hdr_read_stream (ins)
871 except EndOfFile as exn:
872 noise ("PDT: malformed input: end of file reading first object header")
877 pver = hdr ["paramversion"]
878 if PDTCRYPT_VERBOSE is True:
879 noise ("PDT: salt of first object : %s" % binascii.hexlify (nacl))
880 noise ("PDT: parameter version of archive : %d" % pver)
883 defs = ENCRYPTION_PARAMETERS.get(pver, None)
884 kdfname, params = defs ["kdf"]
885 if kdfname != "scrypt":
886 noise ("PDT: input is not an SCRYPT archive")
889 kdf = kdf_by_version (None, defs)
890 except ValueError as exn:
891 noise ("PDT: object has unknown parameter version %d" % pver)
893 hsh, _void = kdf (pw, nacl)
895 return hsh, nacl, hdr ["version"], pver
898 def scrypt_hashfile (pw, fname):
900 Calculate the SCRYPT hash from the password and the information contained
901 in the first header found in the given file. The header is read only at
904 with deptdcrypt_mk_stream (PDTCRYPT_SOURCE, fname or "-") as ins:
905 hsh, _void, _void, _void = scrypt_hashsource (pw, ins)
909 ###############################################################################
911 ###############################################################################
913 class Crypto (object):
915 Encryption context to remain alive throughout an entire tarfile pass.
920 cnt = None # file counter (uint32_t != 0)
921 iv = None # current IV
922 fixed = None # accu for 64 bit fixed parts of IV
923 used_ivs = None # tracks IVs
924 strict_ivs = False # if True, panic on duplicate or non-consecutive object IV
927 insecure = False # allow plaintext parameters
934 info_counter_used = False
935 index_counter_used = False
937 def __init__ (self, *al, **akv):
938 self.used_ivs = set ()
939 self.set_parameters (*al, **akv)
942 def next_fixed (self):
947 def set_object_counter (self, cnt=None):
949 Safely set the internal counter of encrypted objects. Numerous
952 The same counter may not be reused in combination with one IV fixed
953 part. This is validated elsewhere in the IV handling.
955 Counter zero is invalid. The first two counters are reserved for
956 metadata. The implementation does not allow for splitting metadata
957 files over multiple encrypted objects. (This would be possible by
958 assigning new fixed parts.) Thus in a Deltatar backup there is at most
959 one object with a counter value of one and two. On creation of a
960 context, the initial counter may be chosen. The globals
961 ``AES_GCM_IV_CNT_INFOFILE`` and ``AES_GCM_IV_CNT_INDEX`` can be used to
962 request one of the reserved values. If one of these values has been
963 used, any further attempt of setting the counter to that value will
964 be rejected with an ``InvalidFileCounter`` exception.
966 Out of bounds values (i. e. below one and more than the maximum of 2³²)
967 cause an ``InvalidParameter`` exception to be thrown.
970 self.cnt = AES_GCM_IV_CNT_DATA
972 if cnt == 0 or cnt > AES_GCM_IV_CNT_MAX + 1:
973 raise InvalidParameter ("invalid counter value %d requested: "
974 "acceptable values are from 1 to %d"
975 % (cnt, AES_GCM_IV_CNT_MAX))
976 if cnt == AES_GCM_IV_CNT_INFOFILE:
977 if self.info_counter_used is True:
978 raise InvalidFileCounter ("attempted to reuse info file "
979 "counter %d: must be unique" % cnt)
980 self.info_counter_used = True
981 elif cnt == AES_GCM_IV_CNT_INDEX:
982 if self.index_counter_used is True:
983 raise InvalidFileCounter ("attempted to reuse index file "
984 "counter %d: must be unique" % cnt)
985 self.index_counter_used = True
986 if cnt <= AES_GCM_IV_CNT_MAX:
989 # cnt == AES_GCM_IV_CNT_MAX + 1 → wrap
990 self.cnt = AES_GCM_IV_CNT_DATA
994 def set_parameters (self, password=None, key=None, paramversion=None,
995 nacl=None, counter=None, strict_ivs=False,
998 Configure the internal state of a crypto context. Not intended for
1001 A parameter version indicating passthrough (plaintext) mode is rejected
1002 with an ``InvalidParameter`` unless ``insecure`` is set.
1005 self.set_object_counter (counter)
1006 self.strict_ivs = strict_ivs
1008 self.insecure = insecure
1010 if paramversion is not None:
1011 if self.insecure is False \
1012 and paramversion < MIN_SECURE_PARAMETERS:
1013 raise InvalidParameter \
1014 ("set_parameters: requested parameter version %d but "
1015 "plaintext encryption disallowed in secure context!"
1017 self.paramversion = paramversion
1020 self.key, self.nacl = key, nacl
1023 if password is not None:
1024 if isinstance (password, bytes) is False:
1025 password = str.encode (password)
1026 self.password = password
1027 if paramversion is None and nacl is None:
1028 # postpone key setup until first header is available
1030 kdf = kdf_by_version (paramversion)
1032 self.key, self.nacl = kdf (password, nacl)
1035 def process (self, buf):
1037 Encrypt / decrypt a buffer. Invokes the ``.update()`` method on the
1038 wrapped encryptor or decryptor, respectively.
1040 The Cryptography exception ``AlreadyFinalized`` is translated to an
1041 ``InternalError`` at this point. It may occur in sound code when the GC
1042 closes an encrypting stream after an error. Everywhere else it must be
1045 if self.enc is None:
1046 raise RuntimeError ("process: context not initialized")
1047 self.stats ["in"] += len (buf)
1049 out = self.enc.update (buf)
1050 except cryptography.exceptions.AlreadyFinalized as exn:
1051 raise InternalError (exn)
1052 self.stats ["out"] += len (out)
1056 def next (self, password, paramversion, nacl):
1058 Prepare for encrypting another object: Reset the data counters and
1059 change the configuration in case one of the variable parameters differs
1060 from the last object.
1064 self.stats ["obj"] += 1
1066 if ( self.paramversion != paramversion
1067 or self.password != password
1068 or self.nacl != nacl):
1069 self.set_parameters (password=password, paramversion=paramversion,
1070 nacl=nacl, strict_ivs=self.strict_ivs,
1071 insecure=self.insecure)
1074 def counters (self):
1076 Access the data counters.
1078 return self.stats ["obj"], self.stats ["in"], self.stats ["out"]
1083 Clear the current context regardless of its finalization state. The
1084 next operation must be ``.next()``.
1089 def get_used_ivs (self):
1091 Get the set of IVs that were used so far during the lifetime of
1092 this context. Useful to check for IV reuse if multiple encryption
1093 contexts were used independently.
1095 return self.used_ivs
1098 def reset_last_iv (self):
1100 Implemented only for decryptor; no-op otherwise.
1105 class Encrypt (Crypto):
1111 def __init__ (self, version, paramversion, password=None, key=None, nacl=None,
1112 counter=AES_GCM_IV_CNT_DATA, strict_ivs=False, insecure=False):
1114 The ctor will throw immediately if one of the parameters does not conform
1115 to our expectations.
1117 :type version: int to fit uint16_t
1118 :type paramversion: int to fit uint16_t
1119 :param password: mutually exclusive with ``key``
1120 :type password: bytes
1121 :param key: mutually exclusive with ``password``
1124 :type counter: initial object counter the values
1125 ``AES_GCM_IV_CNT_INFOFILE`` and
1126 ``AES_GCM_IV_CNT_INDEX`` are unique in each backup set
1127 and cannot be reused even with different fixed parts.
1128 :type strict_ivs: bool
1129 :param strict_ivs: Enable paranoid tracking of IVs.
1130 :type insecure: bool
1131 :param insecure: whether to permit passthrough mode
1133 *Security considerations*: The ``class Encrypt`` handle guarantees that
1134 all random parts (first eight bytes) of the IVs used for encrypting
1135 objects are unique. This guarantee does *not* apply across handles if
1136 multiple handles are used with the same combination of password and
1137 salt. Thus, use of multiple handles with the same combination of password
1138 and salt is subject to birthday attacks with a bound of 2^32. To avoid
1139 collisions, the application should keep the number of handles as low
1140 as possible and check for reuse by comparing the set of IVs used of all
1141 handles that were created (accessible using the ``get_used_ivs`` method).
1143 if password is None and key is None \
1144 or password is not None and key is not None :
1145 raise InvalidParameter ("__init__: need either key or password")
1148 if isinstance (key, bytes) is False:
1149 raise InvalidParameter ("__init__: key must be provided as "
1150 "bytes, not %s" % type (key))
1152 raise InvalidParameter ("__init__: salt must be provided along "
1153 "with encryption key")
1154 else: # password, no key
1155 if isinstance (password, str) is False:
1156 raise InvalidParameter ("__init__: password must be a string, not %s"
1158 if len (password) == 0:
1159 raise InvalidParameter ("__init__: supplied empty password but not "
1160 "permitted for PDT encrypted files")
1162 if isinstance (version, int) is False:
1163 raise InvalidParameter ("__init__: version number must be an "
1164 "integer, not %s" % type (version))
1166 raise InvalidParameter ("__init__: version number must be a "
1167 "nonnegative integer, not %d" % version)
1169 if isinstance (paramversion, int) is False:
1170 raise InvalidParameter ("__init__: crypto parameter version number "
1171 "must be an integer, not %s"
1172 % type (paramversion))
1173 if paramversion < 0:
1174 raise InvalidParameter ("__init__: crypto parameter version number "
1175 "must be a nonnegative integer, not %d"
1178 if nacl is not None:
1179 if isinstance (nacl, bytes) is False:
1180 raise InvalidParameter ("__init__: salt given, but of type %s "
1181 "instead of bytes" % type (nacl))
1182 # salt length would depend on the actual encryption so it can’t be
1183 # validated at this point
1185 self.version = version
1186 self.paramenc = ENCRYPTION_PARAMETERS.get (paramversion) ["enc"]
1188 super().__init__ (password, key, paramversion, nacl, counter=counter,
1189 strict_ivs=strict_ivs, insecure=insecure)
1192 def next_fixed (self, retries=PDTCRYPT_IV_GEN_MAX_RETRIES):
1194 Generate the next IV fixed part by reading eight bytes from
1195 ``/dev/urandom``. The buffer so obtained is tested against the fixed
1196 parts used so far to prevent accidental reuse of IVs. After a
1197 configurable number of attempts to create a unique fixed part, it will
1198 refuse to continue with an ``IVFixedPartError``. This is unlikely to
1199 ever happen on a normal system but may detect an issue with the random
1202 The list of fixed parts that were used by the context at hand can be
1203 accessed through the ``.fixed`` list. Its last element is the fixed
1204 part currently in use.
1208 fp = os.urandom (PDTCRYPT_IV_FIXEDPART_SIZE)
1209 if fp not in self.fixed:
1210 self.fixed.append (fp)
1213 raise IVFixedPartError ("error obtaining a unique IV fixed part from "
1214 "/dev/urandom; giving up after %d tries" % i)
1219 Construct a 12-bytes IV from the current fixed part and the object
1222 return struct.pack(FMT_I2N_IV, self.fixed [-1], self.cnt)
1225 def next (self, filename=None, counter=None):
1227 Prepare for encrypting the next incoming object. Update the counter
1228 and put together the IV, possibly changing prefixes. Then create the
1231 The argument ``counter`` can be used to specify a file counter for this
1232 object. Unless it is one of the reserved values, the counter of
1233 subsequent objects will be computed from this one.
1235 If this is the first object in a series, ``filename`` is required,
1236 otherwise it is reused if not present. The value is used to derive a
1237 header sized placeholder to use until after encryption when all the
1238 inputs to construct the final header are available. This is then
1239 matched in ``.done()`` against the value found at the position of the
1240 header. The motivation for this extra check is primarily to assist
1241 format debugging: It makes stray headers easy to spot in malformed
1244 if filename is None:
1245 if self.lastinfo is None:
1246 raise InvalidParameter ("next: filename is mandatory for "
1248 filename, _dummy = self.lastinfo
1250 if isinstance (filename, str) is False:
1251 raise InvalidParameter ("next: filename must be a string, no %s"
1253 if counter is not None:
1254 if isinstance (counter, int) is False:
1255 raise InvalidParameter ("next: the supplied counter is of "
1256 "invalid type %s; please pass an "
1257 "integer instead" % type (counter))
1258 self.set_object_counter (counter)
1260 self.iv = self.iv_make ()
1261 if self.paramenc == "aes-gcm":
1263 ( algorithms.AES (self.key)
1264 , modes.GCM (self.iv)
1265 , backend = default_backend ()) \
1267 elif self.paramenc == "passthrough":
1268 self.enc = PassthroughCipher ()
1270 raise InvalidParameter ("next: parameter version %d not known"
1271 % self.paramversion)
1272 hdrdum = hdr_make_dummy (filename)
1273 self.lastinfo = (filename, hdrdum)
1275 self.check_duplicate_iv (self.iv)
1277 super().next (self.password, self.paramversion, self.nacl)
1279 self.set_object_counter (self.cnt + 1)
1283 def check_duplicate_iv (self, iv):
1285 Add an IV (the 12 byte representation as in the header) to the list. With
1286 strict checking enabled, this will throw a ``DuplicateIV``. Depending on
1287 the context, this may indicate a serious error (IV reuse).
1289 IVs are only tracked in strict_ivs mode.
1291 if self.strict_ivs is False:
1294 if iv in self.used_ivs:
1295 raise DuplicateIV ("iv %s was reused" % iv_fmt (iv))
1296 # vi has not been used before; add to collection
1297 self.used_ivs.add (iv)
1300 def done (self, cmpdata):
1302 Complete encryption of an object. After this has been called, attempts
1303 of encrypting further data will cause an error until ``.next()`` is
1306 Returns a 64 bytes buffer containing the object header including all
1307 values including the “late” ones e. g. the ciphertext size and the
1310 if isinstance (cmpdata, bytes) is False:
1311 raise InvalidParameter ("done: comparison input expected as bytes, "
1312 "not %s" % type (cmpdata))
1313 if self.lastinfo is None:
1314 raise RuntimeError ("done: encryption context not initialized")
1315 filename, hdrdum = self.lastinfo
1316 if cmpdata != hdrdum:
1317 raise RuntimeError ("done: bad sync of header for object %d: "
1318 "preliminary data does not match; this likely "
1319 "indicates a wrongly repositioned stream"
1321 data = self.enc.finalize ()
1322 self.stats ["out"] += len (data)
1323 self.ctsize += len (data)
1324 ok, hdr = hdr_from_params (self.version, self.paramversion, self.nacl,
1325 self.iv, self.ctsize, self.enc.tag)
1327 raise InternalError ("error constructing header: %r" % hdr)
1328 return data, hdr, self.fixed
1331 def process (self, buf):
1333 Encrypt a chunk of plaintext with the active encryptor. Returns the
1334 size of the input consumed. This **must** be checked downstream. If the
1335 maximum possible object size has been reached, the current context must
1336 be finalized and a new one established before any further data can be
1337 encrypted. The second argument is the remainder of the plaintext that
1338 was not encrypted for the caller to use immediately after the new
1341 if isinstance (buf, bytes) is False:
1342 raise InvalidParameter ("process: expected byte buffer, not %s"
1345 newptsize = self.ptsize + bsize
1346 diff = newptsize - PDTCRYPT_MAX_OBJ_SIZE
1349 newptsize = PDTCRYPT_MAX_OBJ_SIZE
1350 self.ptsize = newptsize
1351 data = super().process (buf [:bsize])
1352 self.ctsize += len (data)
1356 class Decrypt (Crypto):
1358 tag = None # GCM tag, part of header
1359 last_iv = None # check consecutive ivs in strict mode
1362 def __init__ (self, password=None, key=None, counter=None, fixedparts=None,
1363 strict_ivs=True, insecure=False):
1365 Sanitizing ctor for the decryption context. ``fixedparts`` specifies a
1366 list of IV fixed parts accepted during decryption. If a fixed part is
1367 encountered that is not in the list, decryption will fail.
1369 :param password: mutually exclusive with ``key``
1370 :type password: bytes
1371 :param key: mutually exclusive with ``password``
1373 :type counter: initial object counter the values
1374 ``AES_GCM_IV_CNT_INFOFILE`` and
1375 ``AES_GCM_IV_CNT_INDEX`` are unique in each backup set
1376 and cannot be reused even with different fixed parts.
1377 :type fixedparts: bytes list
1378 :type strict_ivs: bool
1379 :param strict_ivs: fail if IVs of decrypted objects are not linearly
1381 :type insecure: bool
1382 :param insecure: whether to process objects encrypted in
1383 passthrough mode (*``paramversion`` < 1*)
1385 *Security considerations*: The ``strict_ivs`` setting protects against
1386 ciphertext reordering and injection attacks. For this to work it relies
1387 on a property of how the object counters are created during encryption.
1388 If multiple ``Encrypt`` handles have been used during encryption, this
1389 is property is unlikely to apply as it would require manual management
1390 of counters across Encrypt handles. In these cases it may thus be
1391 necessary to disable the ```strict_ivs`` protection.
1393 if password is None and key is None \
1394 or password is not None and key is not None :
1395 raise InvalidParameter ("__init__: need either key or password")
1398 if isinstance (key, bytes) is False:
1399 raise InvalidParameter ("__init__: key must be provided as "
1400 "bytes, not %s" % type (key))
1401 else: # password, no key
1402 if isinstance (password, str) is False:
1403 raise InvalidParameter ("__init__: password must be a string, not %s"
1405 if len (password) == 0:
1406 raise InvalidParameter ("__init__: supplied empty password but not "
1407 "permitted for PDT encrypted files")
1409 if fixedparts is not None:
1410 if isinstance (fixedparts, list) is False:
1411 raise InvalidParameter ("__init__: IV fixed parts must be "
1412 "supplied as list, not %s"
1413 % type (fixedparts))
1414 self.fixed = fixedparts
1417 super().__init__ (password=password, key=key, counter=counter,
1418 strict_ivs=strict_ivs, insecure=insecure)
1421 def valid_fixed_part (self, iv):
1423 Check if a fixed part was already seen.
1425 # check if fixed part is known
1426 fixed, _cnt = struct.unpack (FMT_I2N_IV, iv)
1427 i = bisect.bisect_left (self.fixed, fixed)
1428 return i != len (self.fixed) and self.fixed [i] == fixed
1431 def reset_last_iv (self):
1433 Force a new IV sequence start. The last IV counter will be set from the
1434 next IV encountered and the check for consecutive IVs will be suppressed.
1436 The intended use is backup volume boundaries or handling batches of
1437 objects encrypted with ``Encrypt`` handles initialized with different
1438 initial counter values.
1442 def check_consecutive_iv (self, iv):
1444 Check whether the counter part of the given IV is indeed the successor
1445 of the currently present counter. This should always be the case for
1446 the objects in a well formed PDT archive but should not be enforced
1447 when decrypting out-of-order.
1449 fixed, cnt = struct.unpack (FMT_I2N_IV, iv)
1450 if self.strict_ivs is True \
1451 and self.last_iv is not None \
1452 and self.last_iv [0] == fixed \
1453 and self.last_iv [1] + 1 != cnt:
1454 raise NonConsecutiveIV ("iv %s counter not successor of "
1455 "last object (expected %d, found %d)"
1456 % (iv_fmt (iv), self.last_iv [1] + 1, cnt))
1457 self.last_iv = (fixed, cnt)
1460 def next (self, hdr):
1462 Start decrypting the next object. The PDTCRYPT header for the object
1463 can be given either as already parsed object or as bytes.
1465 if isinstance (hdr, bytes) is True:
1466 hdr = hdr_read (hdr)
1467 elif isinstance (hdr, dict) is False:
1468 # this won’t catch malformed specs though
1469 raise InvalidParameter ("next: wrong type of parameter hdr: "
1470 "expected bytes or spec, got %s"
1473 paramversion = hdr ["paramversion"]
1477 ctsize = hdr ["ctsize"]
1479 raise InvalidHeader ("next: not a header %r" % hdr)
1481 if ctsize > PDTCRYPT_MAX_OBJ_SIZE:
1482 raise InvalidHeader ("next: ciphertext size %d exceeds maximum "
1484 % (ctsize, PDTCRYPT_MAX_OBJ_SIZE))
1486 self.hdr_ctsize = ctsize
1488 super().next (self.password, paramversion, nacl)
1489 if self.fixed is not None and self.valid_fixed_part (iv) is False:
1490 raise InvalidIVFixedPart ("iv %s has invalid fixed part"
1493 self.check_consecutive_iv (iv)
1496 defs = ENCRYPTION_PARAMETERS.get (paramversion, None)
1498 raise FormatError ("header contains unknown parameter version %d; "
1499 "maybe the file was created by a more recent "
1500 "version of Deltatar" % paramversion)
1502 if enc == "aes-gcm":
1504 ( algorithms.AES (self.key)
1505 , modes.GCM (iv, tag=self.tag)
1506 , backend = default_backend ()) \
1508 elif enc == "passthrough":
1509 self.enc = PassthroughCipher ()
1511 raise InternalError ("encryption parameter set %d refers to unknown "
1512 "mode %r" % (paramversion, enc))
1513 self.set_object_counter (self.cnt + 1)
1516 def done (self, tag=None):
1518 Stop decryption of the current object and finalize it with the active
1519 context. This will throw an *InvalidGCMTag* exception to indicate that
1520 the authentication tag does not match the data. If the tag is correct,
1521 the rest of the plaintext is returned.
1526 data = self.enc.finalize ()
1528 if isinstance (tag, bytes) is False:
1529 raise InvalidParameter ("done: wrong type of parameter "
1530 "tag: expected bytes, got %s"
1532 data = self.enc.finalize_with_tag (self.tag)
1533 except cryptography.exceptions.InvalidTag:
1534 raise InvalidGCMTag ("done: tag mismatch of object %d: %s "
1535 "rejected by finalize ()"
1536 % (self.cnt, binascii.hexlify (self.tag)))
1537 self.ptsize += len (data)
1538 self.stats ["out"] += len (data)
1540 assert self.ctsize == self.ptsize == self.hdr_ctsize
1545 def process (self, buf):
1547 Decrypt the bytes object *buf* with the active decryptor.
1549 if isinstance (buf, bytes) is False:
1550 raise InvalidParameter ("process: expected byte buffer, not %s"
1552 self.ctsize += len (buf)
1553 if self.ctsize > self.hdr_ctsize:
1554 raise CiphertextTooLong ("process: object length exceeded: got "
1555 "%d B but header specfiies %d B"
1556 % (self.ctsize, self.hdr_ctsize))
1558 data = super().process (buf)
1559 self.ptsize += len (data)
1563 ###############################################################################
1565 ###############################################################################
1567 def _patch_global (glob, vow, n=None):
1569 Adapt upper file counter bound for testing IV logic. Completely unsafe.
1571 assert vow == "I am fully aware that this will void my warranty."
1572 r = globals () [glob]
1574 n = globals () [glob + "_DEFAULT"]
1575 globals () [glob] = n
1578 _testing_set_AES_GCM_IV_CNT_MAX = \
1579 partial (_patch_global, "AES_GCM_IV_CNT_MAX")
1581 _testing_set_PDTCRYPT_MAX_OBJ_SIZE = \
1582 partial (_patch_global, "PDTCRYPT_MAX_OBJ_SIZE")
1584 def open2_dump_file (fname, dir_fd, force=False):
1587 oflags = os.O_CREAT | os.O_WRONLY
1589 oflags |= os.O_TRUNC
1594 outfd = os.open (fname, oflags,
1595 stat.S_IRUSR | stat.S_IWUSR, dir_fd=dir_fd)
1596 except FileExistsError as exn:
1597 noise ("PDT: refusing to overwrite existing file %s" % fname)
1599 raise RuntimeError ("destination file %s already exists" % fname)
1600 if PDTCRYPT_VERBOSE is True:
1601 noise ("PDT: new output file %s (fd=%d)" % (fname, outfd))
1605 ###############################################################################
1606 ## freestanding invocation
1607 ###############################################################################
1609 PDTCRYPT_SUB_PROCESS = 0
1610 PDTCRYPT_SUB_SCRYPT = 1
1611 PDTCRYPT_SUB_SCAN = 2
1612 PDTCRYPT_SUB_IVCHECK = 3
1615 { "process" : PDTCRYPT_SUB_PROCESS
1616 , "scrypt" : PDTCRYPT_SUB_SCRYPT
1617 , "scan" : PDTCRYPT_SUB_SCAN
1618 , "ivcheck" : PDTCRYPT_SUB_IVCHECK }
1620 PDTCRYPT_DECRYPT = 1 << 0 # decrypt archive with password
1621 PDTCRYPT_SPLIT = 1 << 1 # split archive into individual objects
1622 PDTCRYPT_HASH = 1 << 2 # output scrypt hash for file and given password
1624 PDTCRYPT_SPLITNAME = "pdtcrypt-object-%d.bin"
1625 PDTCRYPT_RESCUENAME = "pdtcrypt-rescue-object-%0.5d.bin"
1627 PDTCRYPT_VERBOSE = False
1628 PDTCRYPT_STRICTIVS = False
1629 PDTCRYPT_OVERWRITE = False
1630 PDTCRYPT_BLOCKSIZE = 1 << 12
1635 PDTCRYPT_DEFAULT_VER = 1
1636 PDTCRYPT_DEFAULT_PVER = 1
1638 # scrypt hashing output control
1639 PDTCRYPT_SCRYPT_INTRANATOR = 0
1640 PDTCRYPT_SCRYPT_PARAMETERS = 1
1641 PDTCRYPT_SCRYPT_DEFAULT = PDTCRYPT_SCRYPT_INTRANATOR
1643 PDTCRYPT_SCRYPT_FORMAT = \
1644 { "i2n" : PDTCRYPT_SCRYPT_INTRANATOR
1645 , "params" : PDTCRYPT_SCRYPT_PARAMETERS }
1647 PDTCRYPT_TT_COLUMNS = 80 # assume standard terminal
1649 class PDTDecryptionError (Exception):
1650 """Decryption failed."""
1652 class PDTSplitError (Exception):
1653 """Decryption failed."""
1656 def noise (*a, **b):
1657 print (file=sys.stderr, *a, **b)
1660 class PassthroughDecryptor (object):
1662 curhdr = None # write current header on first data write
1664 def __init__ (self):
1665 if PDTCRYPT_VERBOSE is True:
1666 noise ("PDT: no encryption; data passthrough")
1668 def next (self, hdr):
1669 ok, curhdr = hdr_make (hdr)
1671 raise PDTDecryptionError ("bad header %r" % hdr)
1672 self.curhdr = curhdr
1675 if self.curhdr is not None:
1679 def process (self, d):
1680 if self.curhdr is not None:
1686 def check_ivs (ifs):
1688 Walk the objects in the given reader, validating uniqueness and
1689 consecutiveness of the IVs in the object headers.
1691 As the IVs are metadata this does not require decryption.
1699 hdr = hdr_read_stream (ifs)
1700 except EndOfFile as exn:
1706 fixed, cnt = struct.unpack (FMT_I2N_IV, cur)
1708 if PDTCRYPT_VERBOSE is True:
1709 noise ("PDT: obj %d, iv %s" % (objs, iv_fmt (cur)))
1711 if last is not None:
1712 if fixed != last [0]:
1713 noise ("PDT: obj %d, fixed part changed last: %s → this: %s"
1715 binascii.hexlify (last [0]),
1716 binascii.hexlify (fixed)))
1717 if cnt != last [1] + 1:
1718 raise NonConsecutiveIV ("iv %s counter not successor of "
1719 "last object (expected %d, found %d)"
1720 % (iv_fmt (cur), last [1] + 1, cnt))
1723 raise DuplicateIV ("iv %s was reused" % iv_fmt (cur))
1728 ifs.read (hdr ["ctsize"])
1733 def depdtcrypt (mode, secret, ins, outs):
1735 Remove PDTCRYPT layer from all objects encrypted with the secret. Used on a
1736 Deltatar backup this will yield a (possibly Gzip compressed) tarball.
1738 ctleft = -1 # length of ciphertext to consume
1739 ctcurrent = 0 # total ciphertext of current object
1740 total_obj = 0 # total number of objects read
1741 total_pt = 0 # total plaintext bytes
1742 total_ct = 0 # total ciphertext bytes
1743 total_read = 0 # total bytes read
1744 outfile = None # Python file object for output
1746 if mode & PDTCRYPT_DECRYPT: # decryptor
1748 if ks == PDTCRYPT_SECRET_PW:
1749 decr = Decrypt (password=secret [1], strict_ivs=PDTCRYPT_STRICTIVS)
1750 elif ks == PDTCRYPT_SECRET_KEY:
1752 decr = Decrypt (key=key, strict_ivs=PDTCRYPT_STRICTIVS)
1754 raise InternalError ("‘%d’ does not specify a valid kind of secret"
1757 decr = PassthroughDecryptor ()
1760 """Dummy for non-split mode: output file does not vary."""
1763 if mode & PDTCRYPT_SPLIT:
1764 def nextout (outfile):
1766 We were passed an fd as outs for accessing the destination
1767 directory where extracted archive components are supposed
1772 if PDTCRYPT_VERBOSE is True:
1773 noise ("PDT: no output file to close at this point")
1775 if PDTCRYPT_VERBOSE is True:
1776 noise ("PDT: release output file %r" % outfile)
1777 # cleanup happens automatically by the GC; the next
1778 # line will error out on account of an invalid fd
1781 assert total_obj > 0
1782 fname = PDTCRYPT_SPLITNAME % total_obj
1784 outfd = open2_dump_file (fname, outs, force=PDTCRYPT_OVERWRITE)
1785 except RuntimeError as exn:
1786 raise PDTSplitError (exn)
1787 return os.fdopen (outfd, "wb", closefd=True)
1791 """ESPIPE is normal on non-seekable stdio stream."""
1794 except OSError as exn:
1795 if exn.errno == errno.ESPIPE:
1798 def out (pt, outfile):
1802 if PDTCRYPT_VERBOSE is True:
1803 noise ("PDT:\t· decrypt plaintext %d B" % (npt))
1805 nn = outfile.write (pt)
1806 except OSError as exn: # probably ENOSPC
1807 raise DecryptionError ("error (%s)" % exn)
1809 raise DecryptionError ("write aborted after %d of %d B" % (nn, npt))
1813 # current object completed; in a valid archive this marks either
1814 # the start of a new header or the end of the input
1815 if ctleft == 0: # current object requires finalization
1816 if PDTCRYPT_VERBOSE is True:
1817 noise ("PDT: %d finalize" % tell (ins))
1820 except InvalidGCMTag as exn:
1821 raise DecryptionError ("error finalizing object %d (%d B): "
1822 "%r" % (total_obj, len (pt), exn)) \
1825 if PDTCRYPT_VERBOSE is True:
1826 noise ("PDT:\t· object validated")
1828 if PDTCRYPT_VERBOSE is True:
1829 noise ("PDT: %d hdr" % tell (ins))
1831 hdr = hdr_read_stream (ins)
1832 total_read += PDTCRYPT_HDR_SIZE
1833 except EndOfFile as exn:
1834 total_read += exn.remainder
1835 if total_ct + total_obj * PDTCRYPT_HDR_SIZE != total_read:
1836 raise PDTDecryptionError ("ciphertext processed (%d B) plus "
1837 "overhead (%d × %d B) does not match "
1838 "the number of bytes read (%d )"
1839 % (total_ct, total_obj, PDTCRYPT_HDR_SIZE,
1841 # the single good exit
1842 return total_read, total_obj, total_ct, total_pt
1843 except InvalidHeader as exn:
1844 raise PDTDecryptionError ("invalid header at position %d in %r "
1845 "(%s)" % (tell (ins), exn, ins))
1846 if PDTCRYPT_VERBOSE is True:
1847 pretty = hdr_fmt_pretty (hdr)
1848 noise (reduce (lambda a, e: (a + "\n" if a else "") + "PDT:\t· " + e,
1849 pretty.splitlines (), ""))
1850 ctcurrent = ctleft = hdr ["ctsize"]
1854 total_obj += 1 # used in file counter with split mode
1856 # finalization complete or skipped in case of first object in
1857 # stream; create a new output file if necessary
1858 outfile = nextout (outfile)
1860 if PDTCRYPT_VERBOSE is True:
1861 noise ("PDT: %d decrypt obj no. %d, %d B"
1862 % (tell (ins), total_obj, ctleft))
1864 # always allocate a new buffer since python-cryptography doesn’t allow
1865 # passing a bytearray :/
1866 nexpect = min (ctleft, PDTCRYPT_BLOCKSIZE)
1867 if PDTCRYPT_VERBOSE is True:
1868 noise ("PDT:\t· [%d] %d%% done, read block (%d B of %d B remaining)"
1870 100 - ctleft * 100 / (ctcurrent > 0 and ctcurrent or 1),
1872 ct = ins.read (nexpect)
1876 raise EndOfFile (nct,
1877 "hit EOF after %d of %d B in block [%d:%d); "
1878 "%d B ciphertext remaining for object no %d"
1879 % (nct, nexpect, off, off + nexpect, ctleft,
1885 if PDTCRYPT_VERBOSE is True:
1886 noise ("PDT:\t· decrypt ciphertext %d B" % (nct))
1887 pt = decr.process (ct)
1891 def deptdcrypt_mk_stream (kind, path):
1892 """Create stream from file or stdio descriptor."""
1893 if kind == PDTCRYPT_SINK:
1895 if PDTCRYPT_VERBOSE is True: noise ("PDT: sink: stdout")
1896 return sys.stdout.buffer
1898 if PDTCRYPT_VERBOSE is True: noise ("PDT: sink: file %s" % path)
1899 return io.FileIO (path, "w")
1900 if kind == PDTCRYPT_SOURCE:
1902 if PDTCRYPT_VERBOSE is True: noise ("PDT: source: stdin")
1903 return sys.stdin.buffer
1905 if PDTCRYPT_VERBOSE is True: noise ("PDT: source: file %s" % path)
1906 return io.FileIO (path, "r")
1908 raise ValueError ("bogus stream “%s” / %s" % (kind, path))
1911 def mode_depdtcrypt (mode, secret, ins, outs):
1913 total_read, total_obj, total_ct, total_pt = \
1914 depdtcrypt (mode, secret, ins, outs)
1915 except DecryptionError as exn:
1916 noise ("PDT: Decryption failed:")
1918 noise ("PDT: “%s”" % exn)
1920 noise ("PDT: Did you specify the correct key / password?")
1923 except PDTSplitError as exn:
1924 noise ("PDT: Split operation failed:")
1926 noise ("PDT: “%s”" % exn)
1928 noise ("PDT: Hint: target directory should be empty.")
1932 if PDTCRYPT_VERBOSE is True:
1933 noise ("PDT: decryption successful" )
1934 noise ("PDT: %.10d bytes read" % total_read)
1935 noise ("PDT: %.10d objects decrypted" % total_obj )
1936 noise ("PDT: %.10d bytes ciphertext" % total_ct )
1937 noise ("PDT: %.10d bytes plaintext" % total_pt )
1943 def mode_scrypt (pw, ins=None, nacl=None, fmt=PDTCRYPT_SCRYPT_INTRANATOR):
1945 paramversion = PDTCRYPT_DEFAULT_PVER
1947 hsh, nacl, version, paramversion = scrypt_hashsource (pw, ins)
1948 defs = ENCRYPTION_PARAMETERS.get(paramversion, None)
1950 nacl = binascii.unhexlify (nacl)
1951 defs = ENCRYPTION_PARAMETERS.get(paramversion, None)
1952 version = PDTCRYPT_DEFAULT_VER
1954 kdfname, params = defs ["kdf"]
1956 kdf = kdf_by_version (None, defs)
1957 hsh, _void = kdf (pw, nacl)
1961 if fmt == PDTCRYPT_SCRYPT_INTRANATOR:
1962 out = json.dumps ({ "salt" : base64.b64encode (nacl).decode ()
1963 , "key" : base64.b64encode (hsh) .decode ()
1964 , "paramversion" : paramversion })
1965 elif fmt == PDTCRYPT_SCRYPT_PARAMETERS:
1966 out = json.dumps ({ "salt" : binascii.hexlify (nacl).decode ()
1967 , "key" : binascii.hexlify (hsh) .decode ()
1968 , "version" : version
1969 , "scrypt_params" : { "N" : params ["N"]
1970 , "r" : params ["r"]
1971 , "p" : params ["p"]
1972 , "dkLen" : params ["dkLen"] } })
1974 raise RuntimeError ("bad scrypt output scheme %r" % fmt)
1979 def noise_output_candidates (cands, indent=8, cols=PDTCRYPT_TT_COLUMNS):
1981 Print a list of offsets without garbling the terminal too much.
1983 The indent is counted from column zero; if it is wide enough, the “PDT: ”
1984 marker will be prepended, considered part of the indentation.
1988 idt = " " * indent if indent < 5 else "PDT: " + " " * (indent - 5)
1993 init = True # prevent leading separator
1996 raise ValueError ("the requested indentation exceeds the line "
1997 "width by %d" % (indent - wd))
2007 if lpos > wd: # line break
2023 SLICE_START = 1 # ordering is important to have starts of intervals
2024 SLICE_END = 0 # sorted before equal ends
2026 def find_overlaps (slices):
2028 Find overlapping slices: iterate open/close points of intervals, tracking
2029 the ones open at any time.
2032 inside = set () # of indices into bounds
2033 ovrlp = set () # of indices into bounds
2035 for i, s in enumerate (slices):
2036 bounds.append ((s [0], SLICE_START, i))
2037 bounds.append ((s [1], SLICE_END , i))
2038 bounds = sorted (bounds)
2042 if val [1] == SLICE_START:
2045 if len (inside) > 1: # closing one that overlapped
2049 return [ slices [i] for i in ovrlp ]
2052 def mode_ivcheck (ifd):
2055 total_obj = check_ivs (ifd)
2056 except (NonConsecutiveIV, DuplicateIV) as exn:
2057 noise ("PDT: Detected inconsistent initialization vectors")
2059 noise ("PDT: “%s”" % exn)
2063 except Exception as exn:
2064 noise ("PDT: Hit an error unrelated to checking IVs")
2066 noise ("PDT: “%s”" % exn)
2070 noise ("PDT: Successfully traversed %d encrypted objects in input."
2073 noise ("PDT: All IVs consecutive and unique.")
2076 def mode_scan (secret, fname, outs=None, nacl=None):
2078 Dissect a binary file, looking for PDTCRYPT headers and objects.
2080 If *outs* is supplied, recoverable data will be dumped into the specified
2084 ifd = os.open (fname, os.O_RDONLY)
2085 except FileNotFoundError:
2086 noise ("PDT: failed to open %s readonly" % fname)
2091 if PDTCRYPT_VERBOSE is True:
2092 noise ("PDT: scan for potential sync points")
2093 cands = locate_hdr_candidates (ifd)
2094 if len (cands) == 0:
2095 noise ("PDT: scan complete: input does not contain potential PDT "
2096 "headers; giving up.")
2098 if PDTCRYPT_VERBOSE is True:
2099 noise ("PDT: scan complete: found %d candidates:" % len (cands))
2100 noise_output_candidates (cands)
2105 junk, todo, slices = [], [], []
2110 vdt, hdr = inspect_hdr (ifd, cand)
2112 vdts = verdict_fmt (vdt)
2114 if vdt == HDR_CAND_JUNK:
2115 noise ("PDT: obj %d: %s object: bad header, skipping" % vdts)
2118 off0 = cand + PDTCRYPT_HDR_SIZE
2119 if PDTCRYPT_VERBOSE is True:
2120 noise ("PDT: obj %d: read payload @%d" % (nobj, off0))
2121 pretty = hdr_fmt_pretty (hdr)
2122 noise (reduce (lambda a, e: (a + "\n" if a else "") + "PDT:\t· " + e,
2123 pretty.splitlines (), ""))
2126 if outs is not None:
2127 ofname = PDTCRYPT_RESCUENAME % nobj
2128 ofd = open2_dump_file (ofname, outs, force=PDTCRYPT_OVERWRITE)
2130 ctsize = hdr ["ctsize"]
2132 l = try_decrypt (ifd, off0, hdr, secret, ofd=ofd)
2134 slices.append ((off0, off0 + l))
2138 if vdt == HDR_CAND_GOOD and ok is True:
2139 noise ("PDT: %d → ✓ %s object %d–%d"
2140 % (cand, vdts, off0, off0 + ctsize))
2141 elif vdt == HDR_CAND_FISHY and ok is True:
2142 noise ("PDT: %d → × %s object %d–%d, corrupt header"
2143 % (cand, vdts, off0, off0 + ctsize))
2144 elif vdt == HDR_CAND_GOOD and ok is False:
2145 noise ("PDT: %d → × %s object %d–%d, problematic payload"
2146 % (cand, vdts, off0, off0 + ctsize))
2147 elif vdt == HDR_CAND_FISHY and ok is False:
2148 noise ("PDT: %d → × %s object %d–%d, corrupt header, problematic "
2149 "ciphertext" % (cand, vdts, off0, off0 + ctsize))
2156 noise ("PDT: all headers ok")
2158 noise ("PDT: %d candidates not parseable as headers:" % len (junk))
2159 noise_output_candidates (junk)
2161 overlap = find_overlaps (slices)
2162 if len (overlap) > 0:
2163 noise ("PDT: %d objects overlapping others" % len (overlap))
2164 for slice in overlap:
2165 noise ("PDT: × %d→%d" % (slice [0], slice [1]))
2168 def usage (err=False):
2172 indent = ' ' * len (SELF)
2173 out ("usage: %s SUBCOMMAND { --help" % SELF)
2174 out (" %s | [ -v ] { -p PASSWORD | -k KEY }" % indent)
2175 out (" %s [ { -i | --in } { - | SOURCE } ]" % indent)
2176 out (" %s [ { -n | --nacl } { SALT } ]" % indent)
2177 out (" %s [ { -o | --out } { - | DESTINATION } ]" % indent)
2178 out (" %s [ -D | --no-decrypt ] [ -S | --split ]" % indent)
2179 out (" %s [ -f | --format ]" % indent)
2182 out ("\t\tSUBCOMMAND main mode: { process | scrypt | scan | ivcheck }")
2184 out ("\t\t process: extract objects from PDT archive")
2185 out ("\t\t scrypt: calculate hash from password and first object")
2186 out ("\t\t scan: scan input for PDTCRYPT headers")
2187 out ("\t\t ivcheck: check whether IVs are consecutive")
2188 out ("\t\t-p PASSWORD password to derive the encryption key from")
2189 out ("\t\t-k KEY encryption key as 16 bytes in hexadecimal notation")
2190 out ("\t\t-s enforce strict handling of initialization vectors")
2191 out ("\t\t-i SOURCE file name to read from")
2192 out ("\t\t-o DESTINATION file to write output to")
2193 out ("\t\t-n SALT provide salt for scrypt mode in hex encoding")
2194 out ("\t\t-v print extra info")
2195 out ("\t\t-S split into files at object boundaries; this")
2196 out ("\t\t requires DESTINATION to refer to directory")
2197 out ("\t\t-D PDT header and ciphertext passthrough")
2198 out ("\t\t-f format of SCRYPT hash output (“default” or “parameters”)")
2200 out ("\tinstead of filenames, “-” may used to specify stdin / stdout")
2202 sys.exit ((err is True) and 42 or 0)
2212 def parse_argv (argv):
2213 global PDTCRYPT_OVERWRITE
2215 mode = PDTCRYPT_DECRYPT
2221 scrypt_format = PDTCRYPT_SCRYPT_DEFAULT
2224 SELF = os.path.basename (next (argvi))
2227 rawsubcmd = next (argvi)
2228 subcommand = PDTCRYPT_SUB [rawsubcmd]
2229 except StopIteration:
2230 bail ("ERROR: subcommand required")
2232 bail ("ERROR: invalid subcommand “%s” specified" % rawsubcmd)
2238 except StopIteration:
2239 bail ("ERROR: argument list incomplete")
2241 def checked_secret (s):
2246 bail ("ERROR: encountered “%s” but secret already given" % arg)
2249 if arg in [ "-h", "--help" ]:
2252 elif arg in [ "-v", "--verbose", "--wtf" ]:
2253 global PDTCRYPT_VERBOSE
2254 PDTCRYPT_VERBOSE = True
2255 elif arg in [ "-i", "--in", "--source" ]:
2256 insspec = checked_arg ()
2257 if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypt from %s" % insspec)
2258 elif arg in [ "-p", "--password" ]:
2259 arg = checked_arg ()
2260 checked_secret (make_secret (password=arg))
2261 if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypting with password")
2263 if subcommand == PDTCRYPT_SUB_PROCESS:
2264 if arg in [ "-s", "--strict-ivs" ]:
2265 global PDTCRYPT_STRICTIVS
2266 PDTCRYPT_STRICTIVS = True
2267 elif arg in [ "-o", "--out", "--dest", "--sink" ]:
2268 outsspec = checked_arg ()
2269 if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypt to %s" % outsspec)
2270 elif arg in [ "-f", "--force" ]:
2271 PDTCRYPT_OVERWRITE = True
2272 if PDTCRYPT_VERBOSE is True: noise ("PDT: overwrite existing files")
2273 elif arg in [ "-S", "--split" ]:
2274 mode |= PDTCRYPT_SPLIT
2275 if PDTCRYPT_VERBOSE is True: noise ("PDT: split files")
2276 elif arg in [ "-D", "--no-decrypt" ]:
2277 mode &= ~PDTCRYPT_DECRYPT
2278 if PDTCRYPT_VERBOSE is True: noise ("PDT: not decrypting")
2279 elif arg in [ "-k", "--key" ]:
2280 arg = checked_arg ()
2281 checked_secret (make_secret (key=arg))
2282 if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypting with key")
2284 bail ("ERROR: unexpected positional argument “%s”" % arg)
2285 elif subcommand == PDTCRYPT_SUB_SCRYPT:
2286 if arg in [ "-n", "--nacl", "--salt" ]:
2287 nacl = checked_arg ()
2288 if PDTCRYPT_VERBOSE is True: noise ("PDT: salt key with %s" % nacl)
2289 elif arg in [ "-f", "--format" ]:
2290 arg = checked_arg ()
2292 scrypt_format = PDTCRYPT_SCRYPT_FORMAT [arg]
2294 bail ("ERROR: invalid scrypt output format %s" % arg)
2295 if PDTCRYPT_VERBOSE is True:
2296 noise ("PDT: scrypt output format “%s”" % scrypt_format)
2298 bail ("ERROR: unexpected positional argument “%s”" % arg)
2299 elif subcommand == PDTCRYPT_SUB_SCAN:
2300 if arg in [ "-o", "--out", "--dest", "--sink" ]:
2301 outsspec = checked_arg ()
2302 if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypt to %s" % outsspec)
2303 elif arg in [ "-f", "--force" ]:
2304 PDTCRYPT_OVERWRITE = True
2305 if PDTCRYPT_VERBOSE is True: noise ("PDT: overwrite existing files")
2307 bail ("ERROR: unexpected positional argument “%s”" % arg)
2310 if PDTCRYPT_VERBOSE is True:
2311 noise ("ERROR: no password or key specified, trying $PDTCRYPT_PASSWORD")
2312 epw = os.getenv ("PDTCRYPT_PASSWORD")
2314 checked_secret (make_secret (password=epw.strip ()))
2317 if PDTCRYPT_VERBOSE is True:
2318 noise ("ERROR: no password or key specified, trying $PDTCRYPT_KEY")
2319 ek = os.getenv ("PDTCRYPT_KEY")
2321 checked_secret (make_secret (key=ek.strip ()))
2324 if subcommand == PDTCRYPT_SUB_IVCHECK:
2326 elif subcommand == PDTCRYPT_SUB_SCRYPT:
2327 bail ("ERROR: scrypt hash mode requested but no password given")
2328 elif mode & PDTCRYPT_DECRYPT:
2329 bail ("ERROR: decryption requested but no password given")
2331 if mode & PDTCRYPT_SPLIT and outsspec is None:
2332 bail ("ERROR: split mode is incompatible with stdout sink "
2335 if subcommand == PDTCRYPT_SUB_SCAN and outsspec is None:
2336 pass # no output by default in scan mode
2337 elif mode & PDTCRYPT_SPLIT or subcommand == PDTCRYPT_SUB_SCAN:
2338 # destination must be directory
2340 bail ("ERROR: mode is incompatible with stdout sink")
2343 os.makedirs (outsspec, 0o700)
2344 except FileExistsError:
2345 # if it’s a directory with appropriate perms, everything is
2346 # good; otherwise, below invocation of open(2) will fail
2348 outs = os.open (outsspec, os.O_DIRECTORY, 0o600)
2349 except FileNotFoundError as exn:
2350 bail ("ERROR: cannot create target directory “%s”" % outsspec)
2351 except NotADirectoryError as exn:
2352 bail ("ERROR: target path “%s” is not a directory" % outsspec)
2354 outs = deptdcrypt_mk_stream (PDTCRYPT_SINK, outsspec or "-")
2356 if subcommand == PDTCRYPT_SUB_SCAN:
2358 bail ("ERROR: please supply an input file for scanning")
2360 bail ("ERROR: input must be seekable; please specify a file")
2361 return True, partial (mode_scan, secret, insspec, outs, nacl=nacl)
2363 if subcommand == PDTCRYPT_SUB_IVCHECK:
2365 bail ("ERROR: please supply an input file for checking ivs")
2367 if subcommand == PDTCRYPT_SUB_SCRYPT:
2368 if secret [0] == PDTCRYPT_SECRET_KEY:
2369 bail ("ERROR: scrypt mode requires a password")
2370 if insspec is not None and nacl is not None \
2371 or insspec is None and nacl is None :
2372 bail ("ERROR: please supply either an input file or "
2377 if insspec is not None or subcommand != PDTCRYPT_SUB_SCRYPT:
2378 ins = deptdcrypt_mk_stream (PDTCRYPT_SOURCE, insspec or "-")
2380 if subcommand == PDTCRYPT_SUB_IVCHECK:
2381 return True, partial (mode_ivcheck, ins)
2383 if subcommand == PDTCRYPT_SUB_SCRYPT:
2384 return True, partial (mode_scrypt, secret [1].encode (), ins, nacl,
2387 return True, partial (mode_depdtcrypt, mode, secret, ins, outs)
2391 ok, runner = parse_argv (argv)
2393 if ok is True: return runner ()
2398 if __name__ == "__main__":
2399 sys.exit (main (sys.argv))