Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
nolze committed Jan 16, 2024
1 parent 03ea21f commit 9a092e7
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 180 deletions.
36 changes: 35 additions & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,38 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

----------------------------------------------------------------------

This software contains derivative works from https://github.com/herumi/msoffice
which is licensed under the BSD 3-Clause License.

https://github.com/herumi/msoffice/blob/c3cdb1ea0a5285a2a1718fee2dc893fd884bdad0/COPYRIGHT

Copyright (c) 2007-2015 Cybozu Labs, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
Neither the name of the Cybozu Labs, Inc. nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
69 changes: 43 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ pip install msoffcrypto-tool

### As CLI tool (with password)

#### Decryption

Specify the password with `-p` flag:

```
msoffcrypto-tool encrypted.docx decrypted.docx -p Passw0rd
```
Expand All @@ -41,13 +45,20 @@ $ msoffcrypto-tool encrypted.docx decrypted.docx -p
Password:
```

Test if the file is encrypted or not (exit code 0 or 1 is returned):
To check if the file is encrypted or not, use `-t` flag:

```
msoffcrypto-tool document.doc --test -v
```

Encrypt an OOXML file:
It returns `1` if the file is encrypted, `0` if not.

#### Encryption (OOXML only, experimental)

> [!IMPORTANT]
> Encryption feature is experimental. Please use it at your own risk.
To password-protect a document, use `-e` flag along with `-p` flag:

```
msoffcrypto-tool -e -p Passw0rd plain.docx encrypted.docx
Expand All @@ -57,7 +68,9 @@ msoffcrypto-tool -e -p Passw0rd plain.docx encrypted.docx

Password and more key types are supported with library functions.

Basic decryption usage:
#### Decryption

Basic usage:

```python
import msoffcrypto
Expand All @@ -73,7 +86,7 @@ with open("decrypted.docx", "wb") as f:
encrypted.close()
```

Basic decryption usage (in-memory):
In-memory:

```python
import msoffcrypto
Expand All @@ -91,7 +104,31 @@ df = pd.read_excel(decrypted)
print(df)
```

Basic encryption usage (only OOXML is supported):
Advanced usage:

```python
# Verify password before decryption (default: False)
# The ECMA-376 Agile/Standard crypto system allows one to know whether the supplied password is correct before actually decrypting the file
# Currently, the verify_password option is only meaningful for ECMA-376 Agile/Standard Encryption
file.load_key(password="Passw0rd", verify_password=True)

# Use private key
file.load_key(private_key=open("priv.pem", "rb"))

# Use intermediate key (secretKey)
file.load_key(secret_key=binascii.unhexlify("AE8C36E68B4BB9EA46E5544A5FDB6693875B2FDE1507CBC65C8BCF99E25C2562"))

# Check the HMAC of the data payload before decryption (default: False)
# Currently, the verify_integrity option is only meaningful for ECMA-376 Agile Encryption
file.decrypt(open("decrypted.docx", "wb"), verify_integrity=True)
```

#### Encryption (OOXML only, experimental)

> [!IMPORTANT]
> Encryption feature is experimental. Please use it at your own risk.
Basic usage:

```python
from msoffcrypto.format.ooxml import OOXMLFile
Expand All @@ -105,7 +142,7 @@ with open("encrypted.docx", "wb") as f:
plain.close()
```

Basic encryption usage (in-memory, only OOXML is supported):
In-memory:

```python
from msoffcrypto.format.ooxml import OOXMLFile
Expand All @@ -120,25 +157,6 @@ with open("plain.xlsx", "rb") as f:
# Do stuff with encrypted buffer; it contains an OLE container with an encrypted stream
```

Advanced usage:

```python
# Verify password before decryption (default: False)
# The ECMA-376 Agile/Standard crypto system allows one to know whether the supplied password is correct before actually decrypting the file
# Currently, the verify_password option is only meaningful for ECMA-376 Agile/Standard Encryption
file.load_key(password="Passw0rd", verify_password=True)

# Use private key
file.load_key(private_key=open("priv.pem", "rb"))

# Use intermediate key (secretKey)
file.load_key(secret_key=binascii.unhexlify("AE8C36E68B4BB9EA46E5544A5FDB6693875B2FDE1507CBC65C8BCF99E25C2562"))

# Check the HMAC of the data payload before decryption (default: False)
# Currently, the verify_integrity option is only meaningful for ECMA-376 Agile Encryption
file.decrypt(open("decrypted.docx", "wb"), verify_integrity=True)
```

## Supported encryption methods

### MS-OFFCRYPTO specs
Expand Down Expand Up @@ -235,7 +253,6 @@ poetry run coverage run -m pytest -v
* <https://github.com/opendocument-app/OpenDocument.core/blob/233663b039/src/internal/ooxml/ooxml_crypto.h>
* <https://github.com/jaydadhania08/PHPDecryptXLSXWithPassword>
* <https://github.com/epicentre-msf/rpxl>
* <https://github.com/herumi/msoffice>

### In publications

Expand Down
22 changes: 11 additions & 11 deletions msoffcrypto/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ def is_encrypted(file):
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-p", "--password", nargs="?", const="", dest="password", help="password text")
group.add_argument("-t", "--test", dest="test_encrypted", action="store_true", help="test if the file is encrypted")
parser.add_argument("-e", dest="encrypt", action="store_true", help="encryption mode (default is false)")
parser.add_argument("-v", dest="verbose", action="store_true", help="print verbose information")
parser.add_argument("-e", dest="encrypt", action="store_true", help="encryption mode (default is to decrypt)")
parser.add_argument("infile", nargs="?", type=argparse.FileType("rb"), help="input file")
parser.add_argument("outfile", nargs="?", type=argparse.FileType("wb"), help="output file (if blank, stdout is used)")

Expand All @@ -82,16 +82,6 @@ def main():
else:
password = getpass.getpass()

if args.encrypt:
# The only format we support for encryption
file = OOXMLFile(args.infile)
else:
if not olefile.isOleFile(args.infile):
raise exceptions.FileFormatError("Not OLE file")

file = OfficeFile(args.infile)
file.load_key(password=password)

if args.outfile is None:
ifWIN32SetBinary(sys.stdout)
if hasattr(sys.stdout, "buffer"): # For Python 2
Expand All @@ -100,9 +90,19 @@ def main():
args.outfile = sys.stdout

if args.encrypt:
# OOXML is the only format we support for encryption
file = OOXMLFile(args.infile)

file.encrypt(password, args.outfile)
else:
if not olefile.isOleFile(args.infile):
raise exceptions.FileFormatError("Not OLE file")

file = OfficeFile(args.infile)
file.load_key(password=password)

file.decrypt(args.outfile)


if __name__ == "__main__":
main()
22 changes: 12 additions & 10 deletions msoffcrypto/exceptions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
class FileFormatError(Exception):
"""Raised when the format of given file is unsupported or unrecognized.
"""
"""Raised when the format of given file is unsupported or unrecognized."""

pass


class ParseError(Exception):
"""Raised when the file cannot be parsed correctly.
"""
"""Raised when the file cannot be parsed correctly."""

pass


class DecryptionError(Exception):
"""Raised when the file cannot be decrypted.
"""
"""Raised when the file cannot be decrypted."""

pass


class EncryptionError(Exception):
"""Raised when the file cannot be encrypted.
"""
"""Raised when the file cannot be encrypted."""

pass


class InvalidKeyError(DecryptionError):
"""Raised when the given password or key is incorrect or cannot be verified.
"""
"""Raised when the given password or key is incorrect or cannot be verified."""

pass
Loading

0 comments on commit 9a092e7

Please sign in to comment.