Skip to content

Commit

Permalink
docs: another link to warnings and better examples on password genera…
Browse files Browse the repository at this point in the history
…tion

This changes documentation in what is now the "Password protection /
encryption" section. It also changes tests to make sure the example in the
documentation works, and makes the tests themselves use better practices, in
case they are seen down the line as examples to follow in the real world.

Specifically, it links to https://crypto.stackexchange.com/a/109269/113464
which serves as a bit of a warning, but also contains some advice. And, it
makes the password in the example longer and random.

It does not implement all the recommendations at
https://crypto.stackexchange.com/a/109269/113464 at least for now to keep this
change small while I consider them - they may come in later changes (e.g.
renaming password to passphrase or generating a default ourselves).

This is done as part of the request/discussion at
#93
  • Loading branch information
michalc committed Jan 4, 2024
1 parent 4776afa commit 7bd5de0
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 11 deletions.
13 changes: 8 additions & 5 deletions docs/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,21 +147,24 @@ The `stat.S_IFDIR` on the file is technically optional, but is probably good pra
It is not required to have a directory member file in order to have files in that directory. So this pattern is most useful to have empty directories in the ZIP.


## Password
## Password protection / encryption

The data of ZIP files can be password protected by passing a password as the `password` parameter to `stream_zip`
The data of ZIP files can be password protected / encrypted by passing a password as the `password` parameter to `stream_zip`.

```python
password_protected_zipped_chunks = stream_zip(member_files(), password='my-password'):
import secrets

password = secrets.token_urlsafe(32)
encrypted_zipped_chunks = stream_zip(member_files(), password=password)
```

Note:
Notes:

1. This encrypts the data with AES-256, adhering to the [WinZip AE-2 specification](https://www.winzip.com/en/support/aes-encryption/).

2. This is seen as more secure than ZipCrypto, the original mechanism of password protecting ZIP files, but fewer clients can open such ZIP files.

3. While a step forward from ZipCrypto, it has flaws that you should be aware of before using it. See ["Attacking and Repairing the WinZip Encryption Scheme" by Tadayoshi Kohno](https://homes.cs.washington.edu/~yoshi/papers/WinZip/winzip.pdf).
3. While a step forward from ZipCrypto, it has flaws that you should be aware of before using it. See ["Attacking and Repairing the WinZip Encryption Scheme" by Tadayoshi Kohno](https://homes.cs.washington.edu/~yoshi/papers/WinZip/winzip.pdf) and [fgrieu's answer to a question about WinZip's AE-1 and AE-2 on Crytography Stack Exchange](https://crypto.stackexchange.com/a/109269/113464).


## Methods
Expand Down
13 changes: 7 additions & 6 deletions test_stream_zip.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from io import BytesIO
import contextlib
import os
import secrets
import stat
import subprocess
import zlib
Expand Down Expand Up @@ -1100,7 +1101,7 @@ def test_unzip_modification_time_extended_timestamps_disabled(method, timezone,
def test_password_unzips_with_stream_unzip(method):
now = datetime.strptime('2021-01-01 21:01:12', '%Y-%m-%d %H:%M:%S')
mode = stat.S_IFREG | 0o600
password = 'my-pass'
password = secrets.token_urlsafe(32)

files = (
('file-1', now, mode, method, (b'a' * 9, b'b' * 9)),
Expand All @@ -1127,7 +1128,7 @@ def test_password_unzips_with_stream_unzip(method):
def test_bad_password_not_unzips_with_stream_unzip(method):
now = datetime.strptime('2021-01-01 21:01:12', '%Y-%m-%d %H:%M:%S')
mode = stat.S_IFREG | 0o600
password = 'my-pass'
password = secrets.token_urlsafe(32)

files = (
('file-1', now, mode, method, (b'a' * 9, b'b' * 9)),
Expand All @@ -1151,7 +1152,7 @@ def test_bad_password_not_unzips_with_stream_unzip(method):
def test_password_unzips_with_7z(method):
now = datetime.strptime('2021-01-01 21:01:12', '%Y-%m-%d %H:%M:%S')
mode = stat.S_IFREG | 0o600
password = 'my-pass'
password = secrets.token_urlsafe(32)

files = (
('file-1', now, mode, method, (b'a' * 9, b'b' * 9)),
Expand Down Expand Up @@ -1187,7 +1188,7 @@ def test_password_unzips_with_7z(method):
def test_password_unzips_with_pyzipper(method):
now = datetime.strptime('2021-01-01 21:01:12', '%Y-%m-%d %H:%M:%S')
mode = stat.S_IFREG | 0o600
password = 'my-pass'
password = secrets.token_urlsafe(32)

files = (
('file-1', now, mode, method, (b'a' * 9, b'b' * 9)),
Expand Down Expand Up @@ -1221,7 +1222,7 @@ def test_password_unzips_with_pyzipper(method):
def test_password_bytes_not_deterministic(method):
now = datetime.strptime('2021-01-01 21:01:12', '%Y-%m-%d %H:%M:%S')
mode = stat.S_IFREG | 0o600
password = 'my-pass'
password = secrets.token_urlsafe(32)

files = (
('file-1', now, mode, method, (b'a' * 9, b'b' * 9)),
Expand Down Expand Up @@ -1251,7 +1252,7 @@ def test_crc_32_not_in_file(method):

now = datetime.strptime('2021-01-01 21:01:12', '%Y-%m-%d %H:%M:%S')
mode = stat.S_IFREG | 0o600
password = 'my-pass'
password = secrets.token_urlsafe(32)

files = (
('file-1', now, mode, method, (b'a' * 9, b'b' * 9)),
Expand Down

0 comments on commit 7bd5de0

Please sign in to comment.