{"id":15542450,"url":"https://github.com/maxfierke/fincher","last_synced_at":"2025-10-05T03:56:39.453Z","repository":{"id":44121677,"uuid":"79176931","full_name":"maxfierke/fincher","owner":"maxfierke","description":"A steganography tool for text","archived":false,"fork":false,"pushed_at":"2025-09-24T05:22:46.000Z","size":2938,"stargazers_count":89,"open_issues_count":0,"forks_count":8,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-24T06:21:08.463Z","etag":null,"topics":["crystal","steganography"],"latest_commit_sha":null,"homepage":"","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maxfierke.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-01-17T01:47:39.000Z","updated_at":"2025-09-24T05:20:26.000Z","dependencies_parsed_at":"2024-11-01T11:03:58.540Z","dependency_job_id":"c183f788-0de0-4705-9cf8-12bf71f4bf02","html_url":"https://github.com/maxfierke/fincher","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/maxfierke/fincher","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxfierke%2Ffincher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxfierke%2Ffincher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxfierke%2Ffincher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxfierke%2Ffincher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxfierke","download_url":"https://codeload.github.com/maxfierke/fincher/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxfierke%2Ffincher/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278406448,"owners_count":25981657,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-05T02:00:06.059Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crystal","steganography"],"created_at":"2024-10-02T12:22:44.335Z","updated_at":"2025-10-05T03:56:39.256Z","avatar_url":"https://github.com/maxfierke.png","language":"Crystal","funding_links":[],"categories":[],"sub_categories":[],"readme":"# fincher\n\nfincher is steganography tool for text. It provides a number of strategies for\nhiding a message within a source text by storing each character as a typo.\n\nThe method by which it works is contigent upon the combination of replacement\nand displacement strategy. See [Usage](#Usage) for more information.\n\n![Still from Person of Interest episode \"Panopticon\", Season 4 Episode 1](docs/panopticon.png)\n\nThe inspiration for `fincher` comes from \"Panopticon\", Season 4 Episode 1 in\nPerson of Interest, in which _The Machine_ encodes a message as typos in the\ndissertation of one of the main characters, Harold Finch.\n\n`fincher` is currently `0.3.1` and considered an **experiment**\nand a project for **funsies**. I am very interested in contributions \u0026 ideas!\n\n## Disclaimer\n\nWhile `fincher` is a steganography tool, **no guarantees are made about it's\nsuitablity for any purpose, especially hiding information from hostile actors**.\n\nDue to the fact that fincher hides messages in a source text as typos, if the\ninformation is stored digitally as text, it would be relatively easy to\nrun a spellchecking over the text to determine where the typos are, and work\nbackwards. Possible mitigations are storing text in physical printed form and\nencrypting the source message.\n\n## Installation\n\n### via Homebrew (macOS users)\n\n```\n$ brew tap maxfierke/fincher\n$ brew install fincher\n```\n\n### Manually\n\n1. Ensure you have the [crystal compiler installed](https://crystal-lang.org/docs/installation/) (1.7.0+)\n2. Clone this repo\n3. Run `make install RELEASE=1` to build for release mode and install\n4. `fincher` will be installed to `/usr/local/bin` and usable anywhere, provided it's in your `PATH`.\n\n## Usage\n\n```\n$ fincher encode\n\nfincher encode [OPTIONS] SOURCE_TEXT_FILE MESSAGE\n\nArguments:\n  MESSAGE           message\n  SOURCE_TEXT_FILE  source text file\n\nOptions:\n  --char-offset NUMBER            character gap between typos (Displacement Strategies: char-offset)\n                                  (default: 130)\n  --codepoint-shift NUMBER        codepoints to shift (Replacement Strategies: n-shifter)\n                                  (default: 7)\n  --displacement-strategy STRING  displacement strategy (Options: char-offset, word-offset, matching-char-offset)\n                                  (default: matching-char-offset)\n  --keymap STRING                 Keymap definition to use for keymap replacement strategy\n                                  (default: en-US_qwerty)\n  --replacement-strategy STRING   replacement strategy (Options: n-shifter, keymap)\n                                  (default: keymap)\n  --seed NUMBER                   seed value. randomly generated if omitted\n                                  (default: )\n  --word-offset NUMBER            word gap between typos (Displacement Strategies: word-offset, matching-char-offset)\n                                  (default: 38)\n```\n\n### Example\n\nLet's use the part of the introduction paragraph of the [English Wikipedia article for Canada](https://en.wikipedia.org/wiki/Canada)\n\n\u003e Canada is a country in the northern part of North America. Its ten provinces\n\u003e and three territories extend from the Atlantic to the Pacific and northward\n\u003e into the Arctic Ocean, covering 9.98 million square kilometres (3.85 million\n\u003e square miles), making it the world's second-largest country by total area.\n\nThis is saved in `test_files/canada.txt`.\n\nNext, we'll encode it with `fincher`.\n\n```\n$ fincher encode --displacement-strategy word-offset --word-offset 3 --replacement-strategy n-shifter --codepoint-shift 0 test_files/canada.txt \"Hello GitHub\"\n```\n\nWhich will produce this output:\n\n\u003e Canada is a **H**ountry in the **e**orthern part of **l**orth America. Its **l**en provinces and\n\u003e **o**hree territories extend **\\_**rom the Atlantic **G**o the Pacific **i**nd northward into **t**he\n\u003e Arctic Ocean, **H**overing 9.98 **u**illion square kilometres (**b**.85 million square miles\n\u003e ), making it the world's second-largest country by total area.\n\n\n### Displacement strategies\n\nDisplacement strategies determine where each character within the message gets\nencoded within the source text.\n\n#### `char-offset`\n\nThe `char-offset` strategy will distribute each message character by N number of\ncharacters, as specified by the `--char-offset` option.\n\ne.g. `--displacement-strategy char-offset --char-offset 10` will\ndistribute a character of the message every 10 characters in the source text.\n\n**Relevant options**: `--char-offset`\n\n#### `matching-char-offset`\n\nThe `matching-char-offset` strategy will distribute each message character by\nfinding a matching character at least every N words, as specified by the\n`--word-offset` option.\n\ne.g. `--displacement-strategy matching-char-offset --word-offset 10`\nwill take a message character and ensure there's _at least_ a 10 word gap\nsince the last message character then find the next matching character in the\nsource text.\n\n**Relevant options**: `--word-offset`\n\n#### `word-offset`\n\nThe `word-offset` strategy will distribute each message character by N number of\nwords, as specified by the `--word-offset` option.\n\ne.g. `--displacement-strategy char-offset --word-offset 10` will\ndistribute a character of the message every 10 words in the source text.\n\n**Relevant options**: `--word-offset`\n\n### Replacement strategies\n\nReplacement strategies determine how a character within the source text is\nreplaced, based on an individual message character.\n\n#### `keymap`\n\nThe `keymap` strategy will replace a character within the source text based on\na keymap definition of which keys neighbor it (including Shift modified). The\nkey chosen will be random.\n\nWhich keymap to use can be specified by the `--keymap` option,\ne.g. `--keymap en-US_qwerty`, but is of little use right now, as only\n`en-US_qwerty` is supported.\n\n`keymap` is best paired with the `matching-char-offset` replacement strategy to\ncreate an effect of a plausible typo.\n\n**Relevant options**: `--keymap`, `--seed`\n\n#### `n-shifter`\n\nThe `n-shifter` strategy will replace a character within the source text with\na message character shifted N codepoints, as specified by the `--codepoint-shift`\noption.\n\n**Relevant options**: `--codepoint-shift`\n\n## Decoding\n\nYou may have noticed that there is no `fincher decode` command. Partly, this is\nis because the intention is that the typos are to be resolved by a human reading\nthe encoded text. However, it is also the case that many of the displacement and\nreplacement strategy combinations are non-deterministic and potentially lossy.\n\nFor example, the `keymap` replacement strategy will (pseudo)randomly decide\nwhich character to use to replace a character in the source text based on the\ncharacters close to a message character on the keyboard.\n\n## Limitations\n\n`fincher` has some notable limitations:\n\n* The current displacement and replacement strategies are not context-aware.\n  i.e. they do not make judgements based on the content of the source text and\n  whether the replacement or displacement makes sense grammatically.\n* Source text scanning happens on a rotating 4K buffer (so you could feed it\n  multi-GB source text, if you wanted to), but the `IOScanner` does not handle\n  regex matching across buffer boundaries. Therefore, the `--[word|char]-offset`\n  parameters are not applied exactly, but will make minimum guarantees about the\n  offset.\n\n## Development\n\nTo work on `fincher`, you'll need a current version of the Crystal compiler. I\ngenerally try to keep it targeting the latest version, as Crystal is a moving\ntarget, and not all APIs have stability guarantees yet.\n\nI welcome suggestion and discussion of new displacement and replacement\nstrategies, as well as architectural and interface changes.\n\n## Contributing\n\n1. Fork it ( https://github.com/maxfierke/fincher/fork )\n2. Create your feature branch (git checkout -b my-new-feature)\n3. Commit your changes (git commit -am 'Add some feature')\n4. Push to the branch (git push origin my-new-feature)\n5. Create a new Pull Request\n\n## Contributors\n\n- [maxfierke](https://github.com/maxfierke) Max Fierke - creator, maintainer\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxfierke%2Ffincher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxfierke%2Ffincher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxfierke%2Ffincher/lists"}