Advertisement
andreymal

filecrypt.py

Aug 9th, 2019
366
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.10 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3.  
  4. import os
  5. import sys
  6. import secrets
  7. import binascii
  8.  
  9. # Generate keys using:
  10. # $ openssl genrsa -out privatekey.pem 4096
  11. # $ openssl rsa -in privatekey.pem -pubout -out publickey.pem
  12.  
  13.  
  14. def main_encrypt(pubkey_path: str, filename: str) -> int:
  15.     from cryptography.hazmat.backends import default_backend
  16.     from cryptography.hazmat.primitives import serialization, hashes
  17.     from cryptography.hazmat.primitives.asymmetric import padding
  18.     from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
  19.  
  20.     backend = default_backend()
  21.  
  22.     # Check destination
  23.     dst = filename + ".enc"
  24.     if os.path.exists(dst):
  25.         print("{!r} already exists".format(dst), file=sys.stderr)
  26.         return 1
  27.  
  28.     # Select preferred hash algorithm
  29.     PaddingHash = hashes.SHA256
  30.     # OpenSSL compatibility: PaddingHash = hashes.SHA1
  31.  
  32.     # Read the public key
  33.     with open(pubkey_path, "rb") as fp:
  34.         public_key = serialization.load_pem_public_key(
  35.             fp.read(),
  36.             backend=backend,
  37.         )
  38.  
  39.     # Generate key and initialization vector (CTR nonce) for symmetric encryption
  40.     aes_key = secrets.token_bytes(32)
  41.     aes_iv = secrets.token_bytes(16)
  42.  
  43.     keys_info = b"%s\n%s\n" % (
  44.         binascii.hexlify(aes_key),  # secret
  45.         binascii.hexlify(aes_iv),  # actually not a secret, but why not
  46.     )
  47.  
  48.     # And store it using asymmetric encryption
  49.     keys_info_encrypted = public_key.encrypt(
  50.         keys_info,
  51.         padding.OAEP(
  52.             mgf=padding.MGF1(algorithm=PaddingHash()),
  53.             algorithm=PaddingHash(),
  54.             label=None,
  55.         )
  56.     )
  57.  
  58.     # Prepare AES-256-CTR encryption
  59.     cipher = Cipher(algorithms.AES(aes_key), modes.CTR(nonce=aes_iv), backend=backend)
  60.     encryptor = cipher.encryptor()
  61.  
  62.     with open(filename, "rb") as rfp:
  63.         with open(dst, "wb") as wfp:
  64.             # Write format identifier
  65.             wfp.write("encrypted:RSA-{},OAEP-MGF1-{}:AES-256-CTR\n".format(
  66.                 public_key.key_size,
  67.                 PaddingHash.name.upper(),
  68.             ).encode("ascii"))
  69.             # write keys
  70.             wfp.write(keys_info_encrypted)
  71.  
  72.             while True:
  73.                 # encrypt!
  74.                 chunk = rfp.read(65536)
  75.                 if not chunk:
  76.                     break
  77.  
  78.                 enc_chunk = encryptor.update(chunk)
  79.                 wfp.write(enc_chunk)
  80.  
  81.             enc_fin = encryptor.finalize()
  82.             wfp.write(enc_fin)
  83.  
  84.     return 0
  85.  
  86.  
  87. def main_decrypt(privkey_path: str, filename: str) -> int:
  88.     from cryptography.hazmat.backends import default_backend
  89.     from cryptography.hazmat.primitives import serialization, hashes
  90.     from cryptography.hazmat.primitives.asymmetric import padding
  91.     from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
  92.  
  93.     backend = default_backend()
  94.  
  95.     # Check destination
  96.     if filename.lower().endswith(".enc"):
  97.         dst = filename[:-4]
  98.     else:
  99.         dst = filename + ".dec"
  100.     if os.path.exists(dst):
  101.         print("{!r} already exists".format(dst), file=sys.stderr)
  102.         return 1
  103.  
  104.     # Read the private key
  105.     with open(privkey_path, "rb") as fp:
  106.         private_key = serialization.load_pem_private_key(
  107.             fp.read(),
  108.             password=None,  # TODO: implement?
  109.             backend=backend,
  110.         )
  111.  
  112.     with open(filename, "rb") as rfp:
  113.         # Detect format
  114.         header = rfp.readline().decode("ascii")
  115.         if not header.startswith("encrypted:"):
  116.             print("Unknown file format", file=sys.stderr)
  117.             return 1
  118.         _, asym_info, sym_info = header.rstrip().split(":")
  119.  
  120.         # Check asymmetric params support
  121.         asym_algo, asym_padding = asym_info.split(",")
  122.         if asym_algo != "RSA-{}".format(private_key.key_size):
  123.             print("Cannot decode {}".format(asym_algo), file=sys.stderr)
  124.             return 1
  125.         if asym_padding == "OAEP-MGF1-SHA1":
  126.             PaddingHash = hashes.SHA1
  127.         elif asym_padding == "OAEP-MGF1-SHA256":
  128.             PaddingHash = hashes.SHA256
  129.         else:
  130.             print("Unknown padding {}".format(asym_padding))
  131.             return 1
  132.  
  133.         # Check symmetric params support
  134.         if sym_info != "AES-256-CTR":
  135.             print("Unknown symmetric algorithm {}".format(sym_info))
  136.             return 1
  137.  
  138.         # Everything is okay, read encrypted key and iv
  139.         keys_info_encrypted = rfp.read(private_key.key_size // 8)
  140.         keys_info = private_key.decrypt(
  141.             keys_info_encrypted,
  142.             padding.OAEP(
  143.                 mgf=padding.MGF1(algorithm=PaddingHash()),
  144.                 algorithm=PaddingHash(),
  145.                 label=None,
  146.             )
  147.         )
  148.  
  149.         aes_key, aes_iv = [binascii.unhexlify(x) for x in keys_info.strip().split(b"\n")]
  150.         if len(aes_key) != 32:
  151.             print("Unsupported AES key size {}".format(len(aes_key * 8)))
  152.             return 1
  153.  
  154.         # Prepare AES-256-CTR decryption
  155.         cipher = Cipher(algorithms.AES(aes_key), modes.CTR(nonce=aes_iv), backend=backend)
  156.         decryptor = cipher.decryptor()
  157.  
  158.         with open(dst, "wb") as wfp:
  159.             while True:
  160.                 # decrypt!
  161.                 enc_chunk = rfp.read(65536)
  162.                 if not enc_chunk:
  163.                     break
  164.                 chunk = decryptor.update(enc_chunk)
  165.                 wfp.write(chunk)
  166.  
  167.             dec_fin = decryptor.finalize()
  168.             wfp.write(dec_fin)
  169.  
  170.     return 0
  171.  
  172.  
  173. def print_help() -> int:
  174.     print((
  175.         "Usage:\n"
  176.         "{0} encrypt publickey.pem filename\n"
  177.         "{0} decrypt privatekey.pem filename.enc\n"
  178.     ).format(sys.argv[0]), file=sys.stderr)
  179.     return 1
  180.  
  181.  
  182. def main() -> int:
  183.     if len(sys.argv) != 4:
  184.         return print_help()
  185.  
  186.     cmd, key_path, filename = sys.argv[1:]
  187.  
  188.     if cmd in ("encrypt", "e"):
  189.         return main_encrypt(key_path, filename)
  190.     if cmd in ("decrypt", "d"):
  191.         return main_decrypt(key_path, filename)
  192.     return print_help()
  193.  
  194.  
  195. if __name__ == "__main__":
  196.     sys.exit(main())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement