Open sirex opened 9 years ago
The task is to read more about crypthogrphy.io.
RSA can only encrypt data blocks that are shorter than the key length.
Usually, RSA is only used to transfer a symmetric key (at the start of the stream for example) and then the bulk data is encrypted with that key.
Asymmetric encryption isn't efficient enough to transfer a lot of data. http://security.stackexchange.com/a/33445
Padding is a way to take data that may or may not be a multiple of the block size for a cipher and extend it out so that it is. This is required for many block cipher modes as they require the data to be encrypted to be an exact multiple of the block size.
https://cryptography.io/en/latest/hazmat/primitives/padding/
Very well explained what is RSA: http://unix.stackexchange.com/a/12265
It looks like we have to use RSA just to encrypt asymmetric key, and the whole message has to be encrypted using that asymmetric key. See https://cryptography.io/en/latest/fernet/
I just read more about PKI and yes, asymmetric algorithms usually are used only for symmetric key exchange. Then, rest of the encryption is done by symmetric algorithms using that exchanged key.
So your task will bet to encrypt message multiple time with list of given symmetric keys, using Fernet protocol. And the key exchange protocol is a different task.
After thinking more about this, I figured out, that you still need public keys. So the API stays the same:
envelope = create_envelope(nodes, content)
Here nodes
is a list of tuples, each tuple has mix-net node id (an int
) and public key instance.
When creating multi-layered envelope, for each layer you need a JSON list with two elements:
int
). Node Id of first layer should be None
.The envelope could look something like this (pseudocode):
envelope = json([
json([
node[1].public_key.encrypt(node[0].fernet_key),
node[1].fernet_key.encrypt(json([
json([
node[0].public_key.encrypt(node[0].fernet_key),
node[0].fernet_key.encrypt(message),
]),
node[0].id,
]))
]),
node[1].id,
])
Here node[0]
would be CEC.
For decoding envelope this API can be used:
content, next_node_id = decrypt_envelope(primary_key, envelope)
Here is what it should do:
json.loads
.primary_key
to get Fernet key.content
(inner envelope) and next_node_id
(address).For the serialization, I'm not sure if JSON is a good choice, since we will be dealing with binary data. Maybe we should use MessagePack instead of JSON?
Today decided to look into crypto things, and assembled set of functions, that should cover most of crypto needs. It would be good, if you wood put into some module and use these functions for your task.
import os
import json
import base64
import hashlib
import binascii
from cryptography.fernet import Fernet
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import padding as asymmetric_padding
def generate(size: int=4096):
return rsa.generate_private_key(
public_exponent=65537,
key_size=size,
backend=default_backend(),
)
def dump_base64(data: bytes) -> str:
return base64.encodebytes(data).replace(b'\n', b'').decode('ascii')
def load_base64(data: str) -> bytes:
return base64.decodebytes(data.encode('ascii'))
def dump_private_key(private_key, password: str=None) -> str:
if password is None:
encryption_algorithm = serialization.NoEncryption()
else:
encryption_algorithm = serialization.BestAvailableEncryption(password.encode('utf-8'))
private_key_bytes = private_key.private_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=encryption_algorithm,
)
return dump_base64(private_key_bytes)
def load_private_key(private_key_data: str, password: str=None):
return serialization.load_der_private_key(
load_base64(private_key_data),
password=password.encode('utf-8'),
backend=default_backend()
)
def dump_public_key(public_key) -> str:
public_key_bytes = public_key.public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
return dump_base64(public_key_bytes)
def load_public_key(public_key_data: str):
return serialization.load_der_public_key(
load_base64(public_key_data),
backend=default_backend(),
)
def public_key_fingerprint(public_key) -> str:
public_key_bytes = public_key.public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
return hashlib.sha1(public_key_bytes).hexdigest()
def encrypt(public_key, message: str) -> (str, str):
key = Fernet.generate_key()
fernet = Fernet(key)
cipher = fernet.encrypt(message.encode('utf-8'))
key = public_key.encrypt(key, asymmetric_padding.OAEP(
mgf=asymmetric_padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
label=None,
))
return dump_base64(key), cipher.decode('ascii')
def decrypt(private_key, key: str, cipher: str) -> str:
key = private_key.decrypt(load_base64(key), asymmetric_padding.OAEP(
mgf=asymmetric_padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
label=None,
))
fernet = Fernet(key)
message = fernet.decrypt(cipher.encode('ascii'))
return message.decode('utf-8')
def sign(private_key, message: str) -> str:
signer = private_key.signer(
asymmetric_padding.PSS(
mgf=asymmetric_padding.MGF1(hashes.SHA256()),
salt_length=asymmetric_padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
signer.update(message.encode('utf-8'))
return dump_base64(signer.finalize())
def verify(public_key, message: str, signature: str) -> bool:
verifier = public_key.verifier(
load_base64(signature),
asymmetric_padding.PSS(
mgf=asymmetric_padding.MGF1(hashes.SHA256()),
salt_length=asymmetric_padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
verifier.update(message.encode('utf-8'))
try:
verifier.verify()
except InvalidSignature:
return False
else:
return True
def get_random_bytes(n: int) -> bytes:
return os.urandom(n)
def apply_hash_function(message: str, salt: bytes) -> str:
hash_bytes = hashlib.pbkdf2_hmac('sha1', message.encode('utf-8'), salt, 1000000)
return binascii.hexlify(hash_bytes).decode('utf-8')
def password_to_fernet_key(password: str, salt: bytes) -> bytes:
key = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 1000000)
return base64.urlsafe_b64encode(key)
def fernet_encrypt(password: str, salt: bytes, data: str) -> bytes:
fernet = Fernet(password_to_fernet_key(password, salt))
return base64.urlsafe_b64decode(fernet.encrypt(data.encode('utf-8')).decode('ascii'))
def fernet_decrypt(password: str, salt: bytes, cipher: str) -> str:
fernet = Fernet(password_to_fernet_key(password, salt))
return fernet.decrypt(base64.urlsafe_b64encode(cipher)).decode('utf-8')
def dump_sensitive_data(password: str, data) -> bytes:
salt = get_random_bytes(32)
cipher = fernet_encrypt(password, salt, json.dumps(data))
return salt + cipher
def load_sensitive_data(password: str, cipher: bytes):
return json.loads(fernet_decrypt(password, cipher[:32], cipher[32:]))
For crypto part use https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/
Here is how this function could look:
nodes
should be list of tuples tuple has two elements: node's IP address and public key.content
should be any JSON serializable object.Returned
envelope
can be deserialized with primary keys of nodes in reverse order.