How to: Crack Excel sheet protection

Last update: Sept 2025

Motivation

One client asked me how to make adjustments to a protected Excel worksheet. He forgot the password which he used longer time ago.

This has been tested with latest MS Excel version 16.101.

Part I: Creating a test file

I have opened Excel, entered some random text, added a specific password and saved it under test.xlsx.

Part II: Preperation

To make the content readable, we have to rename test.xlsx to test.zip and unpack it. You will get some folder tree like this:

.
├── _rels
├── [Content_Types].xml
├── docProps
│   ├── app.xml
│   └── core.xml
└── xl
    ├── _rels
    │   └── workbook.xml.rels
    ├── sharedStrings.xml
    ├── styles.xml
    ├── theme
    │   └── theme1.xml
    ├── workbook.xml
    └── worksheets
        └── sheet1.xml

We need to extract the hash in order to pass it to the hash cracker. If you open xl/worksheets/sheet1.xml you will get something like:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet
	xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
	xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac xr xr2 xr3"
	xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"
	xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision"
	xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2"
	xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" xr:uid="{C6DBE5C3-7054-FC42-83A8-000ADD994773}">
	<dimension ref="A1"/>
	<sheetViews>
		<sheetView tabSelected="1" workbookViewId="0">
			<selection activeCell="A2" sqref="A2"/>
		</sheetView>
	</sheetViews>
	<sheetFormatPr baseColWidth="10" defaultRowHeight="16" x14ac:dyDescent="0.2"/>
	<sheetData>
		<row r="1" spans="1:1" x14ac:dyDescent="0.2">
			<c r="A1" t="s">
				<v>0</v>
			</c>
		</row>
	</sheetData>
	<sheetProtection algorithmName="SHA-512" hashValue="pm5ygSmGa3QJRa6AFxVauHAeJerAcDPzr/Vr0fcWoSAjH3kPYaQfMsCjSdol9TG01IE+Ub7LfM2+7GCvjOnoWQ==" saltValue="3qH6P5BeM9UoPFljT47szw==" spinCount="100000" sheet="1" objects="1" scenarios="1"/>
	<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>
</worksheet>

We are noting the following values:

  • hashValue=”pm5ygSmGa3QJRa6AFxVauHAeJerAcDPzr/Vr0fcWoSAjH3kPYaQfMsCjSdol9TG01IE+Ub7LfM2+7GCvjOnoWQ==”
  • saltValue=”3qH6P5BeM9UoPFljT47szw==”

With these information we need to build a string which can be unterstood by hashcat. The template is $office$2016$0$100000$[saltValue]$[hashValue]. So in our case the string to crack is:

$office$2016$0$100000$3qH6P5BeM9UoPFljT47szw==$pm5ygSmGa3QJRa6AFxVauHAeJerAcDPzr/Vr0fcWoSAjH3kPYaQfMsCjSdol9TG01IE+Ub7LfM2+7GCvjOnoWQ==

We save it under hash.txt.

Part III: Cracking with hashcat

Option 1: pure bruteforce

If you have absolutely no idea about the password and plenty of time and/or resources you can simply try to brute force it.

The command is the following:

hashcat --attack-mode 3 -m 25300 hash.txt "?l?l?l?l"

--attack-mode 3 => 3 means bruteforce
-m 25300        => means MS Office 2016 - SheetProtection
"?l?l?l?l"      => means we assume the password to fit in a mask of 4 lowercase characters

You will get an output like this:

hashcat (v7.1.2) starting

METAL API (Metal 370.63.1)
==========================
* Device #01: Apple M3, skipped

OpenCL API (OpenCL 1.2 (Aug  2 2025 21:16:03)) - Platform #1 [Apple]
====================================================================
* Device #02: Apple M3, GPU, 9093/18186 MB (1704 MB allocatable), 10MCU

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
Minimum salt length supported by kernel: 0
Maximum salt length supported by kernel: 256

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates

Optimizers applied:
* Zero-Byte
* Single-Hash
* Single-Salt
* Brute-Force
* Slow-Hash-SIMD-LOOP
* Uses-64-Bit

Watchdog: Temperature abort trigger set to 100c

Host memory allocated for this attack: 972 MB (9144 MB free)

The wordlist or mask that you are using is too small.
This means that hashcat cannot use the full parallel power of your device(s).
Hashcat is expecting at least 69120 base words but only got 25.4% of that.
Unless you supply more work, your cracking speed will drop.
For tips on supplying more work, see: https://hashcat.net/faq/morework

Approaching final keyspace - workload adjusted.           

[s]tatus [p]ause [b]ypass [c]heckpoint [f]inish [q]uit =>

With hitting the S key you will see some progress information:

[s]tatus [p]ause [b]ypass [c]heckpoint [f]inish [q]uit => s

Session..........: hashcat
Status...........: Running
Hash.Mode........: 25300 (MS Office 2016 - SheetProtection)
Hash.Target......: $office$2016$0$100000$3qH6P5BeM9UoPFljT47szw==$pm5y...noWQ==
Time.Started.....: Thu Sep 18 17:46:38 2025 (18 secs)
Time.Estimated...: Thu Sep 18 17:52:25 2025 (5 mins, 29 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Mask.......: ?l?l?l?l [4]
Guess.Queue......: 1/1 (100.00%)
Speed.#02........:     1332 H/s (3.15ms) @ Accel:27 Loops:1000 Thr:256 Vec:1
Recovered........: 0/1 (0.00%) Digests (total), 0/1 (0.00%) Digests (new)
Progress.........: 17576/456976 (3.85%)
Rejected.........: 0/17576 (0.00%)
Restore.Point....: 0/17576 (0.00%)
Restore.Sub.#02..: Salt:0 Amplifier:1-2 Iteration:43000-44000
Candidate.Engine.: Device Generator
Candidates.#02...: mari -> mqxv
Hardware.Mon.#02.: Util: 99% Pwr:4544mW

Once the password has been found you will get an output like this:

$office$2016$0$100000$3qH6P5BeM9UoPFljT47szw==$pm5ygSmGa3QJRa6AFxVauHAeJerAcDPzr/Vr0fcWoSAjH3kPYaQfMsCjSdol9TG01IE+Ub7LfM2+7GCvjOnoWQ==:test
                                                          
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 25300 (MS Office 2016 - SheetProtection)
Hash.Target......: $office$2016$0$100000$3qH6P5BeM9UoPFljT47szw==$pm5y...noWQ==
Time.Started.....: Thu Sep 18 17:46:38 2025 (2 mins, 20 secs)
Time.Estimated...: Thu Sep 18 17:48:58 2025 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Mask.......: ?l?l?l?l [4]
Guess.Queue......: 1/1 (100.00%)
Speed.#02........:     1252 H/s (4.04ms) @ Accel:27 Loops:1000 Thr:256 Vec:1
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 175760/456976 (38.46%)
Rejected.........: 0/175760 (0.00%)
Restore.Point....: 0/17576 (0.00%)
Restore.Sub.#02..: Salt:0 Amplifier:9-10 Iteration:99000-100000
Candidate.Engine.: Device Generator
Candidates.#02...: tari -> tqxv
Hardware.Mon.#02.: Util: 99% Pwr:7185mW

Started: Thu Sep 18 17:46:24 2025
Stopped: Thu Sep 18 17:48:59 2025

You see in the very first line that my chosen password has been “test”.

Option 2: partly bruteforce

Imagine you know that your password is “BananaXX”, but you are not sure which two digits follow. In this case we can use a command to start with “Banana” followed by a mask with two digits:

hashcat --attack-mode 3 -m 25300 hash.txt "Banana?d?d?"

Some hashcat masking options for your reference:

# ?l # Lowercase a-z
# ?u # Uppercase A-Z
# ?d # Decimals
# ?h # Hex using lowercase chars
# ?H # Hex using uppercase chars
# ?s # Special chars
# ?a # All (l,u,d,s)
# ?b # Binary

For the sake of this tutorial we can also specify only certain characters to check. Here we assume there are only a, b, c, d and e to check:

hashcat --attack-mode 3 -m 25300 -1 abcde "?1?1?1?1"

Option 3: Dictionary attack / using a wordlist

We can adjust the command from option 1 to make use of a wordlist.

hashcat --attack-mode 0 -m 25300 hash.txt wordlist.txt --show

--attack-mode 0 => 3 means wordlist
-m 25300        => means MS Office 2016 - SheetProtection
--show          => usually result is being saved in a pot file, but we want to show it in the terminal

Option 4: Creating a custom wordlist and using it

Imagine you can remember individual parts of a password exactly. Or you know which parts the password consists of, but you no longer know the exact order. Then we need a self-built dictionary to be able to try out all the possibilities. In this case all parts I remember are for example the following fragments:

  • te
  • st
  • ! (exclamation mark)

We will use the script now to generate an own dictionary will all possibilities of the fragments above I remember.

If you have a good wordlist already you can skip this part. Here we are doing it from scratch while using Python.

# install package for python virtual environments if needed
sudo apt install python3-venv

# create the virtual environment for python
python3 -m venv venv

# enable it
source venv/bin/activate
cd venv

Create a file named generate-wordlist.py with the following content:

import itertools

# Input words (you can modify this of course)
words = ["te", "st", "!"]

# Store permutations in a file
with open("generated-wordlist.txt", "w") as file:
    # Write individual words (length 1)
    for word in words:
        file.write(word + "\n")
    
    # Generate and write permutations of varying lengths
    for length in range(2, len(words) + 1):  # From 2 to the number of words
        permutations = itertools.permutations(words, length)
        for perm in permutations:
            # Join words without spaces
            file.write("".join(perm) + "\n")

# Remove duplicates
with open("generated-wordlist.txt", "r") as file:
    unique_lines = set(file.readlines())

# Write unique lines back to the file
with open("generated-wordlist.txt", "w") as file:
    for line in unique_lines:
        file.write(line)

print("Words and unique permutations have been saved to generated-wordlist.txt")

The file wordlist.txt is now our self-made dictionary with all 15 possibilities:

stte
test!
!st
test
!te
st!
te!
stte!
st
!test
te
!stte
st!te
te!st
!

Now you can use the generated-wordlist.txt file in the hashcat command at Option 3 (above) while replacing wordlist.txt. Keep in mind that this solution only comes in handy, if there are not too many fragments you remember. Otherwise the list will get huge and should be splitted (see troubeshooting below).

Part IV: general notes / troubleshooting

Cache of cracked passwords

Once you cracked a hash successfully, the result will likely get stored in a potfile. This means if you re-run hashcat with the same options, it will not run a crack again. Instead it will show you the result, which is stored in the potfile. If you want to get rid of the previous result, you will have to empty or delete the file called “hashcat.potfile”.

  • on linux the default path is ~/.hashcat/hashcat.potfile
  • on macos with homebrew the default path is: /opt/homebrew/Cellar/hashcat/7.1.2/share/hashcat/

If you want to disable the storage within the potfile, you can use switch “–potfile-disable”

Status Exhausted

If you see this status, hashcat has been not able to crack the given hash. This happens when hashcat runs out of potential passwords.

Huge dictionary files

Dictionary files can get huge quickly, if you build them on your own. In case you need to split them into usable parts (here: 10 parts) you can use the Linux command:

split -n 10 wordlist.txt part_

Using prebuilt wordlists from Kali Linux

If you are too lazy to generate your own you can use those ones shipped with Kali already:

In case you need this package urgently: I prepared this download for you, where you can find all wordlists.

Part V: Useful information