Skip to content

Instantly share code, notes, and snippets.

@mmerickel
Last active May 8, 2020 16:56
Show Gist options
  • Save mmerickel/aee97620e92f4d73bb3e2ea297e7e8b7 to your computer and use it in GitHub Desktop.
Save mmerickel/aee97620e92f4d73bb3e2ea297e7e8b7 to your computer and use it in GitHub Desktop.
from nacl.bindings.crypto_secretstream import (
crypto_secretstream_xchacha20poly1305_ABYTES,
crypto_secretstream_xchacha20poly1305_HEADERBYTES,
crypto_secretstream_xchacha20poly1305_KEYBYTES,
crypto_secretstream_xchacha20poly1305_TAG_MESSAGE,
crypto_secretstream_xchacha20poly1305_TAG_FINAL,
crypto_secretstream_xchacha20poly1305_init_pull,
crypto_secretstream_xchacha20poly1305_init_push,
crypto_secretstream_xchacha20poly1305_pull,
crypto_secretstream_xchacha20poly1305_push,
crypto_secretstream_xchacha20poly1305_state,
)
from nacl.utils import random
import struct
# version, chunk size
_header_struct = struct.Struct('<BQ')
def encrypt_stream(srcfp, destfp, symmetric_key, chunk_size=4096):
if len(symmetric_key) != crypto_secretstream_xchacha20poly1305_KEYBYTES:
raise ValueError('symmetric key is too short')
state = crypto_secretstream_xchacha20poly1305_state()
hdr = crypto_secretstream_xchacha20poly1305_init_push(state, symmetric_key)
destfp.write(hdr)
frame = crypto_secretstream_xchacha20poly1305_push(
state,
_header_struct.pack(1, chunk_size),
None,
crypto_secretstream_xchacha20poly1305_TAG_MESSAGE,
)
destfp.write(frame)
eof = False
while not eof:
msg = srcfp.read(chunk_size)
eof = len(msg) < chunk_size
tag = crypto_secretstream_xchacha20poly1305_TAG_FINAL if eof else 0
frame = crypto_secretstream_xchacha20poly1305_push(state, msg, tag=tag)
destfp.write(frame)
def decrypt_stream(srcfp, destfp, symmetric_key):
hdr = srcfp.read(crypto_secretstream_xchacha20poly1305_HEADERBYTES)
if not hdr:
raise ValueError('corrupted stream, missing header')
state = crypto_secretstream_xchacha20poly1305_state()
crypto_secretstream_xchacha20poly1305_init_pull(state, hdr, symmetric_key)
# read the header version and chunk size
hdr = srcfp.read(
_header_struct.size + crypto_secretstream_xchacha20poly1305_ABYTES,
)
chunk, tag = crypto_secretstream_xchacha20poly1305_pull(state, hdr, None)
version, chunk_size = _header_struct.unpack(chunk)
if version != 1:
raise ValueError('unsupported stream version={0}'.format(version))
frame_size = chunk_size + crypto_secretstream_xchacha20poly1305_ABYTES
while tag != crypto_secretstream_xchacha20poly1305_TAG_FINAL:
frame = srcfp.read(frame_size)
if not frame:
raise ValueError('corrupted stream, missing chunks')
chunk, tag = crypto_secretstream_xchacha20poly1305_pull(state, frame)
destfp.write(chunk)
def main():
import sys
key = random(crypto_secretstream_xchacha20poly1305_KEYBYTES)
with open(sys.argv[1], 'rb') as srcfp, open('foo.enc', 'wb') as dstfp:
encrypt_stream(srcfp, dstfp, key)
with open('foo.enc', 'rb') as srcfp, open('foo.dec', 'wb') as dstfp:
decrypt_stream(srcfp, dstfp, key)
if __name__ == '__main__':
main()
@mk-fg
Copy link

mk-fg commented Jul 5, 2019

As it is, anything larger than about "255 * chunk_size" bytes will be silently (!!!) encrypted into non-decryptable output.

Not sure if I was correct about that size, maybe example just breaks with anything above ~10K:

% rm -f foo && dd if=/dev/urandom of=foo bs=20K count=1 && py3 nacl_streams.py foo
Traceback (most recent call last):
  File "nacl_streams.py", line 74, in <module>
    main()
  File "nacl_streams.py", line 71, in main
    decrypt_stream(srcfp, dstfp, key)
  File "nacl_streams.py", line 62, in decrypt_stream
    chunk, tag = crypto_secretstream_xchacha20poly1305_pull(state, frame)
  File "/home/fraggod/.py3/nacl/bindings/crypto_secretstream.py", line 267, in crypto_secretstream_xchacha20poly1305_pull
    raising=exc.ValueError,
  File "/home/fraggod/.py3/nacl/exceptions.py", line 68, in ensure
    raise raising(*args)
nacl.exceptions.ValueError: Ciphertext is too short

EDIT:
Tracked the issue down to this line - frame = crypto_secretstream_xchacha20poly1305_push(state, msg, tag=tag) - error above happens with zero-length msg there, i.e. if file size divides into blocks without remainder.
To fix that, guess either different eof check should be used (e.g. C examples use eof = feof(fp_s);), or last block to always contain some dummy byte to be deliberately discarded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment