Skip to content

Commit

Permalink
Merge pull request #13 from kushaldas/cleaning_up_api
Browse files Browse the repository at this point in the history
Cleaning up api
  • Loading branch information
kushaldas committed Jul 15, 2020
2 parents 6f33b8c + 50b5287 commit 232b104
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 115 deletions.
79 changes: 6 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ It uses amazing [sequoia-pgp](https://sequoia-pgp.org/) library for the actual O
### Build dependencies in Fedora

```
sudo dnf install nettle clang clang-devel
sudo dnf install nettle clang clang-devel nettle-dev
```


Expand All @@ -27,84 +27,17 @@ maturin develop

```Python
>>> import johnnycanencrypt as jce
>>> j = jce.Johnny("secret.asc")
>>> j = jce.Johnny("public.asc")
>>> data = j.encrypt_bytes(b"kushal \xf0\x9f\x90\x8d")
>>> print(data)
-----BEGIN PGP MESSAGE-----

wcFMAwhsWpR1vDokAQ//UQGjrmmPLP0Td8pELf8XZEPh6fY9Xad6XHH6vQjGwvjG
36kK8ejRqyLbZpwVOO1FUfiZt6AyaeIEeEagoolMxmFl67mWBHsw5Z2NUPhydAwJ
EX+VdFn6CtRzQ0xG3T7rOCrsR50COO13gc4fIAn7Rxj1DyjqlFvur10FNnxRm0iJ
jnOwPnWVWKwoROzevfQd1Oef0n4nbkDUuyrS9oHSRFhFF/9I9bGtJhho0VIIcmFG
YVkhR0+QROTZ4edKotUg0R3UUfHmfwT0XcybGWMG/8Nh3W8pYuxwDdtbSMNDZzxu
o9TdpLrgoRIkhyGmuYWrURrRN1hmce5B6XOagWu7aKL7pFhP7Vd6LLoliDwY4G6x
1yKHbSo/1FEof7WBDujCksuVedUO8Gs9giitR/p/U9PBadeyiW0CKTYiTURkkNiF
g79lVfbmM54eZ9zmU+PraVNpekYXvH3o+lvXd5T39mo4Y/dv2fDCjo2aiZ/bE56Q
yn/0Hhmj2ikrsUk3NcuhO4zxt+VLctJt+lfk+R2hal+6NTaRkREdprPp4ltAt/bm
8xwBmqp+FDdxGgY+ItJkG69vsIf4WpPsvBI37fVbeYqrPsaz9NGlz2QKdfQvaH7j
R7rgxf24H2FjbbyNuHF3tJJa4Kfpnhq4nkxA/EdRP9JcVm/X568jLayTLyJGmrbS
PAHlVMLSBXQDApkY+5Veu3teRR5M2BLPr7X/gfeNnTlbZ4kF5S+E+0bjTjrz+6oo
dcsnTYxmcAm9hdPjng==
=1IYb
-----END PGP MESSAGE-----



>>> result = j.decrypt_bytes(data.encode("utf-8"), "mysecretpassword")
>>> js = jce.Johnny("secret.asc")
>>> result = js.decrypt_bytes(data, "mysecretpassword")
>>> print(result.decode("utf-8"))
kushal 🐍

```

## Quick API documentation

Remember that this will change a lot in the coming days.


```Python
import johnnycanencrypt as jce
```

To create new RSA4096 size key, call `jce.newkey("password", "userid")`, both *password* and *userid* are Python str.
Remember to save them into different files ending with *.asc*.

To do any encryption/decryption we have to create an object of the **Johnny** class with the private or public key file.
Remember, except **password** input, every else takes `bytes` as input type.

### Signing a file with detached signature

```Python
j = Johnny("private.asc")
signature = j.sign_file_detached(b"filename.txt", "password")
with open(b"filename.txt.asc", "wb") as f:
f.write(signature)
```

### Verifying a signature


```Python
j = Johnny("public.asc")
with open(b"filename.txt.asc", "b") as f:
sig = f.read()

verified = j.verify_file(b"filename.txt", sig)
print(f"Verified: {verified}")
```

For signing and verifying there are similar method available for bytes, `verify_bytes`, `sign_bytes_detached`.


### Encrypting and decrypting files

```Python
j = jce.Johnny("public.asc")
assert j.encrypt_file(inputfile, output_file_path)
jp = jce.Johnny("secret.asc")

result = jp.decrypt_file(output_file_path, decrypted_output_path, "password")
```
## API documentation

Note that, in this context, `inputfile`, `output_file_path`, and `decrypted_output_path` should be binary, not strings.
Please go through the [full API documentation](https://johnnycanencrypt.readthedocs.io/en/latest/) for detailed descriptions.

## LICENSE: GPLv3+
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

- If the public/secret key file is missing, while trying to create a `Johnny` object will raise `FileNotFound` error.
- If one tries to decrypt using a public key file, it will throw `AttributeError`.
- `encrypt_bytes` now returns bytes (instead of string).
- `encrypt_bytes` takes a third argument, `armor` as boolean, to return ascii-armored bytes or not.
- `encrypt_file` takes a third argument, `armor` as boolean, writes the output file ascii armored if true.

## [0.1.0] - 2020-07-11

Expand Down
92 changes: 92 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
API Documentation
==================

For the rest of the documentation we assume that you imported the module as following.

::


>>> import johnnycanencrypt as jce


.. function:: newkey(password, userid)

Use the `newkey` function in the module to create a new keypair. It takes two arguments as str, a password, and userid.
By default it creates the key with RSA4096, and returns a tuple of public,secret key as str. Raises `FileNotFound` error
if the key file can not be accessed.

::

>>> public, secret = jce.newkey("my super secret password using diceware", "test <[email protected]>")


.. note:: Remember to save both the public and serect keys in a file to use in future.


.. class:: Johnny(filepath)

It creates an object of type `Johnny`, you can provide path to the either public key, or the private key based on the operation
you want to do.

.. method:: encrypt_bytes(data: bytes, armor=False)

This method encrypts the given bytes and returns the encrypted bytes. If you pass `armor=True` to the method, then the
returned value will be ascii armored bytes.

::

>>> j = jce.Johnny("tests/files/public.asc")
>>> enc = j.encrypt_bytes(b"mysecret", armor=True)


.. method:: encrypt_file(inputfile: bytes, output: bytes, armor=False)

This method encrypts the given inputfile and writes the raw encrypted bytes to the output path. If you pass `armor=True` to the method, then the
output file will be written as ascii armored.

::

>>> j = jce.Johnny("tests/files/public.asc")
>>> enc = j.encrypt_file(b"blueleaks.tar.gz", b"notblueleaks.tar.gz.pgp", armor=True)


.. method:: decrypt_bytes(data: bytes, password: str)

Decrypts the given bytes based on the secret key and given password. If you try to decrypt while just using the public key,
then it will raise `AttributeError`.

::

>>> jp = jce.Johnny("tests/files/secret.asc")
>>> result = jp.decrypt_bytes(enc, "redhat")


.. method:: decrypt_file(inputfile: bytes, output: bytes, password: str)

Decrypts the inputfile path (in bytes) and wrties the decrypted data to the `output` file. Both the filepaths to be given as bytes.

::

>>> jp = jce.Johnny("tests/files/secret.asc")
>>> result = jp.decrypt_file(b"notblueleaks.tar.gz.pgp", "blueleaks.tar.gz", "redhat")


.. method:: sign_bytes_detached(data: bytes, pasword: str)

Signs the given bytes and returns the detached ascii armored signature as bytes.

::

>>> j = jce.Johnny("tests/files/secret.asc")
>>> signature = j.sign_bytes_detached(b"mysecret", "redhat")

.. note:: Remember to save the signature somewhere on disk.

.. method:: verify_bytes(data: bytes, signature: bytes)

Verifies if the signature is correct for the given data (as bytes). Returns `True` or `False`.

::

>>> j = jce.Johnny("tests/files/secret.asc")
>>> j.verify_bytes(encrypted_bytes, signature)
31 changes: 31 additions & 0 deletions docs/build.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Building Johnny Can Encrypt
============================

Building this module requires Rust's nightly toolchain. You can install it following
the instructions from `rustup.rs <https://rustup.rs>`_.

You will need `libnettle` and `libnettle-dev` & `clang` (on Debian/Ubuntu) and `nettle` & `nettle-dev` & `clang` packages in Fedora.

Then you can follow the steps below to build a wheel.

::

python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install requirements-dev.txt
maturin build

Only to build and test locally, you should execute

::

maturin develop

## How to run the tests?

After you did the `maturin develop` as mentioned above, execute the following command.

::

python3 -m pytest -vvv

6 changes: 6 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
Welcome to Johnny Can Encrypt's documentation!
==============================================

This is a Python module providing encryption and decryption operations based on OpenPGP. It
uses `sequoia-pgp <https://sequoia-pgp.org>`_ project for the actual operations. This module
is written.

.. toctree::
:maxdepth: 2
:caption: Contents:

build
api


Indices and tables
Expand Down
103 changes: 74 additions & 29 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,12 @@ impl Johnny {
Ok(Johnny { filepath, cert })
}

pub fn encrypt_bytes(&self, data: Vec<u8>) -> PyResult<String> {
pub fn encrypt_bytes(
&self,
py: Python,
data: Vec<u8>,
armor: Option<bool>,
) -> PyResult<PyObject> {
let mode = KeyFlags::default().set_storage_encryption(true);
let p = &P::new();
let recipients = self
Expand All @@ -263,11 +268,15 @@ impl Johnny {
.alive()
.revoked(false)
.key_flags(&mode);
// TODO: Find better way to do this in rust
let mut result = Vec::new();
let mut sink = armor::Writer::new(&mut result, armor::Kind::Message)?;
let mut result2 = Vec::new();
let mut sink = armor::Writer::new(&mut result2, armor::Kind::Message)?;
// Stream an OpenPGP message.
let message = Message::new(&mut sink);

let message = match armor {
Some(true) => Message::new(&mut sink),
_ => Message::new(&mut result),
};
// We want to encrypt a literal data packet.
let encryptor = Encryptor::for_recipients(message, recipients)
.build()
Expand All @@ -285,9 +294,18 @@ impl Johnny {
// writer stack.
literal_writer.finalize().unwrap();

// Finalize the armor writer.
sink.finalize().expect("Failed to write data");
Ok(str::from_utf8(&result).unwrap().to_string())
match armor {
Some(true) => {
// Finalize the armor writer.
sink.finalize().expect("Failed to write data");
let res = PyBytes::new(py, &result2);
return Ok(res.into());
}
_ => {
let res = PyBytes::new(py, &result);
return Ok(res.into());
}
}
}

pub fn decrypt_bytes(&self, py: Python, data: Vec<u8>, password: String) -> PyResult<PyObject> {
Expand All @@ -303,7 +321,7 @@ impl Johnny {
let res = PyBytes::new(py, &result);
Ok(res.into())
}
pub fn encrypt_file(&self, filepath: Vec<u8>, output: Vec<u8>) -> PyResult<bool> {
pub fn encrypt_file(&self, filepath: Vec<u8>, output: Vec<u8>, armor: Option<bool>) -> PyResult<bool> {
let mode = KeyFlags::default().set_storage_encryption(true);
let p = &P::new();
let recipients = self
Expand All @@ -315,28 +333,55 @@ impl Johnny {
.key_flags(&mode);
let mut input = File::open(str::from_utf8(&filepath[..]).unwrap()).unwrap();
let mut outfile = File::create(str::from_utf8(&output[..]).unwrap()).unwrap();
// Stream an OpenPGP message.
let message = Message::new(&mut outfile);

// We want to encrypt a literal data packet.
let encryptor = Encryptor::for_recipients(message, recipients)
.build()
.expect("Failed to create encryptor");

let mut literal_writer = LiteralWriter::new(encryptor)
.build()
.expect("Failed to create literal writer");

// Copy stdin to our writer stack to encrypt the data.
io::copy(&mut input, &mut literal_writer).expect("Failed to encrypt");
//literal_writer.write_all(&data).unwrap();

// Finally, finalize the OpenPGP message by tearing down the
// writer stack.
literal_writer.finalize().unwrap();
// TODO: Find better ways to write this code
match armor {
// For armored output file.
Some(true) => {
let mut sink = armor::Writer::new(&mut outfile, armor::Kind::Message).unwrap();
// Stream an OpenPGP message.
let message = Message::new(&mut sink);

// We want to encrypt a literal data packet.
let encryptor = Encryptor::for_recipients(message, recipients)
.build()
.expect("Failed to create encryptor");

let mut literal_writer = LiteralWriter::new(encryptor)
.build()
.expect("Failed to create literal writer");

// Copy stdin to our writer stack to encrypt the data.
io::copy(&mut input, &mut literal_writer).expect("Failed to encrypt");
//literal_writer.write_all(&data).unwrap();

// Finally, finalize the OpenPGP message by tearing down the
// writer stack.
literal_writer.finalize().unwrap();

// Finalize the armor writer.
sink.finalize().expect("Failed to write data");}
_ => {
let message = Message::new(&mut outfile);

// We want to encrypt a literal data packet.
let encryptor = Encryptor::for_recipients(message, recipients)
.build()
.expect("Failed to create encryptor");

let mut literal_writer = LiteralWriter::new(encryptor)
.build()
.expect("Failed to create literal writer");

// Copy stdin to our writer stack to encrypt the data.
io::copy(&mut input, &mut literal_writer).expect("Failed to encrypt");
//literal_writer.write_all(&data).unwrap();

// Finally, finalize the OpenPGP message by tearing down the
// writer stack.
literal_writer.finalize().unwrap();
}
}

// Finalize the armor writer.
//sink.finalize().expect("Failed to write data");
Ok(true)
}

Expand Down
Loading

0 comments on commit 232b104

Please sign in to comment.