123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- #!/usr/bin/env python
- # initially stolen from: https://github.com/jirutka/haste-client
- # adapted to be only for python 3
- # added get snippet feature and crypt snippet feature
- # change the server url in CONFIG if needed
- """
- haste - a CLI client for Haste server.
- Usage:
- haste send [-r] [--password=<pwd>] [FILE]
- haste get [--password=<pwd>] KEY
- Options:
- -r --raw Return a URL to the raw paste data.
- --password=<pwd> Encrypt/decrypt message using <pwd> as password.
- -h --help Show this message.
- -v --version Show version.
- If FILE is not specified, haste will read from standard input.
- """
- import os
- from base64 import b64decode, b64encode
- import json
- import sys
- import docopt
- import requests
- from cryptography.hazmat.primitives import hashes
- from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
- from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
- from cryptography.hazmat.backends import default_backend
- _BACKEND = default_backend()
- __version__ = '2.0.1'
- CONFIG = {
- 'server_url': "https://hastebin.com",
- 'timeout': 3
- }
- def main(**kwargs):
- """ main function: do actions following args """
- if kwargs['KEY'] and kwargs['get']:
- data = get_snippet(kwargs['KEY'], CONFIG['server_url'], CONFIG['timeout'])
- if kwargs['--password']:
- data = decrypt(kwargs['--password'], json.loads(data)).decode("utf-8")
- print(data)
- elif kwargs['send']:
- data = read_file(kwargs['FILE']) if kwargs['FILE'] else read_stdin()
- if kwargs['--password']:
- data = json.dumps(encrypt(kwargs['--password'], data))
- url = create_snippet(data, CONFIG['server_url'], CONFIG['timeout'], kwargs['--raw'])
- print(url)
- else:
- print(docopt.docopt(__doc__))
- def get_snippet(dockey, baseurl, timeout):
- """ get a snippet from the server """
- try:
- url = baseurl + "/raw/" + dockey
- response = requests.get(url, timeout=float(timeout))
- except requests.exceptions.Timeout:
- exit("Error: connection timed out")
- return response.text
- def create_snippet(data, baseurl, timeout, raw):
- """
- Creates snippet with the given data on the haste server specified by the
- baseurl and returns URL of the created snippet.
- """
- try:
- url = baseurl + "/documents"
- response = requests.post(url, data.encode('utf-8'), timeout=float(timeout))
- except requests.exceptions.Timeout:
- exit("Error: connection timed out")
- dockey = json.loads(response.text)['key']
- return baseurl + ("/raw/" if raw else "/") + dockey
- def read_stdin():
- """ joins lines of stdin into a single string """
- return "".join(sys.stdin.readlines()).strip()
- def read_file(path):
- """ reads lines of a file and joins them into a single string """
- try:
- with open(path, 'r') as text_file:
- return "".join(text_file.readlines()).strip()
- except IOError:
- exit("Error: file '%s' is not readable!" % path)
- def decrypt(pwd, data):
- """ Decrypt using password and input json """
- ct = b64decode(data['ct'])
- salt = b64decode(data['salt'])
- tag_start = len(ct) - data['ts'] // 8
- tag = ct[tag_start:]
- ciphertext = ct[:tag_start]
- mode_class = getattr(modes, data['mode'].upper())
- algo_class = getattr(algorithms, data['cipher'].upper())
- kdf = _kdf(data['ks'], iters=data['iter'], salt=salt)[0]
- key = kdf.derive(bytes(pwd, "utf-8"))
- cipher = Cipher(
- algo_class(key),
- mode_class(
- b64decode(data['iv']),
- tag,
- min_tag_length=data['ts'] // 8
- ),
- backend=_BACKEND
- )
- dec = cipher.decryptor()
- return dec.update(ciphertext) + dec.finalize()
- def _kdf(keysize=128, iters=256000, salt=None):
- """ Returns a key derivation function: used to create a strong key based on the input """
- kdf_salt = salt or os.urandom(8)
- kdf = PBKDF2HMAC(
- algorithm=hashes.SHA256(),
- length=keysize // 8,
- salt=kdf_salt,
- iterations=iters,
- backend=_BACKEND
- )
- return kdf, kdf_salt
- def encrypt(pwd, plaintext, mode='gcm', keysize=128, tagsize=128, iters=256000):
- """ Encrypt plain text using password. Outputs json """
- ts = tagsize // 8
- mode_class = getattr(modes, mode.upper())
- algo_class = getattr(algorithms, 'AES')
- iv = os.urandom(16)
- kdf, salt = _kdf(keysize, iters)
- bpwd = str.encode(pwd)
- key = kdf.derive(bpwd)
- cipher = Cipher(
- algo_class(key),
- mode_class(iv, min_tag_length=ts),
- backend=_BACKEND
- )
- enc = cipher.encryptor()
- btext = str.encode(plaintext)
- ciphertext = enc.update(btext) + enc.finalize()
- output = {
- "v": 1,
- "iv": b64encode(iv).decode("utf-8"),
- "salt": b64encode(salt).decode("utf-8"),
- "ct": b64encode(ciphertext + enc.tag[:ts]).decode("utf-8"),
- "iter": iters,
- "ks": keysize,
- "ts": tagsize,
- "mode": mode,
- "cipher": 'aes',
- "adata": ""
- }
- return output
- DOC = docopt.docopt(__doc__, version='haste ' + __version__)
- if __name__ == "__main__":
- main(**DOC)
|