{"id":16759561,"url":"https://github.com/cyrildever/feistel","last_synced_at":"2025-10-14T15:07:19.680Z","repository":{"id":53495850,"uuid":"332176359","full_name":"cyrildever/feistel","owner":"cyrildever","description":"Feistel cipher implementation in Golang for format-preserving encryption","archived":false,"fork":false,"pushed_at":"2025-04-15T08:18:36.000Z","size":1049,"stargazers_count":15,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-14T15:05:05.298Z","etag":null,"topics":["algorithm","cryptography","encryption","feistel-cipher","format-preserving-encryption","fpe","xor-operation"],"latest_commit_sha":null,"homepage":"","language":"Go","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/cyrildever.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":"2021-01-23T09:49:39.000Z","updated_at":"2025-05-26T17:39:19.000Z","dependencies_parsed_at":"2024-03-27T14:47:20.569Z","dependency_job_id":"8e8208f2-1ac0-4d4a-8ff3-510503d25d00","html_url":"https://github.com/cyrildever/feistel","commit_stats":null,"previous_names":[],"tags_count":52,"template":false,"template_full_name":null,"purl":"pkg:github/cyrildever/feistel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyrildever%2Ffeistel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyrildever%2Ffeistel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyrildever%2Ffeistel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyrildever%2Ffeistel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cyrildever","download_url":"https://codeload.github.com/cyrildever/feistel/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyrildever%2Ffeistel/sbom","scorecard":{"id":314652,"data":{"date":"2025-08-11","repo":{"name":"github.com/cyrildever/feistel","commit":"d851b3bd1612e0b195b6c969695fbfb51262b708"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":0,"reason":"Found 1/23 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 9 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-17T23:56:18.736Z","repository_id":53495850,"created_at":"2025-08-17T23:56:18.736Z","updated_at":"2025-08-17T23:56:18.736Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279019293,"owners_count":26086709,"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-14T02:00:06.444Z","response_time":60,"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":["algorithm","cryptography","encryption","feistel-cipher","format-preserving-encryption","fpe","xor-operation"],"created_at":"2024-10-13T04:08:27.439Z","updated_at":"2025-10-14T15:07:19.660Z","avatar_url":"https://github.com/cyrildever.png","language":"Go","readme":"# feistel\n\n![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/cyrildever/feistel)\n![GitHub last commit](https://img.shields.io/github/last-commit/cyrildever/feistel)\n![GitHub issues](https://img.shields.io/github/issues/cyrildever/feistel)\n![GitHub license](https://img.shields.io/github/license/cyrildever/feistel)\n\nThis is a Golang library implementing the Feistel cipher for Format-Preserving Encryption (FPE).\n\n### Motivation\n\nThe main objective of this library is not to provide a secure encryption scheme but rather a safe obfuscation tool.\n\n\n### Formal description\n\nThis library operates on the concept of the Feistel cipher described in [Wikipedia](https://en.wikipedia.org/wiki/Feistel_cipher) as:\n\u003e A Feistel network is subdivided into several rounds or steps. In its balanced version, the network processes the data in two parts of identical size. On each round, the two blocks are exchanged, then one of the blocks is combined with a transformed version of the other block.\n\u003e Half of the data is encoded with the key, then the result of this operation is added using an XOR operation to the other half of the data.\n\u003e Then in the next round, we reverse: it is the turn of the last half to be encrypted and then to be xored to the first half, except that we use the data previously encrypted.\n\u003e The diagram below shows the data flow (the ${\\oplus}$ represents the XOR operation). Each round uses an intermediate key, usually taken from the main key via a generation called key schedule. The operations performed during encryption with these intermediate keys are specific to each algorithm.\n\n![](assets/400px-Feistel_cipher_diagram_en.svg.png)\n\nThe algorithmic description (provided by Wikipedia) of the encryption is as follows:\n* Let $n+1$ be the number of steps, $K_{0},K_{1},...,K_{n}$ the keys associated with each step and $F:\\Omega\\times\\mathcal{K}\\mapsto\\Omega$ a function of the $(words{\\times}keys)$ space to the $words$ space.\n* For each step $i{\\in}[0;n]$, note the encrypted word in step $i,m_{i}=L_{i}||R_{i}$:\n  * $L_{i+1}=R_{i}$\n  * $R_{i+1}=L_{i}{\\oplus}F(L_{i},K_{i})$\n* $m_{0}=L_{0}||R_{0}$ is the unciphered text, $m_{n+1}=L_{n+1}||R_{n+1}$ is the ciphered word. \n\nThere is no restriction on the $F$ function other than the XOR operation must be possible. For simplicity, we will choose $L_1$ of the same size as $R_1$ and the function $F$ shall transform a word of length $k$ into a word of length $k$ (and this for all $k$).\n\n\n### Usage\n\n```\ngo get github.com/cyrildever/feistel\n```\n\nTo get an obfuscated string from a source data using an automatic key generation from SHA-256 hashing function at each round, first instantiate a `Cipher` object, passing it a base key and a number of rounds.\nThen, use the `Encrypt()` method with the source data as argument. The result will be a byte array.\nTo ensure maximum security, I recommend you use a 256-bit key or longer and a minimum of 10 rounds.\n\nThe decryption process uses the obfuscated byte array and pass it to the `Decrypt()` method of the `Cipher`.\n\n```golang\nimport \"github.com/cyrildever/feistel\"\n\nsource := \"my-source-data\"\n\n// Encrypt\ncipher := feistel.NewCipher(\"some-32-byte-long-key-to-be-safe\", 10)\nobfuscated, err := cipher.Encrypt(source)\n\n// Decrypt\ndeciphered, err := cipher.Decrypt(obfuscated)\n\nassert.Equal(t, deciphered, source)\n```\n_NB: This is the exact replica of my Typescript implementation (see below)._\n\nYou may also use your own set of keys through a `CustomCipher` instance, eg.\n```golang\nkeys := []string{\n  \"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\",\n  \"9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba\",\n  \"abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789\",\n}\ncipher = feistel.NewCustomCipher(keys)\n```\nIn that case, the number of rounds depends on the number of provided keys.\n\nFinally, you might want to use the latest cipher, providing true format-preserving encryption for strings:\n```golang\nimport \"github.com/cyrildever/feistel/common/utils/hash\"\n\ncipher = feistel.NewFPECipher(hash.SHA_256, \"some-32-byte-long-key-to-be-safe\", 128)\n\nobfuscated, err := cipher.EncryptString(source)\n\nstr := obfuscated.String()\nassert.Equal(t, len([]rune(str)), len(source)) // The source and the obfuscated result have the same number of characters\n\nascii := obfuscated.String(true)\nassert.Equal(t, len(ascii), len(source)) // You must use the `true` argument to the String() method to be sure of that equality in Go (see below)\n\nassert.DeepEqual(t, len(obfuscated.Bytes()), len(source))\n```\n\nAs stated in the example above, the result of the cipher's `Encrypt()` method is a `Base256Readable` object.\nThe `String()` method of the latter uses a special 256 charset (see [here](common/utils/base256/readable.go)) which may result in the use of characters that are more than one-byte encoded, thus resulting in an unequality in the results when simply using the `len()` function.\nBut the underlying byte slice is of correct length, as well as the number of runes, ie. the number of characters to display.\n\nSo, for example, you should always use the `Bytes()` method of the result to write bytes directly to your files, instead of the `String()` method which should only be used when displaying (to a screen, to stdout, ...) or use `String(true)` with the risk of having to print unreadable characters if the underlying bytes don't have values within the 33 to 126 range.\n\nRegarding the equality, keep in mind that this is due to the fact that the `len()` function in Go doesn't actually count the number of characters of a string but the length of its underlying byte slice. If the string uses characters that is multiple-byte encoded, then the `len()` function won't return the correct number of actual characters.\n\n**IMPORTANT:** Due to the way the Feistel cipher operates, a word formed of a single character encoded on a single-byte (like `a` for example) is not modified when using the `Encrypt()` or `EncryptString()` methods.\n\n\n### Other implementations\n\nFor those interested, I also made two other implementations of these ciphers:\n* In [Typescript](https://github.com/cyrildever/feistel-cipher) for the browser;\n* In [Scala](https://github.com/cyrildever/feistel-jar) for the JVM;\n* In [Python](https://github.com/cyrildever/feistel-py).\n\nI also created a special library for redacting classified documents using the new FPE cipher. Feel free to [contact me](mailto:cdever@edgewhere.fr) about it.\n\n\n### Specific development\n\nI mainly use this library to manipulate text files, ie. strings. But, because the \"Format\" word in the FPE acronym could have different meanings, I've implemented an extra feature for the `FPECipher`: the possibility to preserve the _visible_ format when the input is a number, ie. if you use a 9-digit number, you could get a 9-digit number from the `EncryptNumber()` method (see below for padding options and restrictions to numbers lower than `256`).\n\n```golang\nsource := 123456789 // 9 digits\ncipher := feistel.NewFPECipher(hash.SHA_256, \"some-32-byte-long-key-to-be-safe\", 128)\n\nobfuscated, _ := cipher.EncryptNumber(uint64(source))\nassert.Equal(t, obfuscated.Uint64(), uint64(22780178))\nassert.Equal(t, obfuscated.ToNumber(), \"22780178\") // Only 8 digits\n\nassert.Equal(t, obfuscated.ToNumber(9), \"022780178\") // To print 9 digits like the source\n\ndeobfuscated, _ := cipher.DecryptNumber(obfuscated)\nassert.Equal(t, deobfuscated, uint64(source))\n```\n\nAs you can see, it means that the returned `Readable` type embeds two new methods to retrieve such results:\n- The `Uint64()` method which returns the integer value (the eventual sign is left to its own devices);\n- The `ToNumber()` method which returns its stringified version, eventually zero-padded to match the minimum length passed as argument (this could be useful to preserve for sure the number of digits to print, as the encryption through the Feistel cipher may result in a smaller number than the original).\n\n_NB: You might want to use the [`NumberToReadable()`](common/utils/base256/readable.go) function when using the ciphered number for decryption._\n\n**IMPORTANT:** Due to the way the Feistel cipher operates, numbers below 256 (ie. only one-byte long) can't preserve the length when using the `EncryptNumber()` method. If length matters, consider using `EncryptString()` instead.\n\nShould you want to use a number with value higher than the accepted max `uint64` value by Golang (`18446744073709551615`) or a floating number, you probably want to use splitting strategies. For example, split it in two numbers that respect the maximum boundaries of a large integer or use both parts (integer and decimal) of the float but not the decimal point itself and rebuild the number afterwards.\n\n\n### White papers\n\nI wrote two white papers to finally make it a fully FPE scheme:\n* the [original one](documentation/src/latex/feistel_whitepaper.pdf) which provided an \"almost\" format-preserving encryption;\n* the [lastest one](documentation/src/latex/fpe_whitepaper.pdf) which elaborates on this first one to push the algorithm towards true format-preserving encryption for strings.\n\n\n### License\n\nThis module is distributed under a MIT license. \\\nSee the [LICENSE](LICENSE) file.\n\n\n\u003chr /\u003e\n\u0026copy; 2019-2025 Cyril Dever. All rights reserved.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyrildever%2Ffeistel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcyrildever%2Ffeistel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyrildever%2Ffeistel/lists"}