HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Encrypting a binary stream with RSA + AES in counter mode

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
streamrsawithaesmodeencryptingbinarycounter

Problem

I'm writing a little script that encrypts a file using the user's SSH identity.

  • I read the RSA private key from ~/.ssh/id_rsa (default)



  • Use the private key to encrypt a random 32 byte symmetric key (PKCS#1 OAEP)



  • Encrypt a stream of data with AES-256 in counter mode (no extra padding)



  • Write all of this to the output stream in 65535 byte chunks



  • Append an HMAC-SHA256 of the ciphertext



The idea is that data is encrypted on a user's computer before being pushed to the cloud. I'm using the SSH identity so that there's no passwords to worry about. If the user's ssh private key is compromised, the data being encrypted is compromised as well.

If the user wants to use a password, they'll just encrypt their private key.

Here's the code:

```
from Crypto import Random
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES
from Crypto.Util import Counter
from Crypto.Hash import HMAC, SHA256

def encrypt_stream_v1(identity, stdin, stdout):
rng = Random.new()
iv = rng.read(8) # counter mode prefix
key = rng.read(32) # random AES-256 key
# AES-256 in counter mode
aes = AES.new(key, mode=AES.MODE_CTR, counter=Counter.new(64, iv))
mac = HMAC.new(key, digestmod=SHA256) # HMAC-SHA256 for ciphertext
with open(identity) as f:
# the AES-256 key is encrypte with the users rsa private key
key = PKCS1_OAEP.new(RSA.importKey(f.read())).encrypt(key)
# the output stream begins with the 8 byte iv, the length of the
# encrypted AES-256 key in two bytes and the encrypted key itself
stdout.write(iv + len(key).to_bytes(2, "big") + key)
while True:
# encrypt in chunks of 65535 bytes (a two byte integer)
chunk = aes.encrypt(stdin.read(0xFFFF))
if chunk:
# write the length of the encrypted chunk and the chunk itself
stdout.write(len(chunk).to_bytes(2, "big") + chunk)
# update the hmac with the ciphertext
m

Solution

Is there anything wrong with how I'm using RSA/OAEP or AES-256-CTR?

I wish I could answer that but I don't know enough crypto.
I hope somebody else can address this point for you.


Is there anything I can optimize with the file format?

Nothing pops to mind. Same note as previous point.


Any mistakes I'm not seeing?

Not really "mistakes", but I see a few minor things that could be improved.

Magic numbers

The chuck size 0xFFFF appears twice.
It would be better to put that in a variable,
perhaps CHUNK_SIZE at the package level.
That way if you ever change the value,
you only have to change in one place.

Duplicated logic

You do len(something).to_bytes(2, "big") twice.
I suppose both uses need to have consistent logic,
just like with magic numbers,
the best way to ensure consistency is to define things in one place,
rather than duplicating at two or more,
which risks inconsistent parallel edits in the future.

The approach

Once again, I'm no expert on this, but anyway...


I'm using the SSH identity so that there's no passwords to worry about.

Makes perfect sense to me.
That is, the default private key should be protected by a passphrase,
normally managed by a key-chain manager,
and you can piggy-back on that.


If the user's ssh private key is compromised, the data being encrypted is compromised as well.

That seems acceptable, because it's inevitable.

Context

StackExchange Code Review Q#103926, answer score: 5

Revisions (0)

No revisions yet.