Skip to content

Instantly share code, notes, and snippets.

@stef
Last active August 29, 2015 14:15
Show Gist options
  • Save stef/3b3b6db83cf6be5d6cf4 to your computer and use it in GitHub Desktop.
Save stef/3b3b6db83cf6be5d6cf4 to your computer and use it in GitHub Desktop.
implements simple one-way encryption pipe using rsa and keccak-based spongewrap
#!/usr/bin/env python
#
# implements simple one-way encryption pipe using rsa and keccak-based
# spongewrap
#
# useful at least in the following use-case: you have an untrusted
# host on which plaintext data arrives, which you want to encrypt
# before it is forwarded in a hostile environment to the final
# recipient holding a private key in a safe location. In this one-way
# setting the recipient is never talking to the host doing the
# encryption.
#
# Example: take photos in a hostile situation, encrypt the the photos
# and being unable to recover them until arrival in the save location
# with the the private key. (note, this does not protect against
# forensics!)
#
# crypto: a random 32 byte message key is encrypted with the public
# key of the recipient in oaep padded RSA, then this message key is
# fed into SpongeWrap, which is then used to authenticated encrypt the
# message.
#
# output format:
# 2 bytes - the length of the RSA encrypted message key
# n bytes - the RSA encrypted message key
# m bytes - the encrypted message
# 16 bytes - the "MAC"
#
# depends: `pip install m2crypto spongeshaker SecureString`
#
# create keys using openssl:
# `openssl genrsa -out my.key 4096`
# `openssl rsa -in my.key -pubout >> my.pub`
# `cat my.key my.pub >>my.pem`
# `srm -fll my.key`
#
# deploy my.pub on the encrypting host, secure my.pem in a safe
# location for decryption.
#
# test with:
#
# for i in {0..42} {8170..8210} 1000000; do
# echo -ne "\r$i "
# dd if=/dev/zero bs=$i count=1 2>/dev/null |
# ./ondir.py e my.pub |
# ./ondir.py d my.pem >/dev/null ||
# break
# done
#
# (C) 2015 by Stefan Marsiske, <s@ctrlc.hu>, GPLv3
import M2Crypto as m2c
from spongeshaker.spongewrap import SpongeWrap
from SecureString import clearmem
import sys, struct
TAGLEN = 16
BUFLEN = 8192
def encrypt(to):
# load recipient pk
key = m2c.RSA.load_pub_key(to)
# gen message key
mkey = m2c.Rand.rand_bytes(32)
# encrypt message key
cmkey = key.public_encrypt(mkey, m2c.RSA.pkcs1_oaep_padding)
# output message key
sys.stdout.write(struct.pack("H", len(cmkey)))
sys.stdout.write(cmkey)
# encrypt message
ctx = SpongeWrap(1536)
# with mkey
ctx.add_header(mkey)
# mkey not needed anymore
clearmem(mkey)
# buffered encrypt of stdin to stdout
while 1:
buf = sys.stdin.read(BUFLEN)
if not buf:
break
sys.stdout.write(ctx.encrypt_body(buf))
# calculate tag
tag=ctx.digest(TAGLEN)
# output tag
sys.stdout.write(tag)
def decrypt(to):
# load recipient pk
key = m2c.RSA.load_key(to)
# read msg key
klen = struct.unpack('H', sys.stdin.read(2))[0]
if klen>1024:
print >>sys.stderr, "probably corrupt file"
sys.exit(1)
cmkey = sys.stdin.read(klen)
# decrypt message key
try:
mkey = key.private_decrypt(cmkey, m2c.RSA.pkcs1_oaep_padding)
except:
# twarth timing attacks
mkey = 'couldntdecryptkey'
# decrypt with mkey
ctx = SpongeWrap(1536)
ctx.add_header(mkey)
# mkey not needed anymore
clearmem(mkey)
zero = True # to detect empty files
rest = ''
# buffered reading of stdin, since we need to catch the last
# bytes for the tag, we always retain the last n bytes for
# this purpose in rest
while 1:
buf = sys.stdin.read(BUFLEN)
if zero and len(buf)>TAGLEN: zero=False
tag=buf[-TAGLEN:]
if len(buf)>TAGLEN:
# prepend the retained last bytes to the next decrypt, if
# there's enough more bytes read
sys.stdout.write(ctx.decrypt_body(rest+buf[:-TAGLEN]))
elif len(buf)>0:
if len(tag)==TAGLEN:
# we have exactly the tag read in buf, decrypt the
# rest
sys.stdout.write(ctx.decrypt_body(rest))
else:
# truncate the last bytes, as we have a boundary
# spanning tag
sys.stdout.write(ctx.decrypt_body(rest[:-TAGLEN+len(tag)]))
if len(buf)<BUFLEN:
# this is the last buffer
if len(tag)<TAGLEN:
# we have a tag spanning a buffer boundary, we must
# patch it up
tag = ''.join((rest, buf[:-TAGLEN], tag))[-TAGLEN:]
# verify if tag is valid
if not zero and tag!=ctx.digest(TAGLEN):
print >>sys.stderr, "couldn't decrypt message"
sys.exit(1)
break
rest=tag
if __name__ == '__main__':
if sys.argv[1]=='e':
encrypt(sys.argv[2])
elif sys.argv[1]=='d':
decrypt(sys.argv[2])
else:
print "usage: %s <e|d> <pub|key>"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment