diff options
author | Andreas Schneider <asn@cryptomilk.org> | 2017-02-16 22:18:38 +0100 |
---|---|---|
committer | Andreas Schneider <asn@cryptomilk.org> | 2017-02-21 15:43:54 +0100 |
commit | e3b53a270ba8e7b56511aaba4be8b3dea65aebe3 (patch) | |
tree | 1e5f1a17927ecee4c8792da7dd1bbba5cd9b975c | |
parent | 5b83fc14f307d68b4f615b6f0a01e9d67ceda05d (diff) | |
download | cpaste-e3b53a270ba8e7b56511aaba4be8b3dea65aebe3.tar.gz cpaste-e3b53a270ba8e7b56511aaba4be8b3dea65aebe3.tar.xz cpaste-e3b53a270ba8e7b56511aaba4be8b3dea65aebe3.zip |
Add PrivateBin AES encryption
-rwxr-xr-x | cpaste | 132 |
1 files changed, 102 insertions, 30 deletions
@@ -26,9 +26,16 @@ # Optionally requires: python-Pygments # +import os import sys import json +import base64 +import zlib import requests +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from optparse import OptionParser try: from pygments.lexers import guess_lexer, guess_lexer_for_filename @@ -47,6 +54,75 @@ def tabulate(words, termwidth=79, pad=3, indent=0): table.append(format_str % tuple(row)) return '\n'.join(table) +def sjcl_encrypt(plaintext, passphrase): + # PBKDF + kdf_salt = bytes(os.urandom(8)) + kdf_iterations = 10000 + kdf_keysize = 256 # size of resulting kdf_key + + backend = default_backend() + kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), + length=int(kdf_keysize / 8), # 256bit + salt=kdf_salt, + iterations=kdf_iterations, + backend=backend) + kdf_key = kdf.derive(passphrase) + + # AES GCM + algo = "aes" + mode = "gcm" + associated_data = "" + tag_size = 128 + ts = int(tag_size / 8) + + iv = bytes(os.urandom(16)) # 128 bit + + backend = default_backend() + cipher = Cipher(algorithms.AES(kdf_key), + modes.GCM(iv, min_tag_length=ts), + backend=backend) + encryptor = cipher.encryptor() + encryptor.authenticate_additional_data(bytes(associated_data, 'utf-8')) + + # zlib decompress in privatebin doesn't work + #compressed_blob = base64.b64encode(zlib.compress(bytes(plaintext, 'utf-8'), 0)) + #compressed_blob = base64.b64encode(bytes(plaintext, 'utf-8')) + compressed_blob = bytes(plaintext, 'utf-8') + + cipher_text = encryptor.update(compressed_blob) + encryptor.finalize() + + cipher_text = cipher_text + encryptor.tag[:ts] + + # validate + #decryptor = cipher.decryptor() + #decryptor.update(cipher_text) + decryptor.finalize() + + # JSON object properties + # + # iv: random initialization vector, encoded as base64 string + # v: version number of the SJCL data format, currently always 1 + # iter: number of iterations, by default 1000 + # ks: key size in bits, should ideally be 256, but SJCL also supports 128 bit keys + # ts: authentication strength in bits, should ideally be 128, but SJCL also supports 64 + # mode: encryption mode, we just switched to gcm by default, but before v1.0 it was ccm + # adata: optional clear text authentication data (of which we currently make no use) + # cipher: cipher algorithm, only aes is supported by SJCL for symmetric encryption + # salt: the salt, encoded as base64 string + # ct: cipher text, encoded as base64 string + + cipher_data = {"iv": base64.b64encode(iv).decode("utf-8"), + "v": 1, + "iter": kdf_iterations, + "ks": kdf_keysize, + "ts": tag_size, + "mode": mode, + "adata": associated_data, + "cipher": algo, + "salt": base64.b64encode(kdf_salt).decode("utf-8"), + "ct": base64.b64encode(cipher_text).decode("utf-8")} + + return cipher_data + default_lang = 'text' # Map numpy to python because the numpy lexer gives false positives @@ -109,6 +185,7 @@ if not paste_data: print("Oops, we have no data") sys.exit(1) +lang = None # API expects lower case language option if options.source: paste_formatter = 'Source Code' @@ -141,19 +218,16 @@ if lang != 'text only': if int(options.expire) != 86400: options.expire = int(options.expire) * 60 -cipher_data = {"iv": "83Ax/OdUav3SanDW9dcQPg", - "v": 1, - "iter": 1000, - "ks": 128, - "ts": 64, - "mode": "ccm", - "adata": "", - "cipher": "aes", - "salt": "Gx1vA2/gQ3U", - "ct": "j7ImByuE5xCqD2YXm6aSyA"} +paste_password = None +if options.password: + paste_password = options.password + +paste_passphrase = base64.b64encode(bytes(os.urandom(32))) + +cipher_data = sjcl_encrypt(paste_data, paste_passphrase) # json payload -payload = {'data': cipher_data, +payload = {'data': json.dumps(cipher_data, ensure_ascii=False), 'expire': options.expire, 'formatter': paste_formatter, 'burnafterreading': paste_burn, @@ -162,29 +236,27 @@ payload = {'data': cipher_data, # http content type headers = {'X-Requested-With':'JSONHttpRequest'} -#r = requests.post(paste_url + '/?data=' + json.dumps(cipher_data) + -# '&expire=' + options.expire + -# '&formatter=' + paste_formatter + -# '&burnafterreading=' + str(paste_burn) + -# '&opendiscussion=' + str(paste_opendicussion), -# headers=headers) r = requests.post(paste_url, - data=json.dumps(payload), + data=payload, headers=headers) -print('result: %s' % r.text) +#print('result: %s' % r.text) +#print('') +#print('') -#status = json.loads(r.text)['status'] -#result = json.loads(r.text)['status'] +paste_status = json.loads(r.text)['status'] +if paste_status: + paste_message = json.loads(r.text)['message'] + print("Oops, error: %s" % paste_message) + sys.exit(1) -#if result.get(u'error', None): -# print("Oops, error with code %(error)s" % result) -# sys.exit(1) -# -#hash = result.get(u'hash', None) -#if hash and len(hash) > 0: -# print(paste_url + "/%(id)s/%(hash)s" % result) -#else: -# print(paste_url + "/%(id)s" % result) + +# result: {"status":0,"id":"fceecc2ba163a632","url":"\/?fceecc2ba163a632","deletetoken":"b64e93f516a6e89801c6b2db0b22a22a531a3c8a0f53640592af9aec6c08fc41"} +paste_id = json.loads(r.text)['id'] +paste_deletetoken = json.loads(r.text)['deletetoken'] + +print('Delete paste: %s/?pasteid=%s&deletetoken=%s' % (paste_url, paste_id, paste_deletetoken)) +print('') +print('### Paste (%s): %s/?%s#%s' % (paste_formatter, paste_url, paste_id, paste_passphrase.decode('utf-8'))) sys.exit(0) |