writing code and mails at the same time! woohooo - one-way encryption

stef s@ctrlc.hu
Fri Feb 20 02:30:21 PST 2015


apparently on mailing lists it's common to write mails, even cypherpunks do
that instead of code. allow me to combine the two things.

i thought it trough, and figured, that with openssl i have no AD crypto, with
gpg i have this MDC thingy and the rfc4880 strongselector beacon
strongselector, and with nacl and it's key derivation using ecdh make them all
unsuitable on first sight for the use-case.

re my 7 rules, i think i need to work on the threat model to have 100% score

pls enjoy (also as contributions to the copyleft/permissive licensing thread)
and comment:

#!/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'

    # todo clear private RSA key from memory

    # 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>"


-- 
otr fp: https://www.ctrlc.hu/~stef/otr.txt


More information about the cypherpunks mailing list