The Blowfish Algorithm
Bruce Schneier designed Blowfish as a free, fast alternative to DES, with a distinctive twist: its S-boxes are rebuilt from the encryption key through a key schedule that starts from the digits of pi. Learn how it works, with every value shown, and why it's still found in legacy systems today.
Pashalis Laoutaris Updated 21 min read
Try it
Interactive Blowfish Visualizer
🔐 Blowfish Encryption
The Blowfish Algorithm
Introduction
Blowfish was designed by cryptographer Bruce Schneier in 1993 as a fast, free, unpatented alternative to DES at a time when most strong encryption algorithms were either export-controlled or commercially licensed. Blowfish was released into the public domain from day one. That openness, combined with genuinely solid security, made it enormously popular in software throughout the 1990s and 2000s, appearing in everything from OpenSSH to password-hashing schemes (via its derivative, bcrypt).
This guide covers the real cipher. The visualizer and the Python code below build the same tables from the digits of pi, run the same 521-encryption key schedule, and produce the ciphertext that the published test vectors require. Every value in the worked example comes from that code.
Table of Contents
- Design Goals
- How Blowfish Works
- The Key Schedule
- The Constants from Pi
- A Worked Example
- Python Implementation
- Limitations
- Security Status
- Blowfish’s Legacy: bcrypt and Twofish
- FAQ
- References
Design Goals
Schneier set out explicit design goals for Blowfish. It should be fast, encrypting at least as quickly as DES on 32-bit processors. It should be compact, running in under 5KB of memory. It should be simple, using only basic operations that are easy to implement correctly. And it should be flexible, supporting variable key lengths from 32 up to 448 bits, so users could choose their own security/performance trade-off.
How Blowfish Works
Like DES, Blowfish is a Feistel cipher: it splits each 64-bit block into two 32-bit halves and repeatedly applies a round function to one half, XORing the result into the other, then swapping. Blowfish runs 16 rounds, each combining the left half with a round subkey from an 18-entry array called the P-array, then passing it through Blowfish’s distinctive F-function:
- Split the 32-bit input into four 8-bit bytes: a, b, c, d.
- Look each byte up in one of four separate 256-entry S-boxes: S1[a], S2[b], S3[c], S4[d].
- Combine them:
F = ((S1[a] + S2[b]) mod 2³²) XOR S3[c], thenF = (F + S4[d]) mod 2³².
This mixture of table lookups, modular addition, and XOR, rather than pure bit permutation like DES, is part of what makes Blowfish fast in software: additions and XORs map directly onto simple CPU instructions.
The whole block encryption is short enough to write in a few lines:
L, R = the two 32-bit halves of the block
for i in 0 to 15:
L = L xor P[i]
R = R xor F(L)
swap L and R
swap L and R (undo the last swap)
R = R xor P[16]
L = L xor P[17]
Decryption is the same network with the subkeys in reverse order. The 16 rounds use P[17] down to P[2], and the final step XORs P[1] into the right half and P[0] into the left half. Because the cipher is a Feistel network, F never needs to be inverted. That is why the encryption and decryption code can share one function.
The state is small. The P-array has 18 words and the four S-boxes have 256 words each, so there are 1,042 words of 32 bits, which is 4,168 bytes. Schneier’s description of the cipher numbers the P-array P1 to P18 and the S-box words S1,0 to S4,255. This guide and its code count from 0, so P[0] here is P1 there, and S1[0] here is S1,0. Keep that in mind when you compare with older sources. The other half of the design is how that state gets its values, and the next two sections cover it.
Interactive Visualizer
The visualizer above runs real Blowfish. Enter a key of 4 to 56 bytes as hex and one 8-byte block as 16 hex characters. Then click Encrypt or Decrypt. The page builds the real P-array and S-boxes from your key, shows the 18 subkeys and the first four words of each S-box, and then steps through the 16 rounds.
Each row of the log shows the subkey, the value L xor P, its four bytes, the four S-box outputs, the result of F, and the two halves after the round. The last row shows the output step that XORs in the final two subkeys. When the run ends, the page runs the opposite direction on the result and tells you whether it got your input back. The default key and block are one of the published test vectors, so the first run ends with 0ACEAB0FC6A0A28D.
The Key Schedule
Blowfish’s most distinctive design element is that its S-boxes aren’t fixed public constants like DES’s or AES’s. They’re derived from the encryption key itself, through an unusually elaborate key schedule:
- The P-array and the four S-boxes are initialized with fixed values derived from the digits of π (pi).
- The key is XORed cyclically across the 18 P-array entries. The first four key bytes go into
P[0], the next four intoP[1], and so on, wrapping around to the start of the key as often as needed. - Blowfish then encrypts an all-zero 64-bit block with the current state and uses the output to replace
P[0]andP[1]. - It encrypts that output again, which is the previous ciphertext and not a new block of zeros, and the new result replaces
P[2]andP[3]. This continues throughP[16]andP[17], which takes 9 encryptions. - The same chain goes on through the S-boxes, two words at a time, from
S1[0]toS4[255]. That takes 512 more encryptions, so the schedule runs 521 block encryptions in all.
Every encryption in this chain uses the state as it stands at that moment, including the values the earlier steps already replaced. So a change in a single key bit spreads through the whole state. Every unique key produces an entirely unique set of S-boxes, adding an extra layer of resistance to attacks that rely on known S-box structure.
The cost is key setup time. A key schedule costs as much as encrypting 4,168 bytes (521 blocks of 8 bytes), so Blowfish is a poor fit for applications that need to rapidly change keys. (This exact property was later deliberately repurposed as a feature in the bcrypt password-hashing algorithm, discussed below.)
Because the key is read cyclically, different keys can give the same cipher. The 18 words of the P-array read 72 bytes of key stream. A key and the same key written twice read the same endless stream, so they produce an identical state. Keys of other lengths match too when their first 72 bytes of stream agree, such as a 4-byte key and a 12-byte key that repeats it three times.
Schneier’s paper specifies keys of 32 to 448 bits, which is 4 to 56 bytes. Eric Young’s test vectors also use keys as short as 8 bits, and the Python code below accepts them. The visualizer follows the paper and takes 4 to 56 bytes.
The Constants from Pi
The starting values of the P-array and the S-boxes are the hexadecimal digits of the fractional part of pi. The first four P words are 243f6a88 85a308d3 13198a2e 03707344. The first S-box begins with d1310ba6 98dfb5ac, and the last word of the fourth S-box is 3ac372e6. The 1,042 words are consecutive: 18 for the P-array first, then 256 for each S-box in order.
Pi makes a good source for a simple reason. Anyone can check the digits, so nobody can suspect that the designer hid a weakness in them. The same idea appears in many designs.
The Python code does not copy the table from anywhere. The function pi_words computes the digits with big integers and Machin’s formula, pi = 16 arctan(1/5) - 4 arctan(1/239). It takes a small fraction of a second. The visualizer stores the same 1,042 words as one string of hex digits, because a page should not run a long computation just to show a table.
A Worked Example
This encrypts the block 0123456789ABCDEF with the key FEDCBA9876543210. It is the eighth row of Eric Young’s ECB test vectors, and the published ciphertext is 0ACEAB0FC6A0A28D. The published list gives only the key, the plaintext, and the ciphertext. Every value in between comes from the code in this guide, which reproduces the published ciphertext.
The key in the P-array. The key has 8 bytes, so it forms two words, fedcba98 and 76543210, and they alternate across all 18 P words. XORing them into the first two P words gives 243f6a88 xor fedcba98 = dae3d010 and 85a308d3 xor 76543210 = f3f73ac3.
The key schedule. After the 521 encryptions, the P-array holds these 18 subkeys:
P[0] = 3bc49294 P[1] = 0bad118b P[2] = 56b9b852 P[3] = 59ffc1a1
P[4] = 22c59cb3 P[5] = c5d308f3 P[6] = 9aed675a P[7] = fbfd6e02
P[8] = 194edc7d P[9] = 532d7b2b P[10] = 16d8d0b6 P[11] = b488d33e
P[12] = 3c498d7d P[13] = 363dc4c9 P[14] = 92c4e9bf P[15] = 2319f252
P[16] = a1368b83 P[17] = 8862b9a5
The first S-box begins with 6a84b1e0 5589ab12 8ae143e6 16152532, where the digits of pi gave d1310ba6 98dfb5ac 2ffd72db d01adfb7.
Round 1. The halves are L0 = 01234567 and R0 = 89abcdef. First the left half is XORed with the subkey: 01234567 xor 3bc49294 = 3ae7d7f3. Its four bytes are 3a, e7, d7, and f3, and each one indexes an S-box:
S1[3a] = 4cbaa031
S2[e7] = beb3a444
S3[d7] = 42b738df
S4[f3] = 1b38b413
Then 4cbaa031 + beb3a444 = 0b6e4475 (mod 2³²), and 0b6e4475 xor 42b738df = 49d97caa. Adding the last lookup gives 49d97caa + 1b38b413 = 651230bd, so F = 651230bd. The right half becomes 89abcdef xor 651230bd = ecb9fd52. After the swap, the pair is L1 = ecb9fd52 and R1 = 3ae7d7f3.
All 16 rounds. In each round, x is the left half XORed with the subkey. The Right column repeats x, because the old left half, after the XOR, becomes the new right half.
| Round | Subkey | x = L xor P | F(x) | Left | Right |
|---|---|---|---|---|---|
| 1 | P[0] = 3bc49294 | 3ae7d7f3 | 651230bd | ecb9fd52 | 3ae7d7f3 |
| 2 | P[1] = 0bad118b | e714ecd9 | bab07651 | 8057a1a2 | e714ecd9 |
| 3 | P[2] = 56b9b852 | d6ee19f0 | a6030a19 | 4117e6c0 | d6ee19f0 |
| 4 | P[3] = 59ffc1a1 | 18e82761 | e7a67c7b | 3148658b | 18e82761 |
| 5 | P[4] = 22c59cb3 | 138df938 | e2bc1a1d | fa543d7c | 138df938 |
| 6 | P[5] = c5d308f3 | 3f87358f | fde107d3 | ee6cfeeb | 3f87358f |
| 7 | P[6] = 9aed675a | 748199b1 | 60f86050 | 5f7f55df | 748199b1 |
| 8 | P[7] = fbfd6e02 | a4823bdd | 09ad031a | 7d2c9aab | a4823bdd |
| 9 | P[8] = 194edc7d | 646246d6 | 6f15b50e | cb978ed3 | 646246d6 |
| 10 | P[9] = 532d7b2b | 98baf5f8 | 2f9ac95d | 4bf88f8b | 98baf5f8 |
| 11 | P[10] = 16d8d0b6 | 5d205f3d | d06d0461 | 48d7f199 | 5d205f3d |
| 12 | P[11] = b488d33e | fc5f22a7 | 31bc480a | 6c9c1737 | fc5f22a7 |
| 13 | P[12] = 3c498d7d | 50d59a4a | 2ca8bbea | d0f7994d | 50d59a4a |
| 14 | P[13] = 363dc4c9 | e6ca5d84 | 7968d55f | 29bd4f15 | e6ca5d84 |
| 15 | P[14] = 92c4e9bf | bb79a6aa | 477fbd7c | a1b5e0f8 | bb79a6aa |
| 16 | P[15] = 2319f252 | 82ac12aa | dcef8fa4 | 6796290e | 82ac12aa |
The output step. After round 16 the pair is 6796290e and 82ac12aa. Undoing the last swap gives L = 82ac12aa and R = 6796290e. Then R = 6796290e xor a1368b83 = c6a0a28d, and L = 82ac12aa xor 8862b9a5 = 0aceab0f. The resulting ciphertext is:
0ACEAB0FC6A0A28D
Decryption. The first decryption round uses P[17]. It computes 0aceab0f xor 8862b9a5 = 82ac12aa, which is the x of round 16 in the table. The rounds then run backward through the table, and the last step XORs P[1] and P[0] to restore 0123456789ABCDEF.
Python Implementation
This is a complete Blowfish: the pi tables, the key schedule, and both directions of the cipher for one 8-byte block. Nothing is copied from a table. It is the same construction as the Blowfish inside the bcrypt guide, with the salt removed. The optional trace argument of encrypt_block reports every round, which is how the numbers above were collected.
# blowfish.py
#
# Blowfish (Bruce Schneier, 1993): a 64-bit Feistel cipher with 16 rounds
# and a key of up to 56 bytes (448 bits). The 18 subkeys of the P-array and
# the four S-boxes of 256 words start as the digits of pi. The key schedule
# XORs the key into the P-array. Then it runs 521 encryptions, starting from
# a block of zeros and feeding each result into the next, and the results
# replace every word of the P-array and the S-boxes.
MASK = 0xFFFFFFFF
def pi_words(count):
"""The first `count` 32-bit words of the fractional part of pi, from Machin's formula."""
bits = 32 * count + 64
one = 1 << bits
def arctan_inverse(x):
total = term = one // x
x2 = x * x
n, sign = 1, -1
while term:
term //= x2
n += 2
total += sign * (term // n)
sign = -sign
return total
pi = 4 * (4 * arctan_inverse(5) - arctan_inverse(239))
frac = pi - 3 * one
return [(frac >> (bits - 32 * (i + 1))) & MASK for i in range(count)]
# 18 words for the P-array, then 4 * 256 for the S-boxes: 1,042 words, 4,168 bytes
_PI = pi_words(18 + 4 * 256)
INIT_P = _PI[:18]
INIT_S = [_PI[18 + 256 * i:18 + 256 * (i + 1)] for i in range(4)]
class Blowfish:
def __init__(self, key):
if not 1 <= len(key) <= 56:
raise ValueError("a Blowfish key has 1 to 56 bytes")
self.p = INIT_P[:]
self.s = [box[:] for box in INIT_S]
self._key_schedule(key)
def f(self, x):
s = self.s
return ((((s[0][x >> 24] + s[1][(x >> 16) & 255]) & MASK) ^ s[2][(x >> 8) & 255]) + s[3][x & 255]) & MASK
def _crypt(self, left, right, decrypt=False, trace=None):
"""16 rounds on two 32-bit halves. Decryption uses the same network with the subkeys reversed."""
p = self.p
for i in range(16):
k = 17 - i if decrypt else i
x = left ^ p[k]
fx = self.f(x)
left, right = right ^ fx, x
if trace:
trace(i + 1, k, x, fx, left, right)
left, right = right, left # undo the last swap
right ^= p[1 if decrypt else 16]
left ^= p[0 if decrypt else 17]
return left, right
def _key_schedule(self, key):
pos = 0
for i in range(18):
word = 0
for _ in range(4):
word = (word << 8) | key[pos]
pos = (pos + 1) % len(key)
self.p[i] ^= word
left = right = 0
for i in range(0, 18, 2):
left, right = self._crypt(left, right)
self.p[i], self.p[i + 1] = left, right
for box in self.s:
for i in range(0, 256, 2):
left, right = self._crypt(left, right)
box[i], box[i + 1] = left, right
def encrypt_block(self, block, trace=None):
left, right = int.from_bytes(block[:4], "big"), int.from_bytes(block[4:], "big")
left, right = self._crypt(left, right, False, trace)
return left.to_bytes(4, "big") + right.to_bytes(4, "big")
def decrypt_block(self, block):
left, right = int.from_bytes(block[:4], "big"), int.from_bytes(block[4:], "big")
left, right = self._crypt(left, right, True)
return left.to_bytes(4, "big") + right.to_bytes(4, "big")
if __name__ == "__main__":
key = bytes.fromhex("FEDCBA9876543210")
plaintext = bytes.fromhex("0123456789ABCDEF")
cipher = Blowfish(key)
print(f"Key: {key.hex().upper()}")
print(f"Plaintext: {plaintext.hex().upper()}")
print(f"P[0] from pi: {INIT_P[0]:08x}, after the key schedule: {cipher.p[0]:08x}")
def show(rnd, k, x, fx, left, right):
if rnd in (1, 2, 16):
print(f"Round {rnd:2d}: P[{k}]={cipher.p[k]:08x} x={x:08x} F={fx:08x} L={left:08x} R={right:08x}")
ciphertext = cipher.encrypt_block(plaintext, show)
print(f"Ciphertext: {ciphertext.hex().upper()}")
print(f"Recovered: {cipher.decrypt_block(ciphertext).hex().upper()}")
Running it produces this output:
Key: FEDCBA9876543210
Plaintext: 0123456789ABCDEF
P[0] from pi: 243f6a88, after the key schedule: 3bc49294
Round 1: P[0]=3bc49294 x=3ae7d7f3 F=651230bd L=ecb9fd52 R=3ae7d7f3
Round 2: P[1]=0bad118b x=e714ecd9 F=bab07651 L=8057a1a2 R=e714ecd9
Round 16: P[15]=2319f252 x=82ac12aa F=dcef8fa4 L=6796290e R=82ac12aa
Ciphertext: 0ACEAB0FC6A0A28D
Recovered: 0123456789ABCDEF
I checked this code against several independent sources before writing it up. The pi tables match the constants file that Schneier publishes: all four S-boxes and the P-array are identical. The cipher reproduces Eric Young’s test vectors, as published by Schneier: all 34 ECB vectors, in both directions, and all 24 key lengths of the key setup test, which use keys of 1 to 24 bytes. It also reproduces the CBC, CFB, and OFB chaining tests of that file, using a small chaining wrapper that is not part of the listing. Finally, it agrees with OpenSSL 3.5.5 (the bf-ecb cipher from its legacy provider) on 200 random 16-byte keys, in both directions.
Those checks have limits. The published vectors have no key longer than 24 bytes, and my OpenSSL test used 16-byte keys only. Keys of 25 to 56 bytes were therefore checked only for a correct round trip, and not against any outside reference. The visualizer’s JavaScript was tested separately. In Node, it passes every vector in that file, and it matches the Python output on 300 random keys of 4 to 56 bytes. In a simulated browser (jsdom), I loaded the built page and clicked its buttons with the animation delays removed. The page produced the published result for all 34 ECB vectors in both directions, and for the 21 key-length vectors with keys of 4 bytes or more. It refused the three shorter keys, and it showed the right message for bad input. That test does not show how the page looks or feels in a real browser.
For Fun
Blowfish skips the short version on purpose: most of its code derives the 18-word P-array and four 256-entry S-boxes from the digits of pi and then runs 521 key-setup encryptions, so a compressed version would still need all of that subkey machinery.
Limitations
This is a faithful teaching version of the cipher, not a production one:
- Single 8-byte block only. There is no mode of operation for longer messages and no padding for partial blocks. Encrypting each block on its own (ECB) would leak patterns, so real use needs a mode such as CBC or CTR.
- Table lookups depend on secret data. The S-box indexes come from secret values, so a real implementation must consider cache-timing leaks that this plain code does not address.
- The key schedule is not optimized. It recomputes all 521 encryptions on every call, which is fine for learning and slow for changing keys often.
- Keys must be read as unsigned bytes. Some old C code read key bytes as signed characters, so a byte of
0x80or more was sign-extended. Python’sbytescannot make that mistake. The bug is described in the next section. - The 64-bit block is a real limit. Even a perfect implementation is unsuitable for large volumes of data, as the next section explains.
- Never use this in production. Use a vetted library, and for new systems choose AES or ChaCha20 instead of Blowfish.
Security Status
Blowfish’s core algorithm has held up well cryptographically. No practical attack breaks full 16-round Blowfish. Schneier’s own page for the cipher says that few results have been published. Serge Vaudenay found a class of weak keys that can be detected, but not broken, in variants with 14 rounds or fewer, and Vincent Rijmen’s thesis includes a second-order differential attack on 4 rounds that does not extend further.
Blowfish’s real weakness is structural, not mathematical. Like DES and 3DES, Blowfish uses a small 64-bit block size. With 64-bit blocks, two ciphertext blocks are expected to collide after about 2³² blocks, which is 32 GiB of data under one key. That makes it vulnerable to the same Sweet32 birthday-attack class that affects 3DES, when large volumes of data are encrypted under a single key in a long-lived connection. This is why Schneier himself now recommends Twofish (its 128-bit-block successor) or AES for new systems. Blowfish remains acceptable mainly for legacy compatibility or short-lived, low-volume encryption needs.
Implementations have also had bugs. In 1996 a post to sci.crypt reported that the Blowfish code printed in Applied Cryptography read each key byte as a signed character. A key byte of 0x80 or more was sign-extended to 0xffffff80, and ORing that into the key word set the upper bits to one, which wiped out much of the key. Schneier’s page says the reference implementation does not have the bug. It was an error in some code and not a flaw of the algorithm. Test vectors with plain ASCII keys would not reveal it. Young’s published vectors use many keys with high bytes. I reproduced the bug in Python, and it fails 24 of the 34 ECB vectors. The same kind of bug later appeared in bcrypt code and gave rise to the $2x$ prefix, which the bcrypt guide describes.
Blowfish’s Legacy: bcrypt and Twofish
Blowfish’s slow, key-dependent S-box generation turned out to be extremely useful outside of raw encryption. The bcrypt password-hashing function deliberately repurposes Blowfish’s expensive key schedule as its core “work factor,” making brute-force password guessing computationally expensive by design. It’s a clever reuse of what started as a performance quirk. The bcrypt guide builds on exactly the code in this article.
Schneier and colleagues later designed Twofish as a direct successor, entering it as an AES competition finalist. It fixed Blowfish’s small block size (moving to 128 bits) while keeping the spirit of fast, key-dependent, software-friendly design.
FAQ
Is Blowfish still safe to use?
Its core cipher remains unbroken, but its 64-bit block size makes it unsuitable for encrypting large amounts of data under one key (see Sweet32). For new systems, use AES or Blowfish’s own successor, Twofish.
Why does Blowfish take so long to set up a new key?
Its key schedule runs 521 encryptions of the cipher itself just to generate its S-boxes and subkeys, which costs as much as encrypting 4,168 bytes. It deliberately trades key-setup speed for stronger, key-unique substitution tables. This trade-off was later repurposed intentionally in bcrypt to slow down password-guessing attacks.
Why do the S-boxes start from the digits of pi?
Pi is a public number that anyone can check, so the starting tables cannot hide a weakness chosen by the designer. The key schedule then overwrites every word, so the digits only matter as a neutral starting point.
What’s the difference between Blowfish and Twofish?
Twofish is Blowfish’s successor, also designed by Bruce Schneier’s team: it uses a 128-bit block (fixing Blowfish’s Sweet32-style vulnerability), key-dependent S-boxes generated more efficiently, and was a finalist in the AES competition.
Can Blowfish use any key length?
The specification allows 32 to 448 bits, which is 4 to 56 bytes. That’s one of its defining design flexibilities, letting implementers choose their own security/performance balance. Some libraries accept shorter keys, and Young’s test vectors include keys of 1 to 3 bytes.
Do a key and the same key written twice give the same ciphertext?
Yes, they give the same ciphertext. The key bytes are read cyclically, so 01020304 and 0102030401020304 produce an identical state, and so do their ciphertexts. This is one more reason to use a random key with no repeating pattern.
Why was Blowfish so popular in the 1990s?
It was released into the public domain, royalty-free and unpatented, at a time when strong cryptography was often export-restricted or commercially licensed. That made it an attractive default for open-source software like OpenSSH.
References
-
Schneier, B. “Description of a New Variable-Length Key, 64-Bit Block Cipher (Blowfish).” 1993. Available at: https://www.schneier.com/academic/blowfish/
-
Schneier, B. “Eric Young’s Blowfish test vectors.” Available at: https://www.schneier.com/wp-content/uploads/2015/12/vectors-2.txt
-
Schneier, B. “Blowfish constants (the hex digits of pi as four S-boxes and a P-array).” Available at: https://www.schneier.com/wp-content/uploads/2015/12/constants-2.txt
-
Morgan, M. “Blowfish can be cracked! (Fix included…).” sci.crypt, 6 July 1996, archived by Schneier. Available at: https://www.schneier.com/wp-content/uploads/2015/12/blowfish-bug-2.txt
-
Wikipedia. “Blowfish (cipher).” Available at: https://en.wikipedia.org/wiki/Blowfish_(cipher)
-
Bhargavan, K. and Leurent, G. “Sweet32: Birthday Attacks on 64-bit Block Ciphers in TLS and OpenVPN.” 2016. Available at: https://sweet32.info/
-
Provos, N. and Mazières, D. “A Future-Adaptable Password Scheme.” USENIX 1999 (the original bcrypt paper).