Gnumed.pycommon.gmCrypto

Package Gnumed :: Package pycommon :: Module gmCrypto |
[frames] | no frames]

# Source Code for Module Gnumed.pycommon.gmCrypto

      1  # -*- coding: utf-8 -*-
      2
      3  __doc__ = """GNUmed crypto tools.
      4
      5  First and only rule:
      6
      7          DO NOT REIMPLEMENT ENCRYPTION
      8
      9          Use existing tools.
     10  """
     11  #===========================================================================
     12  __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
     13  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
     14
     15  # std libs
     16  import sys
     17  import os
     18  import logging
     19  import tempfile
     20
     21
     22  # GNUmed libs
     23  if __name__ == '__main__':
     24          sys.path.insert(0, '../../')
     25  from Gnumed.pycommon import gmLog2
     26  from Gnumed.pycommon import gmShellAPI
     27  from Gnumed.pycommon import gmTools
     28  from Gnumed.pycommon import gmMimeLib
     29
     30
     31  _log = logging.getLogger('gm.encryption')
     32
     33  #===========================================================================
     34  # archiving methods
     35  #---------------------------------------------------------------------------
     36 -def create_encrypted_zip_archive_from_dir(source_dir, comment=None, overwrite=True, passphrase=None, verbose=False):
     37          """Use 7z to create an encrypted ZIP archive of a directory.
     38
     39          <source_dir>            will be included into the archive
     40          <comment>                       included as a file containing the comment
     41          <overwrite>                     remove existing archive before creation, avoiding
     42                                                  *updating* of those, and thereby including unintended data
     43          <passphrase>            minimum length of 5
     44
     45          The resulting zip archive will always be named
     46          "datawrapper.zip" for confidentiality reasons. If callers
     47          want another name they will have to shutil.move() the zip
     48          file themselves. This archive will be compressed and
     49          AES256 encrypted with the given passphrase. Therefore,
     50          the result will not decrypt with earlier versions of
     51          unzip software. On Windows, 7z oder WinZip are needed.
     52
     53          The zip format does not support header encryption thereby
     54          allowing attackers to gain knowledge of patient details
     55          by observing the names of files and directories inside
     56          the encrypted archive.
     57
     58          To reduce that attack surface, GNUmed will create
     59          _another_ zip archive inside "datawrapper.zip", which
     60          eventually wraps up the patient data as "data.zip". That
     61          archive is not compressed and not encrypted, and can thus
     62          be unpacked with any old unzipper.
     63
     64          Note that GNUmed does NOT remember the passphrase for
     65          you. You will have to take care of that yourself, and
     66          possibly also safely hand over the passphrase to any
     67          receivers of the zip archive.
     68          """
     69          if len(passphrase) < 5:
     70                  _log.error('<passphrase> must be at least 5 characters/signs/digits')
     71                  return None
     72          gmLog2.add_word2hide(passphrase)
     73
     74          source_dir = os.path.abspath(source_dir)
     75          if not os.path.isdir(source_dir):
     76                  _log.error('<source_dir> does not exist or is not a directory: %s', source_dir)
     77                  return False
     78
     79          for cmd in ['7z', '7z.exe']:
     80                  found, binary = gmShellAPI.detect_external_binary(binary = cmd)
     81                  if found:
     82                          break
     83          if not found:
     84                  _log.warning('no 7z binary found')
     85                  return None
     86
     87          sandbox_dir = gmTools.mk_sandbox_dir()
     88          archive_path_inner = os.path.join(sandbox_dir, 'data')
     89          if not gmTools.mkdir(archive_path_inner):
     90                  _log.error('cannot create scratch space for inner achive: %s', archive_path_inner)
     91          archive_fname_inner = 'data.zip'
     92          archive_name_inner = os.path.join(archive_path_inner, archive_fname_inner)
     93          archive_path_outer = gmTools.gmPaths().tmp_dir
     94          archive_fname_outer = 'datawrapper.zip'
     95          archive_name_outer = os.path.join(archive_path_outer, archive_fname_outer)
     96          # remove existing archives so they don't get *updated* rather than newly created
     97          if overwrite:
     98                  if not gmTools.remove_file(archive_name_inner, force = True):
     99                          _log.error('cannot remove existing archive [%s]', archive_name_inner)
    100                          return False
    101
    102                  if not gmTools.remove_file(archive_name_outer, force = True):
    103                          _log.error('cannot remove existing archive [%s]', archive_name_outer)
    104                          return False
    105
    106          # 7z does not support ZIP comments so create a text file holding the comment
    107          if comment is not None:
    108                  tmp, fname = os.path.split(source_dir.rstrip(os.sep))
    109                  comment_filename = os.path.join(sandbox_dir, '000-%s-comment.txt' % fname)
    110                  with open(comment_filename, mode = 'wt', encoding = 'utf8', errors = 'replace') as comment_file:
    111                          comment_file.write(comment)
    112
    113          # create inner (data) archive: uncompressed, unencrypted, similar to a tar archive
    114          args = [
    115                  binary,
    116                  'a',                            # create archive
    117                  '-sas',                         # be smart about archive name extension
    118                  '-bd',                          # no progress indicator
    119                  '-mx0',                         # no compression (only store files)
    120                  '-mcu=on',                      # UTF8 filenames
    121                  '-l',                           # store content of links, not links
    122                  '-scsUTF-8',            # console charset
    123                  '-tzip'                         # force ZIP format
    124          ]
    125          if verbose:
    126                  args.append('-bb3')
    127                  args.append('-bt')
    128          else:
    129                  args.append('-bb1')
    130          args.append(archive_name_inner)
    131          args.append(source_dir)
    132          if comment is not None:
    133                  args.append(comment_filename)
    134          success, exit_code, stdout = gmShellAPI.run_process(cmd_line = args, encoding = 'utf8', verbose = verbose)
    135          if not success:
    136                  _log.error('cannot create inner archive')
    137                  return None
    138
    139          # create "decompress instructions" file
    140          instructions_filename = os.path.join(archive_path_inner, '000-on_Windows-open_with-WinZip_or_7z_tools')
    141          open(instructions_filename, mode = 'wt').close()
    142
    143          # create outer (wrapper) archive: compressed, encrypted
    144          args = [
    145                  binary,
    146                  'a',                                    # create archive
    147                  '-sas',                                 # be smart about archive name extension
    148                  '-bd',                                  # no progress indicator
    149                  '-mx9',                                 # best available zip compression ratio
    150                  '-mcu=on',                              # UTF8 filenames
    151                  '-l',                                   # store content of links, not links
    152                  '-scsUTF-8',                    # console charset
    153                  '-tzip',                                # force ZIP format
    154                  '-mem=AES256',                  # force useful encryption
    155                  '-p%s' % passphrase             # set passphrase
    156          ]
    157          if verbose:
    158                  args.append('-bb3')
    159                  args.append('-bt')
    160          else:
    161                  args.append('-bb1')
    162          args.append(archive_name_outer)
    163          args.append(archive_path_inner)
    164          success, exit_code, stdout = gmShellAPI.run_process(cmd_line = args, encoding = 'utf8', verbose = verbose)
    165          if success:
    166                  return archive_name_outer
    167          _log.error('cannot create outer archive')
    168          return None
    169
    170  #---------------------------------------------------------------------------
    171 -def create_zip_archive_from_dir(source_dir, archive_name=None, comment=None, overwrite=True, verbose=False):
    172
    173          source_dir = os.path.abspath(source_dir)
    174          if not os.path.isdir(source_dir):
    175                  _log.error('<source_dir> does not exist or is not a directory: %s', source_dir)
    176                  return False
    177
    178          for cmd in ['7z', '7z.exe']:
    179                  found, binary = gmShellAPI.detect_external_binary(binary = cmd)
    180                  if found:
    181                          break
    182          if not found:
    183                  _log.warning('no 7z binary found')
    184                  return None
    185
    186          if archive_name is None:
    187                  # do not assume we can write to "sourcedir/../"
    188                  archive_path = gmTools.gmPaths().tmp_dir
    189                  # but do take archive name from source_dir
    190                  tmp, archive_fname = os.path.split(source_dir.rstrip(os.sep) + '.zip')
    191                  archive_name = os.path.join(archive_path, archive_fname)
    192          # remove any existing archives so they don't get *updated*
    193          # rather than newly created
    194          if overwrite:
    195                  if not gmTools.remove_file(archive_name, force = True):
    196                          _log.error('cannot remove existing archive [%s]', archive_name)
    197                          return False
    198          # 7z does not support ZIP comments so create
    199          # a text file holding the comment ...
    200          if comment is not None:
    201                  comment_filename = os.path.abspath(archive_name) + '.comment.txt'
    202                  if gmTools.remove_file(comment_filename, force = True):
    203                          with open(comment_filename, mode = 'wt', encoding = 'utf8', errors = 'replace') as comment_file:
    204                                  comment_file.write(comment)
    205                  else:
    206                          _log.error('cannot remove existing archive comment file [%s]', comment_filename)
    207                          comment = None
    208
    209          # compress
    210          args = [
    211                  binary,
    212                  'a',                            # create archive
    213                  '-sas',                         # be smart about archive name extension
    214                  '-bd',                          # no progress indicator
    215                  '-mx9',                         # best available zip compression ratio
    216                  '-mcu=on',                      # UTF8 filenames
    217                  '-l',                           # store content of links, not links
    218                  '-scsUTF-8',            # console charset
    219                  '-tzip'                         # force ZIP format
    220          ]
    221          if verbose:
    222                  args.append('-bb3')
    223                  args.append('-bt')
    224          else:
    225                  args.append('-bb1')
    226          args.append(archive_name)
    227          args.append(source_dir)
    228          if comment is not None:
    229                  args.append(comment_filename)
    230          success, exit_code, stdout = gmShellAPI.run_process(cmd_line = args, encoding = 'utf8', verbose = verbose)
    231          if comment is not None:
    232                  gmTools.remove_file(comment_filename)
    233          if success:
    234                  return archive_name
    235
    236          return None
    237
    238  #===========================================================================
    239  # file decryption methods
    240  #---------------------------------------------------------------------------
    241 -def gpg_decrypt_file(filename=None, passphrase=None, verbose=False, target_ext=None):
    242          assert (filename is not None), '<filename> must not be None'
    243
    244          _log.debug('attempting GPG decryption')
    245          for cmd in ['gpg2', 'gpg', 'gpg2.exe', 'gpg.exe']:
    246                  found, binary = gmShellAPI.detect_external_binary(binary = cmd)
    247                  if found:
    248                          break
    249          if not found:
    250                  _log.warning('no gpg binary found')
    251                  return None
    252
    253          basename = os.path.splitext(filename)[0]
    254          filename_decrypted = gmTools.get_unique_filename(prefix = '%s-decrypted-' % basename, suffix = target_ext)
    255          args = [
    256                  binary,
    257                  '--utf8-strings',
    258                  '--display-charset', 'utf-8',
    259                  '--batch',
    260                  '--no-greeting',
    261                  '--enable-progress-filter',
    262                  '--decrypt',
    263                  '--output', filename_decrypted
    264                  ##'--use-embedded-filename'                             # not all encrypted files carry a filename
    265          ]
    266          if verbose:
    267                  args.extend ([
    268                          '--verbose', '--verbose',
    269                          '--debug-level', '8',
    270                          '--debug', 'packet,mpi,crypto,filter,iobuf,memory,cache,memstat,trust,hashing,clock,lookup,extprog'
    271                          ##'--debug-all',                                                # will log passphrase
    272                          ##'--debug, 'ipc',                                              # will log passphrase
    273                          ##'--debug-level', 'guru',                              # will log passphrase
    274                          ##'--debug-level', '9',                                 # will log passphrase
    275                  ])
    276          args.append(filename)
    277          success, exit_code, stdout = gmShellAPI.run_process(cmd_line = args, verbose = verbose, encoding = 'utf-8')
    278          if success:
    279                  return filename_decrypted
    280          return None
    281
    282  #===========================================================================
    283  # file encryption methods
    284  #---------------------------------------------------------------------------
    285 -def gpg_encrypt_file_symmetric(filename=None, comment=None, verbose=False, passphrase=None, remove_unencrypted=False):
    286
    287          #add short decr instr to comment
    288          assert (filename is not None), '<filename> must not be None'
    289
    290          _log.debug('attempting symmetric GPG encryption')
    291          for cmd in ['gpg2', 'gpg', 'gpg2.exe', 'gpg.exe']:
    292                  found, binary = gmShellAPI.detect_external_binary(binary = cmd)
    293                  if found:
    294                          break
    295          if not found:
    296                  _log.warning('no gpg binary found')
    297                  return None
    298          filename_encrypted = filename + '.asc'
    299          args = [
    300                  binary,
    301                  '--utf8-strings',
    302                  '--display-charset', 'utf-8',
    303                  '--batch',
    304                  '--no-greeting',
    305                  '--enable-progress-filter',
    306                  '--symmetric',
    307                  '--cipher-algo', 'AES256',
    308                  '--armor',
    309                  '--output', filename_encrypted
    310          ]
    311          if comment is not None:
    312                  args.extend(['--comment', comment])
    313          if verbose:
    314                  args.extend ([
    315                          '--verbose', '--verbose',
    316                          '--debug-level', '8',
    317                          '--debug', 'packet,mpi,crypto,filter,iobuf,memory,cache,memstat,trust,hashing,clock,lookup,extprog',
    318                          ##'--debug-all',                                                # will log passphrase
    319                          ##'--debug, 'ipc',                                              # will log passphrase
    320                          ##'--debug-level', 'guru',                              # will log passphrase
    321                          ##'--debug-level', '9',                                 # will log passphrase
    322                  ])
    323          pwd_fname = None
    324          if passphrase is not None:
    325                  pwd_file = tempfile.NamedTemporaryFile(mode = 'w+t', encoding = 'utf8', delete = False)
    326                  pwd_fname = pwd_file.name
    327                  args.extend ([
    328                          '--pinentry-mode', 'loopback',
    329                          '--passphrase-file', pwd_fname
    330                  ])
    331                  pwd_file.write(passphrase)
    332                  pwd_file.close()
    333          args.append(filename)
    334          try:
    335                  success, exit_code, stdout = gmShellAPI.run_process(cmd_line = args, verbose = verbose, encoding = 'utf-8')
    336          finally:
    337                  if pwd_fname is not None:
    338                          os.remove(pwd_fname)
    339          if not success:
    340                  return None
    341          if not remove_unencrypted:
    342                  return filename_encrypted
    343          if gmTools.remove_file(filename):
    344                  return filename_encrypted
    345          gmTools.remove_file(filename_encrypted)
    346          return None
    347
    348  #---------------------------------------------------------------------------
    349 -def aes_encrypt_file(filename=None, passphrase=None, comment=None, verbose=False, remove_unencrypted=False):
    350          assert (filename is not None), '<filename> must not be None'
    351          assert (passphrase is not None), '<passphrase> must not be None'
    352
    353          if len(passphrase) < 5:
    354                  _log.error('<passphrase> must be at least 5 characters/signs/digits')
    355                  return None
    356          gmLog2.add_word2hide(passphrase)
    357
    358          #add 7z/winzip url to comment.txt
    359          _log.debug('attempting 7z AES encryption')
    360          for cmd in ['7z', '7z.exe']:
    361                  found, binary = gmShellAPI.detect_external_binary(binary = cmd)
    362                  if found:
    363                          break
    364          if not found:
    365                  _log.warning('no 7z binary found, trying gpg')
    366                  return None
    367
    368          if comment is not None:
    369                  archive_path, archive_name = os.path.split(os.path.abspath(filename))
    370                  comment_filename = gmTools.get_unique_filename (
    371                          prefix = '%s.7z.comment-' % archive_name,
    372                          tmp_dir = archive_path,
    373                          suffix = '.txt'
    374                  )
    375                  with open(comment_filename, mode = 'wt', encoding = 'utf8', errors = 'replace') as comment_file:
    376                          comment_file.write(comment)
    377          else:
    378                  comment_filename = ''
    379          filename_encrypted = '%s.7z' % filename
    380          args = [binary, 'a', '-bb3', '-mx0', "-p%s" % passphrase, filename_encrypted, filename, comment_filename]
    381          encrypted, exit_code, stdout = gmShellAPI.run_process(cmd_line = args, encoding = 'utf8', verbose = verbose)
    382          gmTools.remove_file(comment_filename)
    383          if not encrypted:
    384                  return None
    385          if not remove_unencrypted:
    386                  return filename_encrypted
    387          if gmTools.remove_file(filename):
    388                  return filename_encrypted
    389          gmTools.remove_file(filename_encrypted)
    390          return None
    391
    392  #---------------------------------------------------------------------------
    393 -def encrypt_pdf(filename=None, passphrase=None, verbose=False, remove_unencrypted=False):
    394          assert (filename is not None), '<filename> must not be None'
    395          assert (passphrase is not None), '<passphrase> must not be None'
    396
    397          if len(passphrase) < 5:
    398                  _log.error('<passphrase> must be at least 5 characters/signs/digits')
    399                  return None
    400
    401          gmLog2.add_word2hide(passphrase)
    402          _log.debug('attempting PDF encryption')
    403          for cmd in ['qpdf', 'qpdf.exe']:
    404                  found, binary = gmShellAPI.detect_external_binary(binary = cmd)
    405                  if found:
    406                          break
    407          if not found:
    408                  _log.warning('no qpdf binary found')
    409                  return None
    410
    411          filename_encrypted = '%s.encrypted.pdf' % os.path.splitext(filename)[0]
    412          args = [
    413                  binary,
    414                  '--verbose',
    415                  '--encrypt', passphrase, '', '128',
    416                  '--print=full', '--modify=none', '--extract=n',
    417                  '--use-aes=y',
    418                  '--',
    419                  filename,
    420                  filename_encrypted
    421          ]
    422          success, exit_code, stdout = gmShellAPI.run_process(cmd_line = args, encoding = 'utf8', verbose = verbose)
    423          if not success:
    424                  return None
    425
    426          if not remove_unencrypted:
    427                  return filename_encrypted
    428
    429          if gmTools.remove_file(filename):
    430                  return filename_encrypted
    431
    432          gmTools.remove_file(filename_encrypted)
    433          return None
    434
    435  #---------------------------------------------------------------------------
    436 -def encrypt_file_symmetric(filename=None, passphrase=None, comment=None, verbose=False, remove_unencrypted=False, convert2pdf=False):
    437          """Encrypt <filename> with a symmetric cipher.
    438
    439          <convert2pdf> - True: convert <filename> to PDF, if possible, and encrypt that.
    440          """
    441          assert (filename is not None), '<filename> must not be None'
    442
    443          if convert2pdf:
    444                  _log.debug('PDF encryption preferred, attempting conversion if needed')
    445                  pdf_fname = gmMimeLib.convert_file (
    446                          filename = filename,
    447                          target_mime = 'application/pdf',
    448                          target_filename = filename + '.pdf',
    449                          verbose = verbose
    450                  )
    451                  if pdf_fname is not None:
    452                          _log.debug('successfully converted to PDF')
    453                          # remove non-pdf file
    454                          gmTools.remove_file(filename)
    455                          filename = pdf_fname
    456
    457          # try PDF-inherent AES
    458          encrypted_filename = encrypt_pdf (
    459                  filename = filename,
    460                  passphrase = passphrase,
    461                  verbose = verbose,
    462                  remove_unencrypted = remove_unencrypted
    463          )
    464          if encrypted_filename is not None:
    465                  return encrypted_filename
    466
    467          # try 7z based AES
    468          encrypted_filename = aes_encrypt_file (
    469                  filename = filename,
    470                  passphrase = passphrase,
    471                  comment = comment,
    472                  verbose = verbose,
    473                  remove_unencrypted = remove_unencrypted
    474          )
    475          if encrypted_filename is not None:
    476                  return encrypted_filename
    477
    478          # try GPG based AES
    479          return gpg_encrypt_file_symmetric(filename = filename, passphrase = passphrase, comment = comment, verbose = verbose, remove_unencrypted = remove_unencrypted)
    480
    481  #---------------------------------------------------------------------------
    482 -def encrypt_file(filename=None, receiver_key_ids=None, passphrase=None, comment=None, verbose=False, remove_unencrypted=False, convert2pdf=False):
    483          """Encrypt an arbitrary file.
    484
    485          <remove_unencrypted>
    486                  True: remove unencrypted source file if encryption succeeded
    487          <convert2pdf>
    488                  True: attempt conversion to PDF of input file before encryption
    489                          success: the PDF is encrypted (and the non-PDF source file is removed)
    490                          failure: the source file is encrypted
    491          """
    492          assert (filename is not None), '<filename> must not be None'
    493
    494          # cannot do asymmetric
    495          if receiver_key_ids is None:
    496                  _log.debug('no receiver key IDs: cannot try asymmetric encryption')
    497                  return encrypt_file_symmetric (
    498                          filename = filename,
    499                          passphrase = passphrase,
    500                          comment = comment,
    501                          verbose = verbose,
    502                          remove_unencrypted = remove_unencrypted,
    503                          convert2pdf = convert2pdf
    504                  )
    505
    506          # asymmetric not implemented yet
    507          return None
    508
    509  #---------------------------------------------------------------------------
    510 -def encrypt_directory_content(directory=None, receiver_key_ids=None, passphrase=None, comment=None, verbose=False, remove_unencrypted=True, convert2pdf=False):
    511          assert (directory is not None), 'source <directory> must not be None'
    512          _log.debug('encrypting content of [%s]', directory)
    513          try:
    514                  items = os.listdir(directory)
    515          except OSError:
    516                  return False
    517
    518          for item in items:
    519                  full_item = os.path.join(directory, item)
    520                  if os.path.isdir(full_item):
    521                          subdir_encrypted = encrypt_directory_content (
    522                                  directory = full_item,
    523                                  receiver_key_ids = receiver_key_ids,
    524                                  passphrase = passphrase,
    525                                  comment = comment,
    526                                  verbose = verbose
    527                          )
    528                          if subdir_encrypted is False:
    529                                  return False
    530                          continue
    531
    532                  fname_encrypted = encrypt_file (
    533                          filename = full_item,
    534                          receiver_key_ids = receiver_key_ids,
    535                          passphrase = passphrase,
    536                          comment = comment,
    537                          verbose = verbose,
    538                          remove_unencrypted = remove_unencrypted,
    539                          convert2pdf = convert2pdf
    540                  )
    541                  if fname_encrypted is None:
    542                          return False
    543
    544          return True
    545
    546  #===========================================================================
    547  # main
    548  #---------------------------------------------------------------------------
    549  if __name__ == '__main__':
    550
    551          if len(sys.argv) < 2:
    552                  sys.exit()
    553
    554          if sys.argv[1] != 'test':
    555                  sys.exit()
    556
    557          # for testing:
    558          logging.basicConfig(level = logging.DEBUG)
    559          from Gnumed.pycommon import gmI18N
    560          gmI18N.activate_locale()
    561          gmI18N.install_domain()
    562
    563          #-----------------------------------------------------------------------
    564 -        def test_gpg_decrypt():
    565                  print(gpg_decrypt_file(filename = sys.argv[2], verbose = True))
    566
    567          #-----------------------------------------------------------------------
    568 -        def test_gpg_encrypt_symmetric():
    569                  print(gpg_encrypt_file_symmetric(filename = sys.argv[2], passphrase = sys.argv[3], verbose = True, comment = 'GNUmed testing'))
    570
    571          #-----------------------------------------------------------------------
    572 -        def test_aes_encrypt():
    573                  print(aes_encrypt_file(filename = sys.argv[2], passphrase = sys.argv[3], comment = sys.argv[4], verbose = True))
    574
    575          #-----------------------------------------------------------------------
    576 -        def test_encrypt_pdf():
    577                  print(encrypt_pdf(filename = sys.argv[2], passphrase = sys.argv[3], verbose = True))
    578
    579          #-----------------------------------------------------------------------
    580 -        def test_encrypt_file():
    581                  print(encrypt_file(filename = sys.argv[2], passphrase = sys.argv[3], verbose = True, convert2pdf = True))
    582
    583          #-----------------------------------------------------------------------
    584 -        def test_zip_archive_from_dir():
    585                  print(create_zip_archive_from_dir (
    586                          sys.argv[2],
    587                          #archive_name=None,
    588                          comment = 'GNUmed test archive',
    589                          overwrite = True,
    590                          verbose = True
    591                  ))
    592
    593          #-----------------------------------------------------------------------
    594 -        def test_encrypted_zip_archive_from_dir():
    595                  print(create_encrypted_zip_archive_from_dir (
    596                          sys.argv[2],
    597                          comment = 'GNUmed test archive',
    598                          overwrite = True,
    599                          passphrase = sys.argv[3],
    600                          verbose = True
    601                  ))
    602
    603          #-----------------------------------------------------------------------
    604          # encryption
    605          #test_aes_encrypt()
    606          #test_encrypt_pdf()
    607          #test_gpg_encrypt_symmetric()
    608          test_encrypt_file()
    609
    610          # decryption
    611          #test_gpg_decrypt()
    612
    613          #test_zip_archive_from_dir()
    614          #test_encrypted_zip_archive_from_dir()
    615