Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Secure Cell passphrase API: PyThemis #596

Merged
merged 10 commits into from
Mar 18, 2020
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,20 @@ _Code:_

- Fixed compatibility issues on 32-bit platforms ([#555](https://github.com/cossacklabs/themis/pull/555)).
- New function `skeygen.GenerateSymmetricKey()` can be used to generate symmetric keys for Secure Cell ([#561](https://github.com/cossacklabs/themis/pull/561)).
- PyThemis now supports _passphrase API_ of Secure Cell in Seal mode ([#596](https://github.com/cossacklabs/themis/pull/596)).

```python
from pythemis.scell import SCellSeal

cell = SCellSeal(passphrase='my passphrase')

encrypted = cell.encrypt(b'message data')
decrypted = cell.decrypt(encrypted)
```

You can safely and securely use human-readable passphrases as strings with this new API.

Existing master key API (`SCellSeal(key=...)`) does not support passphrases. You should use it with symmetric encryption keys, such as generated by `GenerateSymmetricKey()` ([#561](https://github.com/cossacklabs/themis/pull/561)).

- **Ruby**

Expand Down
76 changes: 43 additions & 33 deletions docs/examples/python/scell_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,68 @@
from pythemis.scell import SCellContextImprint

message = "i'm plain text message"
passwrd = b"pass"
context = b"somecontext"
master_key = base64.b64decode("bm8sIHRoaXMgaXMgbm90IGEgdmFsaWQgbWFzdGVyIGtleQ==")
passphrase = b"secret passphrase"


print("running secure cell in seal mode...")
print("# Secure Cell in Seal mode\n")

scell = SCellSeal(passwrd)
print("## Master key API\n")

print("encrypting...")
encrypted_message = scell.encrypt(message.encode('utf-8'))
encrypted_message_string = base64.b64encode(encrypted_message)
print(encrypted_message_string)
scellMK = SCellSeal(key=master_key)

print("decrypting from binary... --> ")
decrypted_message = scell.decrypt(encrypted_message).decode('utf-8')
print(decrypted_message)
encrypted_message = scellMK.encrypt(message.encode('utf-8'))
print("Encrypted: " + base64.b64encode(encrypted_message).decode('ascii'))

decrypted_message = scellMK.decrypt(encrypted_message).decode('utf-8')
print("Decrypted: " + decrypted_message)

print("decrypting from string... --> ")
# check https://themis.cossacklabs.com/data-simulator/cell/
encrypted_message_string = "AAEBQAwAAAAQAAAADQAAACoEM9MbJzEu2RDuRoGzcQgN4jchys0q+LLcsbfUDV3M2eg/FhygH1ns"
# Visit https://docs.cossacklabs.com/simulator/data-cell/
print("")
encrypted_message_string = "AAEBQAwAAAAQAAAAEQAAAC0fCd2mOIxlDUORXz8+qCKuHCXcDii4bMF8OjOCOqsKEdV4+Ga2xTHPMupFvg=="
decrypted_message_from_string = base64.b64decode(encrypted_message_string)
decrypted_message = scell.decrypt(decrypted_message_from_string).decode('utf-8')
print(decrypted_message)
decrypted_message = scellMK.decrypt(decrypted_message_from_string).decode('utf-8')
print("Decrypted (simulator): " + decrypted_message)

print("")

print("----------------------")
print("running secure cell in token protect mode...")

scellTP = SCellTokenProtect(passwrd)
print("## Passphrase API\n")

print("encrypting...")
encrypted_message, additional_auth_data = scellTP.encrypt(message.encode('utf-8'))
encrypted_message_string = base64.b64encode(encrypted_message)
print(encrypted_message_string)
scellPW = SCellSeal(passphrase=passphrase)

print("decrypting from binary... --> ")
decrypted_message = scellTP.decrypt(encrypted_message, additional_auth_data).decode('utf-8')
print(decrypted_message)
encrypted_message = scellPW.encrypt(message.encode('utf-8'))
print("Encrypted: " + base64.b64encode(encrypted_message).decode('ascii'))

decrypted_message = scellPW.decrypt(encrypted_message).decode('utf-8')
print("Decrypted: " + decrypted_message)

print("----------------------")
print("running secure cell in context imprint mode...")
print("")


scellCI = SCellContextImprint(passwrd)
print("# Secure Cell in Token Protect mode\n")

scellTP = SCellTokenProtect(key=master_key)

encrypted_message, auth_token = scellTP.encrypt(message.encode('utf-8'))
print("Encrypted: " + base64.b64encode(encrypted_message).decode('ascii'))
print("Auth token: " + base64.b64encode(auth_token).decode('ascii'))

decrypted_message = scellTP.decrypt(encrypted_message, auth_token).decode('utf-8')
print("Decrypted: " + decrypted_message)

print("")


print("# Secure Cell in Context Imprint mode\n")

scellCI = SCellContextImprint(key=master_key)

print("encrypting...")
encrypted_message = scellCI.encrypt(message.encode('utf-8'), context)
encrypted_message_string = base64.b64encode(encrypted_message)
print(encrypted_message_string)
print("Encrypted: " + base64.b64encode(encrypted_message).decode('ascii'))

print("decrypting from binary... --> ")
decrypted_message = scellCI.decrypt(encrypted_message, context).decode('utf-8')
print(decrypted_message)
print("Decrypted: " + decrypted_message)

print("")
6 changes: 3 additions & 3 deletions docs/examples/python/scell_test_postgres.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import psycopg2.extras
from pythemis import scell

password = b"password"
master_key = base64.b64decode(b'c2NlbGxfeG1sX2Zvcm1hdC1wcmVzZXJ2aW5nX2VuY3J5cHRpb24ucHk=')

CREATE_SCELL_DATA_TABLE_SQL = ("CREATE TABLE IF NOT EXISTS scell_data ("
"id serial PRIMARY KEY, num bytea, data bytea);")
Expand All @@ -44,7 +44,7 @@ def init_table(connection):


def add_record(connection, field1, field2):
encryptor = scell.SCellTokenProtect(password)
encryptor = scell.SCellTokenProtect(master_key)
# encrypt field1
encrypted_field1, field1_auth_data = encryptor.encrypt(
field1.encode('utf-8'))
Expand Down Expand Up @@ -72,7 +72,7 @@ def add_record(connection, field1, field2):

def get_record(connection, id):
# retrieve record from db by id
dec = scell.SCellTokenProtect(password)
dec = scell.SCellTokenProtect(master_key)
with connection.cursor() as cursor:
cursor.execute(
"SELECT * FROM scell_data "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import base64
from pythemis import scell

password = b"password"
master_key = base64.b64decode(b'c2NlbGxfeG1sX2Zvcm1hdC1wcmVzZXJ2aW5nX2VuY3J5cHRpb24ucHk=')


def encrypt_children(node, context):
Expand Down Expand Up @@ -52,7 +52,7 @@ def decrypt_children(node, context):
# encoding file 'example_data/test.xml' and save result to encoded_data.xml
tree = ET.parse('example_data/test.xml')
root = tree.getroot()
encryptor = scell.SCellSeal(password)
encryptor = scell.SCellSeal(master_key)
encrypt_children(root, "")
tree.write("encoded_data.xml")

Expand Down
1 change: 1 addition & 0 deletions src/wrappers/themis/python/pythemis/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
class THEMIS_CODES(IntEnum):
NETWORK_ERROR = 2222
BUFFER_TOO_SMALL = 14
INVALID_PARAMETER = 12
FAIL = 11
SUCCESS = 0
SEND_AS_IS = 1
Expand Down
121 changes: 121 additions & 0 deletions src/wrappers/themis/python/pythemis/scell.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,42 @@
themis = cdll.LoadLibrary(find_library('themis'))


class SecureCellError(ThemisError):
def __init__(self, message, error_code=THEMIS_CODES.INVALID_PARAMETER):
message = 'Secure Cell: ' + message
super(SecureCellError, self).__init__(error_code, message)


class SCellSeal(object):
def __new__(cls, key=None, passphrase=None):
Lagovas marked this conversation as resolved.
Show resolved Hide resolved
"""
Make a new Secure Cell in Seal mode.

You must specify either key= or passphrase= keyword argument.

:type key: bytes
:type passphrase: Union(str, bytes)
"""
if key is None and passphrase is None:
raise SecureCellError('missing key or passphrase')
if key is not None and passphrase is not None:
raise SecureCellError('key and passphrase cannot be specified at the same time')
if key is not None:
return object.__new__(SCellSeal)
if passphrase is not None:
return object.__new__(SCellSealPassphrase)

def __init__(self, key):
"""
Make a new Secure Cell in Seal mode with a master key.

:type key: bytes
"""
if not key:
Lagovas marked this conversation as resolved.
Show resolved Hide resolved
raise ThemisError(THEMIS_CODES.FAIL,
"Secure Cell (Seal) failed creating")
if not isinstance(key, type(b'')):
raise SecureCellError('master key must be "bytes"')
self.key = key

def encrypt(self, message, context=None):
Expand Down Expand Up @@ -70,11 +101,99 @@ def decrypt(self, message, context=None):
return string_at(decrypted_message, decrypted_message_length.value)


class SCellSealPassphrase(SCellSeal):
def __new__(cls, passphrase):
"""
Make a new Secure Cell in Seal mode with a passphrase.

:type passphrase: Union(str, bytes)
"""
return object.__new__(SCellSealPassphrase)

def __init__(self, passphrase):
Lagovas marked this conversation as resolved.
Show resolved Hide resolved
"""
Make a new Secure Cell in Seal mode with a passphrase.

:type passphrase: Union(str, bytes)
"""
if not passphrase:
raise SecureCellError('passphrase cannot be empty')
if isinstance(passphrase, type(u'')):
Lagovas marked this conversation as resolved.
Show resolved Hide resolved
passphrase = passphrase.encode('UTF-8')
elif isinstance(passphrase, type(b'')):
pass
else:
raise SecureCellError('passphrase must be either "unicode" or "bytes"')
self.passphrase = passphrase

def encrypt(self, message, context=None):
"""
Encrypt given message with optional context.

:type message: bytes
:type context: bytes
:returns bytes
"""
context_length = len(context) if context else 0
encrypted_message_length = c_int(0)

res = themis.themis_secure_cell_encrypt_seal_with_passphrase(
self.passphrase, len(self.passphrase),
context, context_length,
message, len(message),
None, byref(encrypted_message_length))
if res != THEMIS_CODES.BUFFER_TOO_SMALL:
raise SecureCellError("encryption failed", error_code=res)

encrypted_message = create_string_buffer(encrypted_message_length.value)
res = themis.themis_secure_cell_encrypt_seal_with_passphrase(
self.passphrase, len(self.passphrase),
context, context_length,
message, len(message),
encrypted_message, byref(encrypted_message_length))
if res != THEMIS_CODES.SUCCESS:
raise SecureCellError("encryption failed", error_code=res)

return string_at(encrypted_message, encrypted_message_length.value)

def decrypt(self, message, context=None):
"""
Decrypt given message with optional context.

:type message: bytes
:type context: bytes
:returns bytes
"""
context_length = len(context) if context else 0
decrypted_message_length = c_int(0)

res = themis.themis_secure_cell_decrypt_seal_with_passphrase(
self.passphrase, len(self.passphrase),
context, context_length,
message, len(message),
None, byref(decrypted_message_length))
if res != THEMIS_CODES.BUFFER_TOO_SMALL:
raise SecureCellError("decryption failed", error_code=res)

decrypted_message = create_string_buffer(decrypted_message_length.value)
res = themis.themis_secure_cell_decrypt_seal_with_passphrase(
self.passphrase, len(self.passphrase),
context, context_length,
message, len(message),
decrypted_message, byref(decrypted_message_length))
if res != THEMIS_CODES.SUCCESS:
raise SecureCellError("decryption failed", error_code=res)

return string_at(decrypted_message, decrypted_message_length.value)


class SCellTokenProtect(object):
def __init__(self, key):
if not key:
raise ThemisError(THEMIS_CODES.FAIL,
"Secure Cell (Token Protect) failed creating")
if not isinstance(key, type(b'')):
Lagovas marked this conversation as resolved.
Show resolved Hide resolved
raise SecureCellError('master key must be "bytes"')
self.key = key

def encrypt(self, message, context=None):
Expand Down Expand Up @@ -124,6 +243,8 @@ def __init__(self, key):
if not key:
raise ThemisError(THEMIS_CODES.FAIL,
"Secure Cell (Context Imprint) failed creating")
if not isinstance(key, type(b'')):
raise SecureCellError('master key must be "bytes"')
self.key = key

def encrypt(self, message, context):
Expand Down
Loading