{"id":13507674,"url":"https://github.com/rubencaro/cipher","last_synced_at":"2026-02-21T18:03:15.829Z","repository":{"id":23007342,"uuid":"26358068","full_name":"rubencaro/cipher","owner":"rubencaro","description":"Elixir crypto library to encrypt/decrypt arbitrary binaries","archived":false,"fork":false,"pushed_at":"2021-06-04T14:04:59.000Z","size":155,"stargazers_count":61,"open_issues_count":0,"forks_count":23,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-10-07T00:32:23.683Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/rubencaro.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}},"created_at":"2014-11-08T11:10:39.000Z","updated_at":"2024-09-27T14:25:36.000Z","dependencies_parsed_at":"2022-08-21T18:10:48.756Z","dependency_job_id":null,"html_url":"https://github.com/rubencaro/cipher","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/rubencaro/cipher","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubencaro%2Fcipher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubencaro%2Fcipher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubencaro%2Fcipher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubencaro%2Fcipher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rubencaro","download_url":"https://codeload.github.com/rubencaro/cipher/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rubencaro%2Fcipher/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29689644,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T15:51:39.154Z","status":"ssl_error","status_checked_at":"2026-02-21T15:49:03.425Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":[],"created_at":"2024-08-01T02:00:37.658Z","updated_at":"2026-02-21T18:03:15.525Z","avatar_url":"https://github.com/rubencaro.png","language":"Elixir","funding_links":[],"categories":["Cryptography","Frameworks and Libs"],"sub_categories":["Elixir"],"readme":"# Deprecated\n\nCipher is currently being phased out and is no longer under activate maintenence.\nWe suggest you use an alternative for URL signing and validation such as [JWT](https://jwt.io/) or something else.\n\nFor more information on why this decision was taken, feel free to refer to [this issue](https://github.com/rubencaro/cipher/issues/22).\n\n# Cipher\n\n[![Build Status](https://api.travis-ci.org/rubencaro/cipher.svg)](https://travis-ci.org/rubencaro/cipher)\n[![Hex Version](http://img.shields.io/hexpm/v/cipher.svg?style=flat)](https://hex.pm/packages/cipher)\n[![Hex Version](http://img.shields.io/hexpm/dt/cipher.svg?style=flat)](https://hex.pm/packages/cipher)\n\nElixir crypto library to encrypt/decrypt arbitrary binaries. It uses [Erlang Crypto](http://www.erlang.org/doc/man/crypto.html), so it's not a big deal. Mostly a collection of helpers wrapping it.\n\nThis library allows us to use a crypted key to validate signed requests, with a cipher compatible with [this one](https://gist.github.com/rubencaro/9545060#file-gistfile3-ex). This way it can be used from Python, Ruby or Elixir apps.\n\n`Cipher` is only meant for that. **Not for security**. For applications that need any level of security I would recommend using a good implementation of JWT.\n\n## Use\n\nJust add `{:cipher, \"\u003e= 1.4.0\"}` to your `mix.exs`.\n\nThen add your keys to `config.exs`, **they are needed to compile `Cipher`**:\n```elixir\nconfig :cipher, keyphrase: \"testiekeyphraseforcipher\",\n                ivphrase: \"testieivphraseforcipher\",\n                magic_token: \"magictoken\"\n```\n\nYou can provide different keys at runtime by using `Application.put_env/3`.\n\nThen you may use any of the given helpers:\n\n## Encrypt/Decrypt binaries\n\nNow you can use bare `encrypt/1` and `decrypt/1`:\n```elixir\n\"secret\"\n|\u003e Cipher.encrypt  # \"KSHHdx0uyveYGY5PHqLAKw%3D%3D\"\n|\u003e Cipher.decrypt  # \"secret\"\n```\n\n### Decryption errors\n\nWhen you decrypt non-valid strings you can get two kinds of errors:\n\n* `{:error, \"Could not decode string 'yourstring'...\"}` if your string was tampered or wrongly transferred.\n* `{:error, \"Could not decrypt string 'yourstring'...\"}` if your string was encrypted using different keys. Maybe some edge cases of tampering too.\n\n## Cipher/Parse JSON\n\n`cipher/1` and `parse/1`. Just as `encrypt/1` and `decrypt/1` but for JSON.\n\n```elixir\n%{\"hola\": \" qué tal ｸｿ\"}\n|\u003e Cipher.cipher  # \"qW0Voj3h4nglx4NPy8aLXVY5ze5V3OBu5IoaQTMUUbU%3D\"\n|\u003e Cipher.parse  #  {:ok, %{\"hola\" =\u003e \" qué tal ｸｿ\"}}\n```\n\n## Sign/Validate a URL\n\nHere you use `sign_url/2` and `validate_signed_url/1`.\n\n`sign_url` will add a `signature` parameter to the end of the query string. It's a crypted hash based on the given path.\n\n```elixir\n\"/bla/bla?p1=1\u0026p2=2\"\n|\u003e Cipher.sign_url  # \"/bla/bla?p1=1\u0026p2=2\u0026signature=4B6WOiuD9N39K7p%2BnqNIljGh5F%2F%2BnHRQGZC9ih%2Bh%2BHGZc8Tz0KdRJXC%2B5M%2B8%2BHZ2mAXPh3jQcSRieTq4dGm5Ng%3D%3D\"\n```\n\n`validate_signed_url` must be given an url with the `signature` parameter on the query string just as `sign_url` returned it. It will pop it, and validate that it corresponds with the rest of the URL.\n\n```elixir\n\"/bla/bla?p1=1\u0026p2=2\u0026signature=4B6WOiuD9N39K7p%2BnqNIljGh5F%2F%2BnHRQGZC9ih%2Bh%2BHGZc8Tz0KdRJXC%2B5M%2B8%2BHZ2mAXPh3jQcSRieTq4dGm5Ng%3D%3D\"\n|\u003e Cipher.validate_signed_url  # {:ok, %{\"md5\" =\u003e \"86e359da7ab4886f3525ac2b9c5edc5b  613146\"}}\n```\n\nAny changes to the signed URL `\"/bla/bla?p1=1\u0026p2=2\"` will return `{:error, reason}` when validated.\n\n```elixir\n\"/bla/bla?p1=1\u0026p2=3\u0026signature=4B6WOiuD9N39K7p%2BnqNIljGh5F%2F%2BnHRQGZC9ih%2Bh%2BHGZc8Tz0KdRJXC%2B5M%2B8%2BHZ2mAXPh3jQcSRieTq4dGm5Ng%3D%3D\"\n|\u003e Cipher.validate_signed_url  # {:error, \"Checksum did not match given base '/bla/bla?p1=1\u0026p2=3'.\"}\n```\n\n### Denied params\n\nYou can choose to sign a URL but then add some parameters to the query string that may not be signed, such as a `cachebuster`.\n\nFor that you can use `sign_url/2`, which accepts a payload to be included on the crypted signature. If you add a `deny` list, then any parameter on that list will be rejected.\n\n```elixir\nsigned = \"/bla/bla?p1=1\u0026p2=2\" |\u003e Cipher.sign_url(deny: [\"p1\"])\n\n\"#{signed}\u0026cachebuster=123456789\" |\u003e Cipher.validate_signed_url\n#   {:ok,\n#   %{\"deny\" =\u003e [\"p1\"],\n#     \"md5\" =\u003e \"86e359da7ab4886f3525ac2b9c5edc5b  837505\"}}\n\n\"#{signed}\u0026cachebuster=123456789\u0026p1=parm\"\n|\u003e Cipher.validate_signed_url  # {:error, \"Parameter 'p1=parm' is not allowed by given signature. Denials: [\\\"p1\\\"]\"}\n\n```\n\n### Concealed params\n\nNote you can use `sign_url/2` to pass any data within the signature itself, just as you do with the `deny` list. Any payload will be returned by `validate_signed_url/1`.\n\n```elixir\nsigned = \"/bla/bla?p1=1\" |\u003e Cipher.sign_url(mydata: \"yes, any data\")\nsigned |\u003e Cipher.validate_signed_url\n#  {:ok,\n#   %{\"md5\" =\u003e \"eacac4224aef3bfabee309ee2f95c1e8  176303\",\n#     \"mydata\" =\u003e \"yes, any data\"}}\n```\n\nIf you want to pass cipher data on your URLs you could also use straight `cipher/1` and `parse/1`.\n\n## Sign/Validate body\n\nThe same as signing a complete URL with query string, but for PUT/POST requests, where the signed data is in the body.\n\nHelpers are `sign_url_from_body/2` and `validate_signed_body/1`. They put and validate the signature on the query string, so the body is untouched.\n\n```elixir\nurl = \"/bla/bla\"\nbody = Poison.encode! %{\"hola\": \" qué tal ｸｿ\"}\nsigned = Cipher.sign_url_from_body(url, body, deny: [\"cb\"])\n# \"/bla/bla?signature=HdlsREqEP9hJmP94...\"\n{:ok, _} = \"#{signed}\" |\u003e Cipher.validate_signed_body(body)\n{:error, _} = \"#{signed}\u0026cb=123456\" |\u003e Cipher.validate_signed_body(body)\nassert {:ok, _} = \"#{signed}\u0026other=123456\" |\u003e Cipher.validate_signed_body(body)\n```\n\n### Mapped body\n\nWhen the body is to be validated after parse time (as in a simple `Plug` pipeline, where body can be read only once, and it is read by `Plug.Parsers`) you should sign it using `sign_url_from_mapped_body/2`, passing the body as a `Map`. Like this:\n\n```elixir\nurl = \"/bla/bla\"\nraw_body = %{\"hola\": \" qué tal ｸｿ\"}\nbody = raw_body |\u003e Poison.encode! |\u003e Poison.decode!\nsigned = Cipher.sign_url_from_mapped_body(url, raw_body, deny: [\"cb\"])\n# \"/bla/bla?signature=HdlsREqEP9hJmP94...\"\n{:ok, _} = \"#{signed}\" |\u003e Cipher.validate_signed_body(body)\n{:error, _} = \"#{signed}\u0026cb=123456\" |\u003e Cipher.validate_signed_body(body)\nassert {:ok, _} = \"#{signed}\u0026other=123456\" |\u003e Cipher.validate_signed_body(body)\n```\n\n## Magic Token\n\nThis is a master signature. If you put this binary as `signature` on your url, then it will always validate. This is useful for development, debugging, private network use, etc. You put your chosen `magic_token` on your `config.exs` and you are good to go.\n\n```elixir\n\"/bla?any=thing\u0026signature=mymagictoken\"\n|\u003e Cipher.validate_signed_url  # {:ok, %{}}\n```\n\n## Use with Plug applications\n\nCipher provides `ValidatePlug`, a plug that uses Cipher to validate signatures and halt with 401 when they are not valid.\nUse it as any other plug:\n\n```elixir\nplug Cipher.ValidatePlug\n```\n\n### Options for `ValidatePlug`\n\n1. `error_callback`\n2. `test_mode`\n\nYou can pass an `error_callback` that will be called right before sending the 401 response. This callback is meant to let the user do things like logging when validation fails. You should not call `send_resp` or `halt` over the `conn`, as that will already be done by the plug.\n```elixir\n# ...\nplug Cipher.ValidatePlug, error_callback: \u0026MyApp.my_validation_error_logging_callback/2\n# ...\n\ndef my_validation_error_logging_callback(conn, error) do\n  # Do something with the `error` message and the `conn`, just like:\n  Logger.info(error)\n  # right before the plug halts with 401\nend\n\n# ...\n```\n\nYou can also pass `test_mode` as an option (which is `false` by default).\nIf set to `true`, it will **not** halt the Plug pipeline and will simply continue.\n\nThis can be useful in conjunction with `error_callback` where you just log requests whose validation has failed, but continue anyway.\n\n```elixir\n  plug(\n    Cipher.ValidatePlug,\n    test_mode: true,\n    error_callback: \u0026MyApp.my_validation_error_logging_callback/2\n  )\n```\n\n### Notes\n\nNote that for body signature validations (those required by POST, PUT, etc.) this plug requires that the signature is made using `Cipher.sign_url_from_mapped_body`. This is due to the way `Plug` parses the request body. The body can be read only once, and it is already read by the `Plug.Parsers` plug. By the time it gets to the `ValidatePlug` it has already been parsed to a `Map`, so the signature must have been done over the mapped structure of data instead of the plain text encoded body.\n\n\n## TODOs\n\n* Add large body signing\n* Separate package for `ValidatePlug`\n\n## Changelog\n\n### 1.4.0\n\n* Add the possibility of giving different keys at runtime by using `Application.put_env/3`.\n\n### 1.3.4\n\n* Adhere closer to the PKCS#7 implementation described in RFC 5652 (pull request #16)\n\n### 1.3.3\n\n* Fix incompatibility deciphering previous versions's ciphers\n\n### 1.3.2\n\n* Add links to source on generated docs\n* Require ivphrase \u003e= 16 bytes\n\n### 1.3.1\n\n* Support Poison 3.x\n\n### 1.3.0\n\n* Add `test_mode` option to ease plug testing\n\n### 1.2.4\n\n* Improve error messages\n\n### 1.2.3\n\n* Fix some bugs\n* Remove Elixir 1.4 warnings\n\n### 1.2.0\n\n* Add denied params, remove ignored ones.\n\n### 1.1.1\n\n* Fix `plug` dependency\n\n### 1.1.0\n\n* Add `ValidatePlug`\n* Add mapped body signing\n\n### 1.0.5\n\n* Fix end line character replace on incoming signatures\n\n### 1.0.4\n\n* Fix app name on `env` helper\n\n### 1.0.3\n\n* Fix bug when ignoring multiple params\n\n### 1.0.2\n\n* Fix [#3](https://github.com/rubencaro/cipher/issues/3), [#4](https://github.com/rubencaro/cipher/issues/4)\n\n### 1.0.1\n\n* Fix [#2](https://github.com/rubencaro/cipher/issues/2)\n\n### 1.0.0\n\n* First stable release\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frubencaro%2Fcipher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frubencaro%2Fcipher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frubencaro%2Fcipher/lists"}