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