Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0f23094bc | ||
|
|
86061e595c | ||
|
|
49362c70a6 | ||
|
|
70328903fa | ||
|
|
29498b9315 | ||
|
|
19f130feb1 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -131,3 +131,6 @@ venv.bak/
|
|||||||
|
|
||||||
# PyCharm project settings
|
# PyCharm project settings
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
# PBinCLI downloaded paste text
|
||||||
|
paste-*.txt
|
||||||
|
|||||||
3
MANIFEST.in
Normal file
3
MANIFEST.in
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
include LICENSE
|
||||||
|
include README.rst
|
||||||
|
include requirements.txt
|
||||||
@@ -2,6 +2,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
__author__ = "R4SAS <r4sas@i2pmail.org>"
|
__author__ = "R4SAS <r4sas@i2pmail.org>"
|
||||||
__version__ = "0.3.0"
|
__version__ = "0.3.1"
|
||||||
__copyright__ = "Copyright (c) R4SAS"
|
__copyright__ = "Copyright (c) R4SAS"
|
||||||
__license__ = "MIT"
|
__license__ = "MIT"
|
||||||
|
|||||||
@@ -36,19 +36,21 @@ def main():
|
|||||||
send_parser.add_argument("-q", "--notext", default=False, action="store_true", help="don't send text in paste")
|
send_parser.add_argument("-q", "--notext", default=False, action="store_true", help="don't send text in paste")
|
||||||
send_parser.add_argument("-c", "--compression", default="zlib", action="store",
|
send_parser.add_argument("-c", "--compression", default="zlib", action="store",
|
||||||
choices=["zlib", "none"], help="set compression for paste (default: zlib). Note: works only on v2 paste format")
|
choices=["zlib", "none"], help="set compression for paste (default: zlib). Note: works only on v2 paste format")
|
||||||
# URL shortener
|
## URL shortener
|
||||||
send_parser.add_argument("-S", "--short", default=False, action="store_true", help="use URL shortener")
|
send_parser.add_argument("-S", "--short", default=False, action="store_true", help="use URL shortener")
|
||||||
send_parser.add_argument("--short-api", default=argparse.SUPPRESS, action="store", choices=["tinyurl", "clckru", "isgd", "vgd", "cuttly", "yourls"], help="API used by shortener service")
|
send_parser.add_argument("--short-api", default=argparse.SUPPRESS, action="store",
|
||||||
|
choices=["tinyurl", "clckru", "isgd", "vgd", "cuttly", "yourls"], help="API used by shortener service")
|
||||||
send_parser.add_argument("--short-url", default=argparse.SUPPRESS, help="URL of shortener service API")
|
send_parser.add_argument("--short-url", default=argparse.SUPPRESS, help="URL of shortener service API")
|
||||||
send_parser.add_argument("--short-user", default=argparse.SUPPRESS, help="Shortener username")
|
send_parser.add_argument("--short-user", default=argparse.SUPPRESS, help="Shortener username")
|
||||||
send_parser.add_argument("--short-pass", default=argparse.SUPPRESS, help="Shortener password")
|
send_parser.add_argument("--short-pass", default=argparse.SUPPRESS, help="Shortener password")
|
||||||
send_parser.add_argument("--short-token", default=argparse.SUPPRESS, help="Shortener token")
|
send_parser.add_argument("--short-token", default=argparse.SUPPRESS, help="Shortener token")
|
||||||
# Connection options
|
## Connection options
|
||||||
send_parser.add_argument("-s", "--server", default=argparse.SUPPRESS, help="PrivateBin service URL (default: https://paste.i2pd.xyz/)")
|
send_parser.add_argument("-s", "--server", default=argparse.SUPPRESS, help="PrivateBin service URL (default: https://paste.i2pd.xyz/)")
|
||||||
send_parser.add_argument("-x", "--proxy", default=argparse.SUPPRESS, help="Proxy server address (default: None)")
|
send_parser.add_argument("-x", "--proxy", default=argparse.SUPPRESS, help="Proxy server address (default: None)")
|
||||||
send_parser.add_argument("--no-check-certificate", default=False, action="store_true", help="disable certificate validation")
|
send_parser.add_argument("--no-check-certificate", default=False, action="store_true", help="disable certificate validation")
|
||||||
send_parser.add_argument("--no-insecure-warning", default=False, action="store_true", help="suppress InsecureRequestWarning (only with --no-check-certificate)")
|
send_parser.add_argument("--no-insecure-warning", default=False, action="store_true",
|
||||||
#
|
help="suppress InsecureRequestWarning (only with --no-check-certificate)")
|
||||||
|
##
|
||||||
send_parser.add_argument("-d", "--debug", default=False, action="store_true", help="enable debug")
|
send_parser.add_argument("-d", "--debug", default=False, action="store_true", help="enable debug")
|
||||||
send_parser.add_argument("--dry", default=False, action="store_true", help="invoke dry run")
|
send_parser.add_argument("--dry", default=False, action="store_true", help="invoke dry run")
|
||||||
send_parser.add_argument("stdin", help="input paste text from stdin", nargs="?", type=argparse.FileType("r"), default=sys.stdin)
|
send_parser.add_argument("stdin", help="input paste text from stdin", nargs="?", type=argparse.FileType("r"), default=sys.stdin)
|
||||||
@@ -58,8 +60,13 @@ def main():
|
|||||||
get_parser = subparsers.add_parser("get", description="Get data from PrivateBin instance")
|
get_parser = subparsers.add_parser("get", description="Get data from PrivateBin instance")
|
||||||
get_parser.add_argument("pasteinfo", help="example: aabb#cccddd")
|
get_parser.add_argument("pasteinfo", help="example: aabb#cccddd")
|
||||||
get_parser.add_argument("-p", "--password", help="password for decrypting paste")
|
get_parser.add_argument("-p", "--password", help="password for decrypting paste")
|
||||||
|
## Connection options
|
||||||
|
get_parser.add_argument("-s", "--server", default=argparse.SUPPRESS, help="PrivateBin service URL (default: https://paste.i2pd.xyz/)")
|
||||||
|
get_parser.add_argument("-x", "--proxy", default=argparse.SUPPRESS, help="Proxy server address (default: None)")
|
||||||
get_parser.add_argument("--no-check-certificate", default=False, action="store_true", help="disable certificate validation")
|
get_parser.add_argument("--no-check-certificate", default=False, action="store_true", help="disable certificate validation")
|
||||||
get_parser.add_argument("--no-insecure-warning", default=False, action="store_true", help="suppress InsecureRequestWarning (only with --no-check-certificate)")
|
get_parser.add_argument("--no-insecure-warning", default=False, action="store_true",
|
||||||
|
help="suppress InsecureRequestWarning (only with --no-check-certificate)")
|
||||||
|
##
|
||||||
get_parser.add_argument("-d", "--debug", default=False, action="store_true", help="enable debug")
|
get_parser.add_argument("-d", "--debug", default=False, action="store_true", help="enable debug")
|
||||||
get_parser.set_defaults(func=pbincli.actions.get)
|
get_parser.set_defaults(func=pbincli.actions.get)
|
||||||
|
|
||||||
@@ -67,8 +74,13 @@ def main():
|
|||||||
delete_parser = subparsers.add_parser("delete", description="Delete paste from PrivateBin instance using token")
|
delete_parser = subparsers.add_parser("delete", description="Delete paste from PrivateBin instance using token")
|
||||||
delete_parser.add_argument("-p", "--paste", required=True, help="paste id")
|
delete_parser.add_argument("-p", "--paste", required=True, help="paste id")
|
||||||
delete_parser.add_argument("-t", "--token", required=True, help="paste deletion token")
|
delete_parser.add_argument("-t", "--token", required=True, help="paste deletion token")
|
||||||
|
## Connection options
|
||||||
|
delete_parser.add_argument("-s", "--server", default=argparse.SUPPRESS, help="PrivateBin service URL (default: https://paste.i2pd.xyz/)")
|
||||||
|
delete_parser.add_argument("-x", "--proxy", default=argparse.SUPPRESS, help="Proxy server address (default: None)")
|
||||||
delete_parser.add_argument("--no-check-certificate", default=False, action="store_true", help="disable certificate validation")
|
delete_parser.add_argument("--no-check-certificate", default=False, action="store_true", help="disable certificate validation")
|
||||||
delete_parser.add_argument("--no-insecure-warning", default=False, action="store_true", help="suppress InsecureRequestWarning (only with --no-check-certificate)")
|
delete_parser.add_argument("--no-insecure-warning", default=False, action="store_true",
|
||||||
|
help="suppress InsecureRequestWarning (only with --no-check-certificate)")
|
||||||
|
##
|
||||||
delete_parser.add_argument("-d", "--debug", default=False, action="store_true", help="enable debug")
|
delete_parser.add_argument("-d", "--debug", default=False, action="store_true", help="enable debug")
|
||||||
delete_parser.set_defaults(func=pbincli.actions.delete)
|
delete_parser.set_defaults(func=pbincli.actions.delete)
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,33 @@
|
|||||||
from Crypto.Random import get_random_bytes
|
|
||||||
from Crypto.Cipher import AES
|
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
from pbincli.utils import PBinCLIError
|
from pbincli.utils import PBinCLIError
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
|
# try import AES cipher and check if it has GCM mode (prevent usage of pycrypto)
|
||||||
|
try:
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
if not hasattr(AES, 'MODE_GCM'):
|
||||||
|
try:
|
||||||
|
from Cryptodome.Cipher import AES
|
||||||
|
from Cryptodome.Random import get_random_bytes
|
||||||
|
except ImportError:
|
||||||
|
PBinCLIError("AES GCM mode is not found in imported crypto module.\n" +
|
||||||
|
"That can happen if you have installed pycrypto.\n\n" +
|
||||||
|
"We tried to import pycryptodomex but it is not available.\n" +
|
||||||
|
"Please install it via pip, if you still need pycrypto, by running:\n" +
|
||||||
|
"\tpip install pycryptodomex\n" +
|
||||||
|
"... otherwise use separate python environment or uninstall pycrypto:\n" +
|
||||||
|
"\tpip uninstall pycrypto")
|
||||||
|
else:
|
||||||
|
from Crypto.Random import get_random_bytes
|
||||||
|
except ImportError:
|
||||||
|
PBinCLIError("Unable import pycryptodome")
|
||||||
|
|
||||||
|
|
||||||
CIPHER_ITERATION_COUNT = 100000
|
CIPHER_ITERATION_COUNT = 100000
|
||||||
CIPHER_SALT_BYTES = 8
|
CIPHER_SALT_BYTES = 8
|
||||||
CIPHER_BLOCK_BITS = 256
|
CIPHER_BLOCK_BITS = 256
|
||||||
CIPHER_BLOCK_BYTES = int(CIPHER_BLOCK_BITS/8)
|
CIPHER_TAG_BITS = 128
|
||||||
CIPHER_TAG_BITS = int(CIPHER_BLOCK_BITS/2)
|
|
||||||
CIPHER_TAG_BYTES = int(CIPHER_TAG_BITS/8)
|
|
||||||
|
|
||||||
class Paste:
|
class Paste:
|
||||||
def __init__(self, debug=False):
|
def __init__(self, debug=False):
|
||||||
@@ -19,9 +37,13 @@ class Paste:
|
|||||||
self._text = ''
|
self._text = ''
|
||||||
self._attachment = ''
|
self._attachment = ''
|
||||||
self._attachment_name = ''
|
self._attachment_name = ''
|
||||||
self._key = get_random_bytes(CIPHER_BLOCK_BYTES)
|
|
||||||
self._password = ''
|
self._password = ''
|
||||||
self._debug = debug
|
self._debug = debug
|
||||||
|
self._iteration_count = CIPHER_ITERATION_COUNT
|
||||||
|
self._salt_bytes = CIPHER_SALT_BYTES
|
||||||
|
self._block_bits = CIPHER_BLOCK_BITS
|
||||||
|
self._tag_bits = CIPHER_TAG_BITS
|
||||||
|
self._key = get_random_bytes(int(self._block_bits / 8))
|
||||||
|
|
||||||
|
|
||||||
def setVersion(self, version):
|
def setVersion(self, version):
|
||||||
@@ -105,8 +127,8 @@ class Paste:
|
|||||||
return PBKDF2(
|
return PBKDF2(
|
||||||
self._key + self._password.encode(),
|
self._key + self._password.encode(),
|
||||||
salt,
|
salt,
|
||||||
dkLen = CIPHER_BLOCK_BYTES,
|
dkLen = int(self._block_bits / 8),
|
||||||
count = CIPHER_ITERATION_COUNT,
|
count = self._iteration_count,
|
||||||
prf = lambda password, salt: HMAC.new(
|
prf = lambda password, salt: HMAC.new(
|
||||||
password,
|
password,
|
||||||
salt,
|
salt,
|
||||||
@@ -115,10 +137,10 @@ class Paste:
|
|||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __initializeCipher(self, key, iv, adata):
|
def __initializeCipher(self, key, iv, adata, tagsize):
|
||||||
from pbincli.utils import json_encode
|
from pbincli.utils import json_encode
|
||||||
|
|
||||||
cipher = AES.new(key, AES.MODE_GCM, nonce=iv, mac_len=CIPHER_TAG_BYTES)
|
cipher = AES.new(key, AES.MODE_GCM, nonce=iv, mac_len=tagsize)
|
||||||
cipher.update(json_encode(adata))
|
cipher.update(json_encode(adata))
|
||||||
return cipher
|
return cipher
|
||||||
|
|
||||||
@@ -173,16 +195,22 @@ class Paste:
|
|||||||
from json import loads as json_decode
|
from json import loads as json_decode
|
||||||
iv = b64decode(self._data['adata'][0][0])
|
iv = b64decode(self._data['adata'][0][0])
|
||||||
salt = b64decode(self._data['adata'][0][1])
|
salt = b64decode(self._data['adata'][0][1])
|
||||||
|
|
||||||
|
self._iteration_count = self._data['adata'][0][2]
|
||||||
|
self._block_bits = self._data['adata'][0][3]
|
||||||
|
self._tag_bits = self._data['adata'][0][4]
|
||||||
|
cipher_tag_bytes = int(self._tag_bits / 8)
|
||||||
|
|
||||||
key = self.__deriveKey(salt)
|
key = self.__deriveKey(salt)
|
||||||
|
|
||||||
# Get compression type from received paste
|
# Get compression type from received paste
|
||||||
self._compression = self._data['adata'][0][7]
|
self._compression = self._data['adata'][0][7]
|
||||||
|
|
||||||
cipher = self.__initializeCipher(key, iv, self._data['adata'])
|
cipher = self.__initializeCipher(key, iv, self._data['adata'], cipher_tag_bytes)
|
||||||
# Cut the cipher text into message and tag
|
# Cut the cipher text into message and tag
|
||||||
cipher_text_tag = b64decode(self._data['ct'])
|
cipher_text_tag = b64decode(self._data['ct'])
|
||||||
cipher_text = cipher_text_tag[:-CIPHER_TAG_BYTES]
|
cipher_text = cipher_text_tag[:-cipher_tag_bytes]
|
||||||
cipher_tag = cipher_text_tag[-CIPHER_TAG_BYTES:]
|
cipher_tag = cipher_text_tag[-cipher_tag_bytes:]
|
||||||
cipher_message = json_decode(self.__decompress(cipher.decrypt_and_verify(cipher_text, cipher_tag)).decode())
|
cipher_message = json_decode(self.__decompress(cipher.decrypt_and_verify(cipher_text, cipher_tag)).decode())
|
||||||
|
|
||||||
self._text = cipher_message['paste'].encode()
|
self._text = cipher_message['paste'].encode()
|
||||||
@@ -233,8 +261,8 @@ class Paste:
|
|||||||
def _encryptV2(self):
|
def _encryptV2(self):
|
||||||
from pbincli.utils import json_encode
|
from pbincli.utils import json_encode
|
||||||
|
|
||||||
iv = get_random_bytes(CIPHER_TAG_BYTES)
|
iv = get_random_bytes(int(self._tag_bits / 8))
|
||||||
salt = get_random_bytes(CIPHER_SALT_BYTES)
|
salt = get_random_bytes(self._salt_bytes)
|
||||||
key = self.__deriveKey(salt)
|
key = self.__deriveKey(salt)
|
||||||
|
|
||||||
# prepare encryption authenticated data and message
|
# prepare encryption authenticated data and message
|
||||||
@@ -242,9 +270,9 @@ class Paste:
|
|||||||
[
|
[
|
||||||
b64encode(iv).decode(),
|
b64encode(iv).decode(),
|
||||||
b64encode(salt).decode(),
|
b64encode(salt).decode(),
|
||||||
CIPHER_ITERATION_COUNT,
|
self._iteration_count,
|
||||||
CIPHER_BLOCK_BITS,
|
self._block_bits,
|
||||||
CIPHER_TAG_BITS,
|
self._tag_bits,
|
||||||
'aes',
|
'aes',
|
||||||
'gcm',
|
'gcm',
|
||||||
self._compression
|
self._compression
|
||||||
@@ -258,9 +286,12 @@ class Paste:
|
|||||||
cipher_message['attachment'] = self._attachment
|
cipher_message['attachment'] = self._attachment
|
||||||
cipher_message['attachment_name'] = self._attachment_name
|
cipher_message['attachment_name'] = self._attachment_name
|
||||||
|
|
||||||
cipher = self.__initializeCipher(key, iv, adata)
|
cipher = self.__initializeCipher(key, iv, adata, int(self._tag_bits /8 ))
|
||||||
ciphertext, tag = cipher.encrypt_and_digest(self.__compress(json_encode(cipher_message)))
|
ciphertext, tag = cipher.encrypt_and_digest(self.__compress(json_encode(cipher_message)))
|
||||||
|
|
||||||
|
if self._debug: print("PBKDF2 Key:\t{}\nCipherText:\t{}\nCipherTag:\t{}"
|
||||||
|
.format(b64encode(key), b64encode(ciphertext), b64encode(tag)))
|
||||||
|
|
||||||
self._data = {'v':2,'adata':adata,'ct':b64encode(ciphertext + tag).decode(),'meta':{'expire':self._expiration}}
|
self._data = {'v':2,'adata':adata,'ct':b64encode(ciphertext + tag).decode(),'meta':{'expire':self._expiration}}
|
||||||
|
|
||||||
|
|
||||||
@@ -268,7 +299,8 @@ class Paste:
|
|||||||
from sjcl import SJCL
|
from sjcl import SJCL
|
||||||
from pbincli.utils import json_encode
|
from pbincli.utils import json_encode
|
||||||
|
|
||||||
self._data = {'expire':self._expiration,'formatter':self._formatter,'burnafterreading':int(self._burnafterreading),'opendiscussion':int(self._discussion)}
|
self._data = {'expire':self._expiration,'formatter':self._formatter,
|
||||||
|
'burnafterreading':int(self._burnafterreading),'opendiscussion':int(self._discussion)}
|
||||||
|
|
||||||
password = self.__preparePassKey()
|
password = self.__preparePassKey()
|
||||||
if self._debug: print("Password:\t{}".format(password))
|
if self._debug: print("Password:\t{}".format(password))
|
||||||
|
|||||||
7
setup.py
7
setup.py
@@ -31,9 +31,14 @@ setup(
|
|||||||
],
|
],
|
||||||
packages=['pbincli'],
|
packages=['pbincli'],
|
||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
|
python_requires='>=3',
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
'pbincli=pbincli.cli:main',
|
'pbincli=pbincli.cli:main',
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
|
project_urls={
|
||||||
|
'Bug Reports': 'https://github.com/r4sas/PBinCLI/issues',
|
||||||
|
'Source': 'https://github.com/r4sas/PBinCLI/',
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user