Browse Source

initial upload

pull/4/head
R4SAS 8 years ago
commit
a46f03ec83
  1. 3
      .gitignore
  2. 4
      README.md
  3. 40
      pbincli.py
  4. 0
      pbincli/__init__.py
  5. 7
      pbincli/actions.py
  6. 152
      pbincli/sjcl_gcm.py
  7. 15
      pbincli/utils.py
  8. 1
      requirements.txt

3
.gitignore vendored

@ -0,0 +1,3 @@
__pycache__
*.pyc
venv/

4
README.md

@ -0,0 +1,4 @@
PBinCLI
========
PrivateBin CLI (in development)

40
pbincli.py

@ -0,0 +1,40 @@
#! /usr/bin/env python3
import os
import sys
import argparse
import pbincli.actions
from pbincli.utils import PBinCLIException
def main():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title="actions", help="List of commands")
# a send command
send_parser = subparsers.add_parser("send", description="Send data to PrivateBin instance", usage="""%(prog)s --cert data/user_at_mail.i2p.crt --private-key data/priv_key.pem --signer-id user@mail.i2p""")
send_parser.add_argument("-b", "--burn", default=False, action="store_true", help="Burn after reading")
send_parser.add_argument("-d", "--discus", default=False, action="store_true", help="Open discussion")
send_parser.add_argument("-e", "--expire", default="1day", action="store", help="Expiration of paste")
send_parser.add_argument("-f", "--format", default="plaintext", action="store", help="Format of text paste")
send_parser.add_argument("-p", "--password", default=None, help="RSA private key (default: data/priv_key.pem)")
send_parser.add_argument("filename", type=argparse.FileType('r'), help="Filename (example: image.jpg)")
send_parser.set_defaults(func=pbincli.actions.send)
'''# a get command
get_parser = subparsers.add_parser("get", description="Recieve data from PrivateBin instance", usage="""echo $YOUR_PASSWORD | %(prog)s --netdb /path/to/netDb --private-key data/priv_key.pem --outfile output/i2pseeds.su3 --signer-id user@mail.i2p""")
get_parser.add_argument("--signer-id", required=True, help="Identifier of certificate (example: user@mail.i2p)")
get_parser.add_argument("--private-key", default="data/priv_key.pem", help="RSA private key (default: data/priv_key.pem)")
get_parser.add_argument("-o", "--outfile", default="output/i2pseeds.su3", help="Output file (default: output/i2pseeds.su3)")
get_parser.add_argument("--netdb", required=True, help="Path to netDb folder (example: ~/.i2pd/netDb)")
get_parser.set_defaults(func=pyseeder.actions.reseed)'''
# parse arguments
args = parser.parse_args()
if hasattr(args, "func"):
try:
args.func(args)
except PBinCLIException as pe:
print("PBinCLI error: {}".format(pe))
sys.exit(1)
else:
parser.print_help()
if __name__ == "__main__":
main()

0
pbincli/__init__.py

7
pbincli/actions.py

@ -0,0 +1,7 @@
"""Action functions for argparser"""
import pbincli.sjcl_gcm
import pbincli.actions
from pbincli.utils import PBinCLIException, check_readable, check_writable
def send(args):
print("Meow!")

152
pbincli/sjcl_gcm.py

@ -0,0 +1,152 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Edited SJCL.py library (https://github.com/berlincode/sjcl/)
for AES.MODE_GCM support, work begin.
pip3.5 install pycryptodome
"""
from Crypto.Hash import SHA256, HMAC
#pip3.5 install pycryptodome
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
import base64
def truncate_iv(iv, ol, tlen): # ol and tlen in bits
ivl = len(iv) # iv length in bytes
ol = (ol - tlen) // 8
# "compute the length of the length" (see gcm.js)
L = 2
while (L < 4) and ((ol >> (8*L))) > 0:
L += 1
if L < 15 - ivl:
L = 15 - ivl
return iv[:(15-L)]
def check_mode_gcm():
# checks if pycrypto has support for gcm
try:
AES.MODE_GCM
except:
raise Exception(
"Pycrypto does not seem to support MODE_gcm. " +
"You need a version >= 2.7a1 (or a special branch)."
)
class SJCL(object):
def __init__(self):
self.salt_size = 8 # bytes
self.tag_size = 16 # bytes
self.mac_size = 16 # bytes; mac = message authentication code (MAC)
self.prf = lambda p, s: HMAC.new(p, s, SHA256).digest()
def decrypt(self, data, passphrase):
check_mode_gcm() # check gcm support
if data["cipher"] != "aes":
raise Exception("only aes cipher supported")
print(data["mode"])
if data["mode"] != "gcm":
raise Exception("unknown mode(!=gcm)")
if data["adata"] != "":
raise Exception("additional authentication data not equal ''")
if data["v"] != 1:
raise Exception("only version 1 is currently supported")
if data["ts"] != self.tag_size * 8:
raise Exception("desired tag length != %d" % (self.tag_size * 8))
salt = base64.b64decode(data["salt"])
# print "salt", hex_string(salt)
if len(salt) != self.salt_size:
raise Exception("salt should be %d bytes long" % self.salt_size)
dkLen = data["ks"]//8
if dkLen != 16:
raise Exception("key length should be 16 bytes")
key = PBKDF2(
passphrase,
salt,
count=data['iter'],
dkLen=dkLen,
prf=self.prf
)
# print "key", hex_string(key)
ciphertext = base64.b64decode(data["ct"])
iv = base64.b64decode(data["iv"])
# print AES.block_size
nonce = truncate_iv(iv, len(ciphertext)*8, data["ts"])
# split tag from ciphertext (tag was simply appended to ciphertext)
mac = ciphertext[-(data["ts"]//8):]
# print len(ciphertext)
ciphertext = ciphertext[:-(data["ts"]//8)]
# print len(ciphertext)
# print len(tag)
# print "len", len(nonce)
cipher = AES.new(key, AES.MODE_GCM, nonce)
plaintext = cipher.decrypt(ciphertext)
print(mac)
# cipher.verify(mac)
return plaintext
def encrypt(self, plaintext, passphrase, count=1000, dkLen=16):
# dkLen = key length in bytes
check_mode_gcm() # check gcm support
salt = get_random_bytes(self.salt_size)
iv = get_random_bytes(16) # TODO dkLen?
key = PBKDF2(passphrase, salt, count=count, dkLen=dkLen, prf=self.prf)
# TODO plaintext padding?
nonce = truncate_iv(iv, len(plaintext) * 8, self.tag_size * 8)
# print len(nonce)
cipher = AES.new(
key,
AES.MODE_GCM,
nonce,
mac_len=self.mac_size
)
ciphertext = cipher.encrypt(plaintext)
mac = cipher.digest()
ciphertext = ciphertext + mac
return {
"salt": base64.b64encode(salt),
"iter": count,
"ks": dkLen*8,
"ct": base64.b64encode(ciphertext),
"iv": base64.b64encode(iv),
"cipher": "aes",
"mode": "gcm",
"adata": "",
"v": 1,
"ts": self.tag_size * 8
}

15
pbincli/utils.py

@ -0,0 +1,15 @@
"""Various code"""
import os
class PBinCLIException(Exception):
pass
def check_readable(f):
"""Checks if path exists and readable"""
if not os.path.exists(f) or not os.access(f, os.R_OK):
raise PBinCLIException("Error accessing path: {}".format(f))
def check_writable(f):
"""Checks if path is writable"""
if not os.access(os.path.dirname(f) or ".", os.W_OK):
raise PBinCLIException("Path is not writable: {}".format(f))

1
requirements.txt

@ -0,0 +1 @@
pycryptodome
Loading…
Cancel
Save