Minimalistic logo symbolizing computational linguistics John Winstead
  • Home
  • Services
  • Research
  • Blog
  • CV

On this page

  • Key Recovery
    • 1. Clear Text ↔︎ Cipher Text Alignment
    • 2. Map Letters to Numbers
    • 3. Compute Shifts
    • 4. Convert Back to Letters
    • 5. What This Means
  • Why Would Grammarly Do This?
  • Final Thoughts
  • References
  • Edit this page
  • Report an issue

When Grammarly Speaks Gibberish

Grammarly
cryptography
Reverse-engineering a seemingly nonsensical Grammarly suggestion.
Published

June 9, 2025

A tweet was making the rounds today that showed this bizarre Grammarly suggestion:

Google document with Grammarly Chrome extension suggesting an edit

At first suggestion appears to be another instance of an AI-generated hallucination, but notice that every word length in the suggestion matches a word length in the original sentence. The only major reordering is that the longest word has been moved to the end.

Original text

to effectively pass my flame to others

Grammarly suggestion

fe gcnv wj dicwz dv vdbsoe sdbsofluriq

If you ignore the word movement, each word of the suggestion is exactly the same length as its English counterpart, which suggests there is a relationship between the original phrase and the Grammarly suggestion. Whatever this relationship is, it can’t be a simple substitution of letters, since, for example, t maps onto both f and d. There are many types of substitutions in cryptography that can deterministically map the same input letter onto multiple different output letters. The Enigma machine famously had a very sophisticated rotating system of mapping input and output letters. Assuming there is a meaningful relationship between the input and output here, it is likely to be something less computationally intense than Enigma. But what? The simplest kind of cipher I know of that behaves this way and that is non-trivially difficult to crack is a Vigenère cipher [1].

To test if this is the case, I treated the clear text (the English text) and the cipher text (the Grammarly suggestion) as a Vigenère pair and set out to recover the key, the per‐character shift sequence that governs each letter’s transformation. Typically in cryptography you would not know the clear text, making key recovery difficult; here, having both strings makes recovering the potential key doable.


Key Recovery

1. Clear Text ↔︎ Cipher Text Alignment

First, we have to make an assumption about the encryption process. Since this encryption process, whatever it is, occurs in the context of a grammar and spelling checker, it’s more likely that the cipher text would be the clear text version of the Grammarly suggestion and not the original version the user wrote, since if the user’s original text was the cipher text then Grammarly would have to run it’s grammar and spelling checker on an arbitrarily encrypted string. It makes much more sense that the user should have their text sent to Grammarly, Grammarly generate a possible correction, and then encrypt this suggestion before sending it back to the user or at least some process in that order. If that is the case, then to calculate the key for this cipher we have to align corrected clear text suggestion with the cipher text, then strip out spaces and lowercase everything so each letter lines up:

Clear : topassmyflametootherseffectively

Cipher : fegcnvwjdicwzdvvdbsoesdbsofluriq

2. Map Letters to Numbers

Next, to convert the letters into numbers, we use the common convention A=0, B=1, \dots, Z=25, then compute the shifts, and finally convert the results back into letters. You could just as well start at A = 1 (or any other consistent numbering), and you’d still recover the same key because any constant offset cancels out when you subtract. The only thing that matters is that you label the alphabet in a fixed order; shuffling the order would break the arithmetic. For the purposes of this demonstration, I’ll stick with 0–25, since it is simply the clearest, most universally understood choice.

Clear P_i Cipher C_i
t 19 f 5
o 14 e 4
p 15 g 6
a 0 c 2
… … … …

3. Compute Shifts

Once we have the both sets of letters mapped, we calculate for each index i, the value of the key for that index pair.

k_i \;=\; (C_i - P_i)\bmod 26.

i P_i C_i k_i = (C_i - P_i)\bmod26
0 19 5 12
1 14 4 16
2 15 6 17
3 0 2 2
4 18 13 13
… … … …

Carrying this calculation through all 32 positions results in the following list:

12, 16, 17, 2, 21, 3, 10, 11, 24, 23, 2, 10, 21, 10, 7, 7, 10, 20, 14, 23, 12, 14, 24, 22, 14, 12, 12, 3, 25, 13, 23, 18

4. Convert Back to Letters

Once we have our sequence of numbers, we use the same letter-number ordering to translate each k_i into a letter. The first few shifts give us:

  • 12 → M
  • 16 → Q
  • 17 → R
  •  2 → C

Concatenating the full sequence together yields the 32-character key:

MQRCVDKLYXCKVKHHKUOXMOYWOMMDZNXS

In orther words, we’ve proved there is a determinstic relationship between the original English text and the Grammarly suggestion and that the relationship can be modeled using a Vigenère cipher. With this key, you can run the whole process in reverse to generate from some clear text new cipher text. The Python code below implements this whole process:

import string

# suggestion clear text and the obfuscated suggestion
P = "topassmyflametootherseffectively"
C = "fegcnvwjdicwzdvvdbsoesdbsofluriq"
A = string.ascii_lowercase

# recover key
key = ''.join(
    A[(A.index(c) - A.index(p)) % 26]
    for p, c in zip(P, C)
)
assert key.upper() == "MQRCVDKLYXCKVKHHKUOXMOYWOMMDZNXS"

# re-encrypt to double-check
cipher2 = ''.join(
    A[(A.index(p) + A.index(k)) % 26]
    for p, k in zip(P, key)
)
assert cipher2 == C

5. What This Means

Every time Grammarly generates an edit suggestion, it creates a random key whose length matches the suggestion. Then it encrypts the suggestion one character at a time: for each position i, it takes the i-th key character and shifts the i-th suggestion character by that amount. Once every character has been processed, the fully enciphered text is sent to the user, then immediately decrypted via the inverse shifts to recover and display the clear text suggestion.


Why Would Grammarly Do This?

Grammarly already protects user data in transit and at rest: all communication with its servers uses TLS 1.2, and data stored on AWS is encrypted with AES-256 server-side encryption under AWS Key Management Service control [2][3]. Wrapping each suggestion in a one-time Vigenère cipher therefore isn’t intended to bolster network or disk security; instead, it likely is intended to provide an in-browser obfuscation layer whose primary aim is to keep the suggestion text hidden from other page scripts or scrapers. Since Grammarly is proprietary, close source software, all I can do is sketch out what I think is happening under the hood and why.

When the Chrome extension receives the user’s text, it’s sent over TLS to Grammarly’s servers, which process it and return a JSON payload containing the clear text suggestion [4]. Only then does the extension generate a fresh, per-suggestion key entirely inside the browser most likely by calling `crypto.getRandomValues()` to produce cryptographically secure random bytes, which are then reduced modulo 26 to yield per-character shifts [5][6].

Before any suggestion appears in the page’s DOM, the extension encrypts it with that key via a Vigenère-style shift, producing the gibberish cipher text seen in the oringinal screenshot. Because this happens in the extension’s isolated content-script context, an execution environment separate from the page’s own scripts, no other script, DOM-scraping tool, or network sniffer ever sees the clear text until the extension’s `decryptAndRender()` routine immediately reverses the process to display the human-readable suggestion for the user [6]. If decryption is momentarily delayed (for example, due to a race condition or debug pause), users briefly glimpse the scrambled text before it is replaced.

By generating a key exactly as long as the suggestion and never reusing it, Grammarly effectively achieves perfect secrecy since the cipher text reveals no information about clear text beyond its length [7]. Still, because the key is created and used entirely in-browser with minimal arithmetic operations (a few adds, mod 26, and array lookups), the performance impact is negligible and requires no extra network calls.

Ultimately, this mechanism is about protecting Grammarly’s proprietary NLP-generated suggestions from easy client-side extraction or scraping. It mirrors common closed-source obfuscation tactics such as simple XOR or Caesar ciphers to guard in-memory strings, but here applied dynamically per suggestion in the browser [8].

Final Thoughts

What might initially seem like a bug or AI hallucination is actually a mark of the incredibly complex technological ecosystem that our contemporary world is built around. Neat.

References

  1. “Vigenère cipher,” Wikipedia. https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
  2. Security at Grammarly. “Data encryption,” Grammarly Trust Center. Accessed June 2025. https://www.grammarly.com/security
  3. Grammarly Support. “Privacy and security FAQ,” accessed June 2025. https://support.grammarly.com/hc/en-us/articles/20916119474829-Privacy-and-security-FAQ
  4. reverse_engineering_grammarly_api. “README,” GitHub. https://github.com/bblanchon/reverse_engineering_grammarly_api/blob/master/README.md
  5. Web APIs | MDN. “Crypto.getRandomValues() method,” accessed June 2025. https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
  6. Chrome Extensions documentation. “Content scripts,” accessed June 2025. https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts
  7. “One-Time Pad OTP,” Cryptomuseum. https://www.cryptomuseum.com/crypto/otp/index.htm
  8. Chrome Extensions documentation. “Use content scripts carefully,” accessed June 2025. https://developer.chrome.com/docs/extensions/develop/security-privacy/stay-secure

© 2025, John Winstead

Built with Quarto

  • Edit this page
  • Report an issue
Cookie Preferences
  • Contact