{"id":13726066,"url":"https://github.com/marcoonroad/twostep","last_synced_at":"2025-05-07T21:31:04.180Z","repository":{"id":42710288,"uuid":"296164141","full_name":"marcoonroad/twostep","owner":"marcoonroad","description":"HOTP and TOTP algorithms for 2-step verification (for OCaml). :clock10: :closed_lock_with_key: :1234: :camel:","archived":false,"fork":false,"pushed_at":"2023-02-18T08:35:06.000Z","size":149,"stargazers_count":20,"open_issues_count":1,"forks_count":3,"subscribers_count":2,"default_branch":"stable","last_synced_at":"2024-11-14T16:45:01.618Z","etag":null,"topics":["2-factor-authentication","2-step-verification","authentication","cryptography","infosec","ocaml","ocaml-library","one-time-password","one-time-passwords","security","security-tools"],"latest_commit_sha":null,"homepage":"https://twostep.marcoonroad.dev","language":"OCaml","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/marcoonroad.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}},"created_at":"2020-09-16T22:50:20.000Z","updated_at":"2024-01-15T12:12:09.000Z","dependencies_parsed_at":"2024-01-06T02:15:13.091Z","dependency_job_id":null,"html_url":"https://github.com/marcoonroad/twostep","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcoonroad%2Ftwostep","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcoonroad%2Ftwostep/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcoonroad%2Ftwostep/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcoonroad%2Ftwostep/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcoonroad","download_url":"https://codeload.github.com/marcoonroad/twostep/tar.gz/refs/heads/stable","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252957122,"owners_count":21831438,"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","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":["2-factor-authentication","2-step-verification","authentication","cryptography","infosec","ocaml","ocaml-library","one-time-password","one-time-passwords","security","security-tools"],"created_at":"2024-08-03T01:02:51.418Z","updated_at":"2025-05-07T21:31:03.861Z","avatar_url":"https://github.com/marcoonroad.png","language":"OCaml","funding_links":[],"categories":["OCaml"],"sub_categories":[],"readme":"# twostep\n\nHOTP and TOTP algorithms for 2-step verification (for OCaml).\n\n\u003cp align=\"center\"\u003e\n\u003ca target=\"_blank\" href=\"https://github.com/marcoonroad/twostep/blob/stable/twostep.opam\"\u003e\n\u003cimg alt=\"OCaml Minimal Version\" src=\"https://img.shields.io/static/v1?label=OCaml\u0026message=%2Bv4.08.0\u0026color=orange\u0026style=flat-square\u0026logo=ocaml\" /\u003e\n\u003c/a\u003e\n\u003ca target=\"_blank\" href=\"https://github.com/marcoonroad/twostep/actions?query=workflow%3A%22Ubuntu+CI+Workflow%22+branch%3Astable\"\u003e\n\u003cimg alt=\"Ubuntu Workflow Status\" src=\"https://img.shields.io/github/actions/workflow/status/marcoonroad/twostep/ubuntu-ci.yml?branch=stable\u0026label=Ubuntu\u0026logo=github\u0026style=flat-square\" /\u003e\n\u003c/a\u003e\n\u003ca target=\"_blank\" href=\"https://github.com/marcoonroad/twostep/actions?query=workflow%3A%22Windows+CI+Workflow%22+branch%3Astable\"\u003e\n\u003cimg alt=\"Windows Workflow Status\" src=\"https://img.shields.io/github/actions/workflow/status/marcoonroad/twostep/windows-ci.yml?branch=stable\u0026label=Windows\u0026logo=github\u0026style=flat-square\" /\u003e\n\u003c/a\u003e\n\u003ca target=\"_blank\" href=\"https://github.com/marcoonroad/twostep/actions?query=workflow%3A%22MacOS+CI+Workflow%22+branch%3Astable\"\u003e\n\u003cimg alt=\"MacOS Workflow Status\" src=\"https://img.shields.io/github/actions/workflow/status/marcoonroad/twostep/macos-ci.yml?branch=stable\u0026label=MacOS\u0026logo=github\u0026style=flat-square\" /\u003e\n\u003c/a\u003e\n\u003ca target=\"_blank\" href=\"https://github.com/marcoonroad/twostep/blob/stable/LICENSE\"\u003e\n\u003cimg alt=\"Project License\" src=\"https://img.shields.io/github/license/marcoonroad/twostep?label=License\u0026logo=github\u0026style=flat-square\u0026color=blue\" /\u003e\n\u003c/a\u003e\n\u003ca target=\"_blank\" href=\"https://coveralls.io/github/marcoonroad/twostep\"\u003e\n\u003cimg alt=\"Coverage Status\" src=\"https://img.shields.io/coveralls/github/marcoonroad/twostep?label=Coverage\u0026logo=coveralls\u0026style=flat-square\" /\u003e\n\u003c/a\u003e\n\u003c/p\u003e\n\nThis project implements algorithms for 2-step verification,\nbeing the HMAC-based One-Time Password\n(see [RFC 4226](https://tools.ietf.org/html/rfc4226)) and the\nTime-based One-Time Password\n(see [RFC 6238](https://tools.ietf.org/html/rfc6238)).\n\n## Installation\n\nIf available on OPAM, try:\n\n```shell\nopam install twostep\n```\n\nOtherwise, you can install the development version of this\nproject with OPAM's `pin` command.\n\n## Usage\n\nThe authentication of 2-step verification needs prior known\nand shared secret between the client and server. If no\nsecret was sent before, you can generate this Base-32 secret\nwith:\n\n```ocaml\nlet secret: string = Twostep.TOTP.secret();; (* kinda \"A222 BBBB 3333 D5D5\" *)\n```\n\nAs an additional note, the `Twostep.TOTP.secret` function above uses\na cryptographically safe PRNG, that is, a secure source of pseudo\nrandomness. To generate an OTP code, you can use this function:\n\n```ocaml\nlet code: string = Twostep.TOTP.code ~secret:secret ();; (* kinda \"098123\" *)\n```\n\nThe function above assumes the `SHA-1` hash algorithm, `30` seconds\nas timestep/window before refreshed code, `6` digits for output\nnumber code (padded with zeros on left sometimes) and no clock\ndrifts / not-sync time between server and client (i.e, no\n30 seconds on the past or on the future).\n\nTo verify one-time codes from the client, use the following\nfunction below:\n\n```ocaml\nlet valid: bool = Twostep.TOTP.verify ~secret:secret ~code:code ();;\n```\n\nThis function assumes the same configuration of `Twostep.TOTP.code`,\nexcept for the clock drift, where `Twostep.TOTP.verify` assumes too\npast and future 30 seconds (ideal on slow connections or latency\nproblems). For the full API reference or coverage status, please refer to:\n- [Generated API docs](https://www.marcoonroad.dev/twostep/apiref/twostep/Twostep/index.html)\n- [Generated API coverage](https://www.marcoonroad.dev/twostep/apicov/index.html)\n\nYou can test this library against mobile apps such as Google\nAuthenticator or Microsoft Authenticator without any problems\n(I have tested on both as well). On any doubts to generate\na QR-code for the base-32 secret, please refer to Google\nAuthenticator's Key Uri Format (for the proper data format\non QR-code encoding/decoding):\n- [Key Uri Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format)\n\n## Security Concerns\n\nThe generated secret must be sent for the\nclient in a secure channel, such as **HTTPS/TLS**, and must\nbe stored encrypted in your servers' databases. A good\napproach is to encrypt with a KDF on the client password,\nafter you checking the client password against the strongly\nhashed version on database (prefer 256/512-bits hash algorithms\nwhenever possible, and a KDF in front of this with server's\nsalt is ideal too). So, in this approach the front app must\nsend the client password twice, during authentication and\nduring 2-step verification, and after that, erasing the\npassword persisted on front (nice UX for the client to not\ntype twice her own password).\n\nIt's recommended for the OTP authentication to be optional on\nmost cases. The end-user can opt-in this feature some time later,\nand on OTP service setup, she needs to _confirm_ that have\nconfigured things properly through the _first-time_ OTP-code\nverification. For the first-time, which means no OTP-prompt-prior-login\nuntil customer confirmation of first-time, you could use\na `confirmed` boolean (initially `false`) column aside\n`encryptedSecret` on your storage -- so this first-time confirmation\nchanges `confirmed` to `true` and then all logins would request an\nOTP code together. Keep in mind that it's just a design sketch of\nimplementation, more like an idea than actual RFC recommendation.\n\nAlso, you should track valid OTP codes sent from the end user in\na persisted storage (such as databases). This is just to avoid\n**replay attacks** once an attacker intercepts a valid OTP code from\nthe end user. Your tracking of the OTP code should be a pair\n`(otpCode, nonce)`, where `nonce` is the current interval / period\n(if using TOTP algorithm, otherwise nonce will be a HOTP counter)\nand the `otpCode` is verified / checked as valid. Keep in mind that\nyou should only track valid / verified OTP codes to not waste storage\ncosts with invalid OTP codes (i.e, codes that can't be exploited by\nreplay attacks). After such tracked pairs hit OTP expiration and are\nnot able to be exploited anymore, you can clean them from the\nunderlying tracklist storage without problems.\n\n---\n\n**Important:** This is a warning / security advice. Implement\nsuch system carefully, and if possible, with audits from external\nexperts and security teams. As a disclaimer, I'm not responsible\nfor any damages.\n\n---\n\n## Tips\n\nFor stronger / longer secrets, you can use the `~bytes` optional parameter\n(the default and minimal value is 10,\n_with the invariant that it must be divisible-by / multiple-of 5_):\n\n```ocaml\nlet secret: string = Twostep.TOTP.secret ~bytes:20 ();;\n(* kinda \"D3D3 F5F5 A2A2 3B3B GGGG 7K7K 5555 Q2Q2\" *)\n```\n\n## Remarks\n\nPull requests are welcome! Happy hacking! Hope this project can\nhelp you to solve problems.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcoonroad%2Ftwostep","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcoonroad%2Ftwostep","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcoonroad%2Ftwostep/lists"}