nullcipher

Hide the secrets, paste into the AI, get them back

Keys, passwords and personal data become placeholders like [EMAIL_1]. It all happens in this tab: nothing is sent anywhere, not even to us.

1 Paste the text you want to ask about

Too big to paste, or a HAR capture? Mask a whole log, .env, JSON or HAR file and download the masked copy.

Words to always hide

Names, project codenames, customers, hostnames: no pattern can tell them from ordinary words, so list them here and every match becomes [CUSTOM_1], [CUSTOM_2]… Or select one in your text and tap Hide selected text.

    This list is saved only in this browser, on this device. It is never sent anywhere and is lost if you clear this site's data. It holds the words and their match mode, nothing else.

    How the matching works

    Whole word ignores case and needs a non-letter on both sides, in any alphabet: Bluebird hides Bluebird, BLUEBIRD and Bluebird-api but not Bluebirds or myBluebird; Åse hides ÅSE but not the end of Kåse. Anywhere also finds it inside longer words, which is what a codename used in identifiers like BluebirdService needs. A space in an entry matches any run of spaces or a line break, so Jens Hansen is still found when a line wraps between the names.

    Each entry is plain text, never a pattern: a.b hides only a.b. Each spelling gets its own placeholder (Jens Hansen and JENS HANSEN are two), so the answer comes back exactly as you wrote it. If a word sits inside something a rule already hides, such as jens@example.com, the longer of the two wins and it stays one [EMAIL_1]. Not matched: other forms of the name. Jens Hansen does not hide Jens on its own or the initials J.H., and in whole-word mode Jens Hansen's is caught but Jens Hansens is not. Add each form you use.

    The export is a plain .txt file with one entry per line, whole word: Jens Hansen or anywhere: Bluebird. Import adds a file's entries to your list; a line without a prefix counts as a whole word, so a plain list of names imports too.

    2 Paste the AI's answer to get your values back

    The list of placeholders and their real values lives only in this page's memory. It is not saved in browser storage, cookies or the address bar, so closing or reloading the tab forgets it — keep the tab open until you have the answer back. (The only thing this page saves in browser storage is your list of words to always hide: the words and their match mode, never which placeholder they got or any of your text.)

    What it looks for, and how it recognises each type

    nullcipher runs 242 detectors: the rules described below, including 196 provider-key rules ported from the open-source scanner gitleaks. Each detector is a fixed rule, run in this order; when two rules overlap, the earlier one wins (a GitHub token inside token= is labelled as a GitHub token, not a generic secret). Checksums are used wherever the format has one, because false alarms matter too: a masked version number or order ID makes the AI's answer harder to use.

    Each type has its own page with a live check and the provider's own revoke or rotate steps: what each kind of secret looks like, and what to do if you already pasted one into an AI. In the list of what was hidden, each type's name links to its page.

    Private key blocks — [PRIVATE_KEY_1]
    A whole -----BEGIN … PRIVATE KEY----- to -----END … PRIVATE KEY----- block (RSA, EC, DSA, OpenSSH, PKCS#8, encrypted and PGP) becomes one placeholder. Public keys and certificates are left alone; they are meant to be shared.
    AWS access key IDs — [AWS_KEY_1]
    AKIA (long-term) or ASIA (temporary) followed by exactly 16 upper-case letters or digits. The matching 40-character secret key has no prefix, so it is caught by the config rule when it sits after aws_secret_access_key=.
    GitHub tokens — [GITHUB_TOKEN_1]
    ghp_, gho_, ghu_, ghs_ or ghr_ followed by at least 36 letters and digits, or a fine-grained github_pat_ token.
    Slack tokens — [SLACK_TOKEN_1]
    xoxb- (bot), xoxp- (user), xoxa- or xoxr- followed by the dash-separated number and secret groups.
    Stripe secret and restricted keys — [STRIPE_KEY_1]
    sk_live_, sk_test_, rk_live_ or rk_test_ followed by at least 10 letters and digits. Publishable pk_ keys are designed to be public and are not masked.
    Google API keys — [GOOGLE_API_KEY_1]
    AIza followed by exactly 35 letters, digits, dashes or underscores — 39 characters in all.
    OpenAI and Anthropic API keys — [OPENAI_KEY_1] [ANTHROPIC_KEY_1]
    sk-ant- plus at least 20 characters is Anthropic. sk-proj-, sk-svcacct- or sk-admin- plus at least 20 characters, or a legacy sk- key of exactly 48 letters and digits, is OpenAI.
    Discord bot tokens — [DISCORD_BOT_TOKEN_1]
    Three dot-separated base64 parts whose first part decodes to the bot's 17–20 digit Discord user ID, so a bot token is recognised without the word Discord nearby. More
    Wallet recovery phrases — [RECOVERY_PHRASE_1]
    12, 15, 18, 21 or 24 consecutive words from the 2,048-word BIP-39 English list whose built-in SHA-256 checksum is valid (checked in this tab). Only 1 in 16 random 12-word runs passes, so a sentence made of list words stays; the whole phrase becomes one placeholder. More
    Keys and tokens from 196 more provider formats — [SENDGRID_API_TOKEN_1] [NPM_ACCESS_TOKEN_1] …
    Ported from gitleaks' default rules, each under its own placeholder: Slack webhook URLs, Twilio, SendGrid, npm, PyPI, Hugging Face, GitLab, Shopify, DigitalOcean, Heroku, Azure AD client secrets, Databricks, Doppler, Grafana, HashiCorp Vault, Linear, Notion, PlanetScale, Postman, Pulumi, Sentry and many more. Only the secret part is masked, and a rule runs only when its keyword appears. Where a provider's key has no prefix of its own (Mailgun, Telegram, Twitter, Discord client secrets), the rule needs the provider's name just before the value, as in MAILGUN_API_KEY=….
    Google Cloud service-account key files — [GCP_SA_KEY_ID_1]
    The 40-hex-digit private_key_id; the key file's private key is masked as a private key block and its client_email as an e-mail address. More
    JSON Web Tokens — [JWT_1]
    Three base64url segments joined by dots, where the first segment actually decodes to a JSON object with an "alg" field. Domain names and file names with dots fail that decode, so they stay.
    Passwords and keys in connection strings, URLs and commands — [CONN_SECRET_1]
    Only the secret part, so the AI can still help with the command: the password in postgres://app:password@db:5432/orders (also redis://:password@host); Password=, AccountKey= and SharedAccessKey= in a Key=Value; connection string; the sig= of an Azure SAS URL and the X-Amz-Signature= of an S3 presigned URL; the password in curl -u user:password; --password VALUE (docker login, wget); mysql -pPASSWORD; and a .netrc password. Host, user, port, database, AccountName and the other SAS parameters stay. Nothing to hide, so left as is: curl -u bob (curl prompts), mysql -p mydb (mysql prompts), --password-stdin, and docker run -u 1000:1000.
    Bearer tokens and Basic credentials — [BEARER_1]
    The token after Authorization: Bearer (it must contain a digit, so the phrase “Bearer authentication” is left alone) and the base64 value after Basic when it decodes to user:password. The header name and the scheme stay.
    Session cookies — [COOKIE_1]
    Every value in a Cookie: request header, in a raw HTTP dump, a curl -H argument or curl -b 'name=value', because a request's cookie list is session state and the session cookie can have any name. In Set-Cookie: only the first value. Cookie names and the attributes Path, Domain, Expires, Max-Age, Secure, HttpOnly and SameSite stay readable.
    Passwords and secrets in config lines — [PASSWORD_1] [SECRET_1]
    The value after a key whose last word names a secret — password, passwd, pwd, pass, passphrase, secret, token, api_key, access_key, private_key, client_secret — in KEY=value, key: value, "key": "value" and --key=value shapes. The key stays visible so the AI still understands the line. This includes a secret-named parameter in a URL query string, such as ?api_key=… or &access_token=…, while page= and lang= stay. Left alone: true/false/null, ${VAR} references, type names such as password: string, code such as password = get_secret(), keys like token_ttl or max_tokens, and the shell's PWD= working directory. An unquoted value ends at the first space.
    E-mail addresses — [EMAIL_1]
    name@domain.tld with a letters-only top-level domain. user@localhost, react@18.2.0 and image names like logo@2x.png are not addresses.
    Phone numbers — [PHONE_1]
    A + and country code with 8–15 digits in all (the E.164 limit), e.g. +44 20 7946 0958; a North American number in (415) 555-2671 or 415-555-2671 form, whose area code and exchange cannot start with 0 or 1; or a national number that starts with a 0 trunk prefix in space- or dash-separated groups, e.g. 020 7946 0958 or 01 23 45 67 89. Not phones: dates (2026-09-22, 22/09/2026), times, version numbers (10.0.19045) and runs of digits with no separators. Limit: a national number with no + and no leading 0, such as a Danish 12 34 56 78, is indistinguishable from other numbers and is not masked.
    IBANs — [IBAN_1]
    Two-letter country code, two check digits and the account part (spaces every four characters allowed), at the exact length that country uses (22 for GB and DE, 18 for DK and NL, 27 for FR…), passing the ISO 13616 mod-97 check. GB82 WEST 1234 5698 7654 32 is masked; change any one digit and the check fails, so it is not.
    National ID numbers — [CPR_1] [SSN_1] …
    Twenty-one, each checked against its issuing authority's or its defining law's published structure: the Danish CPR number, Swedish personnummer and samordningsnummer, Norwegian fødselsnummer and D-number, Finnish henkilötunnus (HETU), Dutch BSN, UK National Insurance number, US Social Security number, German Steuer-ID, French NIR, Spanish DNI and NIE, Italian codice fiscale, Polish PESEL, Belgian national register number, Icelandic kennitala, Czech and Slovak birth number, Bulgarian EGN, Croatian OIB, Irish PPS number, Slovenian EMŠO, Austrian social insurance number and Swiss AHV number. Dates must be real and check digits must match. Where a random number passes too easily — the CPR number and the SSN have no check digit at all, and 1 in 11 nine-digit numbers passes the BSN 11-proef — the number is only masked when one of its names (CPR, SSN, BSN, personnummer, NI number, Steuer-ID, DNI…) stands within 40 characters before it, so order 123456782 stays. The HETU and the codice fiscale are distinctive enough to be masked on their own.
    Payment card numbers — [CARD_1]
    13 to 19 digits, with single spaces or dashes allowed, that pass the Luhn check and start with a real issuer prefix at that issuer's length (Visa 4…, Mastercard 51–55 and 2221–2720, Amex 34/37 at 15 digits, Discover, JCB, Diners, UnionPay, Maestro). 4111 1111 1111 1111 is masked; 4111 1111 1111 1112 fails Luhn and is not. The expiry date and CVV are not recognised on their own.
    Names and street addresses — suggested, never hidden on their own — [NAME_1] [ADDRESS_1]

    A capitalised given name followed by a capitalised surname (Jens Hansen, also Mary Ann Smith, Jens P. Hansen, and Sanne de Vries with van, de, von der and similar particles). The given name must be on one of the published lists below. The surname is accepted when it is on a surname list, or when it is any other capitalised word that is not a common word, weekday, month or place, so Visual Studio Code, Google Chrome, On Monday and New York Times are not flagged. A given name that is also an ordinary word or a place, such as Will, May or Austin, only counts before a listed surname: Will Smith is suggested, Will this is not.

    Street addresses need a street line and a postcode of the right shape after it, separated by a comma or a line break: Danish and Norwegian Vesterbrogade 12, 1620 København V (four digits, 0800–9990 in Denmark, 0001–9991 in Norway, and a street name ending in a street word such as -vej, -gade, -gata or -veien); Swedish Drottninggatan 5, 111 51 Stockholm (NNN NN); German Hauptstraße 5, 10115 Berlin (five digits, 01001–99998); Dutch Damrak 1, 1012 LG Amsterdam (NNNN AA); British 221B Baker Street, London NW1 6XE (the postcode pattern from the UK government's data standards); and US 1600 Pennsylvania Avenue NW, Washington, DC 20500 (number, street and suffix, city, a real state code, ZIP or ZIP+4).

    Tap Hide and every occurrence of that exact text, in any letter case, becomes [NAME_1] or [ADDRESS_1] and comes back when you un-mask. Also add to my words to always hide puts it on your saved list instead, so it is hidden next time too. What it misses: a first name on its own (Jens), names written HANSEN, Jens or in all capitals, given names not on the lists (in a test on this build: Pieter, Wolfgang and Björn are missing), addresses without a postcode, and other countries' address shapes. What it flags wrongly: a listed first name before any capitalised word, such as a company called Emily Studios. Measured before this section was written, over 15,000 words of this site's pages and its maker's working notes, it suggested three names, all of them real, and nothing else; on your text it may do worse.

    The lists, lower-cased and merged into one 230 KB file (names.json), downloaded from this site the first time you paste something and never sent anywhere: US Census Bureau 1990 first names (5,494, public domain) and 2010 surnames (the 20,000 most common of 162,253, public domain); Statistics Norway first names and surnames (2,150 first names and the 3,705 surnames used by 200 or more people, CC BY 4.0); and the Office for National Statistics baby names for England and Wales, 2025 (the 1,000 most common for boys and for girls, Open Government Licence v3.0). All fetched on 26 September 2026. Danmarks Statistik and Statistics Sweden publish name counts only through search pages with no downloadable list, and the US Social Security baby-name file refused automated downloads, so they are not used.

    IP addresses — [IPV4_1] [IPV6_1]
    IPv4: four numbers 0–255 without leading zeros, not part of a longer dotted run, so 999.1.1.1 and 10.0.19045.2965 are not addresses; 0.0.0.0, 127.0.0.1 and 255.255.255.255 identify nothing and are skipped. IPv6: eight groups of up to four hex digits, or fewer with one ::, optionally ending in an IPv4 address or a %eth0 zone; ::1 is skipped, and times such as 12:30:45 or MAC addresses do not parse as IPv6.
    User names in file paths — [USER_1]
    Only the account folder of C:\Users\jens.hansen\ (also with forward slashes, or doubled backslashes inside JSON), /Users/jens/ and /home/jens/, so a stack trace keeps its shape: C:\Users\[USER_1]\AppData\Local\…. Accounts that name no person are left alone: Public, Default, All Users, Shared, root, ubuntu, node, www-data and CI accounts such as runner. It misses a user name anywhere else in a path (D:\dev\jens\) or in the text. The full rule.
    Internal host names — [HOST_1]
    Names under the private-network suffixes .internal, .local, .lan, .home.arpa, .localdomain, .intranet and .corp, and hosts inside a corp. or intranet. zone such as db01.corp.acme.com. In a URL only the host is hidden, so https://[HOST_1]:8443/v2/health keeps its port and path. A public domain is never masked by this rule; add your company's domain to your word list if it should go too. It misses internal hosts with public-looking names and may flag a Python module path such as myapp.settings.local. The full rule.
    MAC addresses — [MAC_1]
    Exactly six hex pairs with one separator throughout (00:1A:2B:3C:4D:5E, 00-1A-2B-3C-4D-5E) or Cisco's 001a.2b3c.4d5e. IPv6 addresses, times such as 12:34:56.789, git hashes and hex dumps are left alone; a MAC written with no separators is missed. The full rule.

    Your own words — [CUSTOM_1]: anything on your list of words to always hide, matched as literal text either as a whole word or anywhere. Where one overlaps a match from the rules above, the longer of the two wins.

    Never masked by any rule: UUIDs, git commit hashes, SHA-256 digests, dates, times and version numbers — unless they sit after a secret-named key such as token=, in which case the key is what gives them away.

    How the placeholders come back

    Each distinct value gets one placeholder, numbered by type in the order it first appears, and the same value always gets the same placeholder — so if alice@example.com appears in five log lines, the AI sees [EMAIL_1] five times and can still tell it is one person. If your text already contains something like [EMAIL_1], the numbering skips it so the two can never be confused.

    DB_PASSWORD=hunter2            →  DB_PASSWORD=[PASSWORD_1]
    alice@example.com failed login →  [EMAIL_1] failed login
    from 203.0.113.7               →  from [IPV4_1]

    When you paste the answer into step 2, every placeholder the page issued is swapped back, wherever the AI put it: inside `backticks`, quotes, bold text, code blocks, next to punctuation, or with the brackets and underscore escaped as \[EMAIL\_1\]. Bracketed tokens it did not issue, like [TODO_1] or a placeholder the AI invented, are left exactly as they are.

    Why mask before pasting instead of asking the AI to ignore it

    Once text is sent to a chat service it has left your machine: it may be stored in the conversation history, reviewed for abuse, or used for training depending on the service and your settings. An instruction like "ignore the password" arrives together with the password. Masking has to happen before the paste, on your side — which is why this page does it in the browser and keeps the key to the placeholders in the tab.

    Made with AI tools.