summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Schneider <asn@cryptomilk.org>2017-02-16 22:18:38 +0100
committerAndreas Schneider <asn@cryptomilk.org>2017-02-21 15:43:54 +0100
commite3b53a270ba8e7b56511aaba4be8b3dea65aebe3 (patch)
tree1e5f1a17927ecee4c8792da7dd1bbba5cd9b975c
parent5b83fc14f307d68b4f615b6f0a01e9d67ceda05d (diff)
downloadcpaste-e3b53a270ba8e7b56511aaba4be8b3dea65aebe3.tar.gz
cpaste-e3b53a270ba8e7b56511aaba4be8b3dea65aebe3.tar.xz
cpaste-e3b53a270ba8e7b56511aaba4be8b3dea65aebe3.zip
Add PrivateBin AES encryption
-rwxr-xr-xcpaste132
1 files changed, 102 insertions, 30 deletions
diff --git a/cpaste b/cpaste
index 4da1dc2..cbbe126 100755
--- a/cpaste
+++ b/cpaste
@@ -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)