{"id":28113221,"url":"https://github.com/atlassian/1time","last_synced_at":"2026-01-14T02:50:55.958Z","repository":{"id":46558383,"uuid":"512548362","full_name":"atlassian/1time","owner":"atlassian","description":"Lightweight, thread-safe Java/Kotlin TOTP (time-based one-time passwords) and HOTP generator and validator for multi-factor authentication valid for both prover and verifier based on shared secret","archived":false,"fork":false,"pushed_at":"2025-12-16T14:28:43.000Z","size":1159,"stargazers_count":90,"open_issues_count":9,"forks_count":5,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-12-30T05:09:16.054Z","etag":null,"topics":["hotp","mfa","rfc-6238","rfc4226","totp"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/atlassian.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2022-07-10T21:59:01.000Z","updated_at":"2025-11-25T00:18:14.000Z","dependencies_parsed_at":"2026-01-07T00:13:08.695Z","dependency_job_id":null,"html_url":"https://github.com/atlassian/1time","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/atlassian/1time","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atlassian%2F1time","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atlassian%2F1time/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atlassian%2F1time/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atlassian%2F1time/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/atlassian","download_url":"https://codeload.github.com/atlassian/1time/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atlassian%2F1time/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408777,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"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":["hotp","mfa","rfc-6238","rfc4226","totp"],"created_at":"2025-05-14T05:01:33.752Z","updated_at":"2026-01-14T02:50:55.947Z","avatar_url":"https://github.com/atlassian.png","language":"Kotlin","funding_links":[],"categories":["安全"],"sub_categories":[],"readme":"# Atlassian 1time\n[![Atlassian license](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](CONTRIBUTING.md)\n\nJava/Kotlin lightweight implementation of RFC-6238 and RFC-4226 to generate and validate time-based one-time passwords (TOTP).\n\nCommunity\n---------\n* Get an invite to the Slack channel by filling in [this form](https://forms.gle/iAtMVmtaZoszUH7EA).\n\n## Maven / Gradle dependency\n\nCheck the latest package at Maven central on: https://mvnrepository.com/artifact/com.atlassian/onetime.\n\n### Maven:\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.atlassian\u003c/groupId\u003e\n    \u003cartifactId\u003eonetime\u003c/artifactId\u003e\n    \u003cversion\u003e2.2.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Gradle:\n```kotlin\nimplementation(\"com.atlassian:onetime:2.2.0\")\n```\n\n### SBT: \n```scala\nlibraryDependencies += \"com.atlassian\" % \"onetime\" % \"2.2.0\"\n```\n\n## Quick start\n\nThis library is suitable for both prover and verifier. \n\n### Prover\n\nAs described in RFC-6238, the prover is the user trying to authenticate and submitting the generated TOTP to the verifier.\n\nAn example is: The user is trying to access an online service and is required to input the TOTP after authenticating with username and password. \nThe user will need an application to generate such TOTP.\n\nTo generate the current TOTP for a given user's secret, do:\n\nAssuming the user's enrollment has a base 32 encoded secret key: `ZIQL3WHUAGCS5FQQDKP74HZCFT56TJHR`\n\nWe can then generate the TOTP for the current time step:\n\n```kotlin\nval secret = TOTPSecret.fromBase32EncodedString(\"ZIQL3WHUAGCS5FQQDKP74HZCFT56TJHR\")\nval totpGenerator: TOTPGenerator = TOTPGenerator()\nval totp = totpGenerator.generateCurrent(secret) //TOTP(value=123456)\n```\nYou can now present the value of `totp` to the user.\n\n### Verifier\n\nAs described in RFC-6238, the verifier is the system validating the user's credentials, including multi-factor authentication. After the system validates the username/password, it then verifies the user's TOTP input.\n\nBut before being able to verify a user's TOTP, we need to enrol the user.\n\nUpon an enrolment request, the system will\n- Generate a secret and store it for the user and other information the system deems necessary.\n- Generate a TOTP URI for further QR code generation that will allow the user to enrol with mobile apps such as Google Authenticator\n\n\n#### Generating a secret\n\nThis library provides two out of the box secret generators:\n\n- ASCII range secret generator\n- Random secret generator\n\nBut it is not limited to that. If you want to use a more complex secret generator using, for example [GCP](https://cloud.google.com/kms/docs/generate-random) or [AWS](https://docs.aws.amazon.com/kms/latest/APIReference/API_GenerateRandom.html), you'll benefit from implementing a non-blocking secret generator.\n\n1time provides 3 examples for this sake:\n\n- Blocking generator\n- Generator that returns `CompletableFuture`\n- Generator using Kotlin coroutines and thus relying on continuations (CPS)\n\n##### Blocking generator\n\n```kotlin\nfun interface SecretProvider {\n  fun generateSecret(): TOTPSecret\n}\n```\n\nThis is the simplest generator that doesn't rely on any complex generator. We provide two examples ready to use, ASCII and random:\n\n```kotlin\nclass AsciiRangeSecretProvider : SecretProvider {\n\n  companion object {\n    private val ASCII_RANGE: CharRange = (' '..'z')\n\n    fun generateSecret() = TOTPSecret(\n      (1..20).map { ASCII_RANGE.random() }.joinToString(\"\").toByteArray()\n    )\n  }\n\n  override fun generateSecret() = AsciiRangeSecretProvider.generateSecret()\n}\n```\n\n```kotlin\nclass RandomSecretProvider : SecretProvider {\n\n  companion object {\n    fun generateSecret() =\n      SecureRandom().let {\n        val byteArray = ByteArray(20)\n        it.nextBytes(byteArray)\n        TOTPSecret(byteArray)\n      }\n  }\n\n  override fun generateSecret() = RandomSecretProvider.generateSecret()\n}\n```\n\nTo use any of these, just do:\n\n```kotlin\nval secret = RandomSecretProvider.generateSecret()\n```\n\n##### Completable future generator\n\nThis is particularly useful when dealing with a complex, external resource for better random generation and when using Java.\n\n```kotlin\nfun interface AsyncSecretProvider {\n  fun generateSecret(): CompletableFuture\u003cTOTPSecret\u003e\n}\n```\n\nWe provide examples of these too, e.g.:\n\n```kotlin\nclass AsyncAsciiRangeSecretProvider : AsyncSecretProvider {\n\n  override fun generateSecret(): CompletableFuture\u003cTOTPSecret\u003e =\n    CompletableFuture.supplyAsync {\n      AsciiRangeSecretProvider.generateSecret()\n    }\n}\n```\n\nTo use any of these, just do:\n\n```kotlin\nval secret: CompletableFuture\u003cTOTPSecret\u003e = AsyncAsciiRangeSecretProvider().generateSecret()\n//handle completable future\n```\n\n##### Continuation (CPS) generator\n\nIf you are using Kotlin, this should be the default approach. Even if dealing with external random generator, it will be non-blocking by default:\n\n```kotlin\nfun interface CPSSecretProvider {\n  suspend fun generateSecret(): TOTPSecret\n}\n```\n\nWe provide examples of these too:\n\n```kotlin\nclass CPSAsciiRangeSecretProvider : CPSSecretProvider {\n\n  override suspend fun generateSecret(): TOTPSecret =\n    AsciiRangeSecretProvider.generateSecret()\n}\n```\n\nTo use any of these, just do:\n\n```kotlin\nrunBlocking { \n  val secret: TOTPSecret = CPSAsciiRangeSecretProvider().generateSecret()\n  // use secret\n}\n``` \nOr in your `suspend` functions without blocking.\n\n\n#### Generating a TOTP URI\n\n```kotlin\n\nval totpUri = service.generateTOTPUrl(\n    secret, ////NIQXUILREVGHIUKNORKHSJDHKMWS6UTY\n    EmailAddress(\"jsmith@acme.com\"),\n    Issuer(\"Acme Co\")\n)\ntotpUri // otpauth://totp/Acme+Co:jsmith%40acme.com?secret=NIQXUILREVGHIUKNORKHSJDHKMWS6UTY\u0026issuer=Acme+Co\u0026algorithm=SHA1\u0026digits=6\u0026period=30\n```\n\nOnce generated the totpUrl, the system can use any library of choice to generate the TOTP QR code. For example:\n\n\u003cimg height=\"400\" src=\"docs/img/totp-qr.png\" title=\"TOTP QR code\" width=\"400\" alt=\"totp-qr\"/\u003e\n\n#### Verifying enrolled user\n\nAfter the user has successfully enrolled, both the user and the system will share the secret.\n\nWhen the user (prover) is trying to authenticate, the system (verifier) will validate the user's TOTP input.\n\nIn order to do so, do:\n\n```kotlin\nval userInput: TOTP = TOTP(\"123456\") //TOTP from user input\nval result = service.verify(\n  userInput, \n  secret //NIQXUILREVGHIUKNORKHSJDHKMWS6UTY\n)\n```\n\nThe obtained `result` can be either\n- `TOTPVerificationResult.InvalidOtp`: The provided TOTP is invalid for the secret and default configuration\n- `TOTPVerificationResult.Success`: The provided TOTP is valid for the secret and default configuration. In addition, the index of the matching TOTP is provided in the `Success` data class.\n\n#### Success - index of matching TOTP\n\nWhen the verification is successful, the type of `result` will be `TOTPVerificationResult.Success`. This data class contains an index value:\n\n```kotlin\ndata class Success(val index: Int) : TOTPVerificationResult()\n```\n\nThe index value indicates the window on which the TOTP was found. More on windows [here](#prover-clock-delays-and-verifier-allowed-windows).\n\nWhen the verifier allows, for instance, 1 past window, one present window (mandatory) and one future window, upon successful verification, the index value will be:\n- -1 when successful on past window or T - 1\n- 0 when successful on current window or T\n- 1 when successful on future window or T + 1\n\nWhere T is the time step. This is calculated by current epoch time in seconds divided by (floor) time step size (30 seconds by default). \n\nThis index is useful if your solution wants to implement [resynchronization](https://datatracker.ietf.org/doc/html/rfc6238#section-6).\n\nFrom RFC-6238:\n\u003e Upon successful validation, the\nvalidation server can record the detected clock drift for the token\nin terms of the number of time steps.  When a new OTP is received\nafter this step, the validator can validate the OTP with the current\ntimestamp adjusted with the recorded number of time-step clock drifts\nfor the token.\n\nBy keeping track of the successful indexes per user device, the verifier could:\n- Adjust the allowed windows to cater for prover's clock drifts. \n- Notify the user that the device's clock is drifting and could end up in blocking access to the account, asking the user to re-enrol.\n\n### Tuning configuration\n\nThe previous instructions rely on a set of defaults.\n\n#### Default values for TOTP Generator\n\nTOTP Generator constructor takes the following parameters and corresponding default values:\n\n```kotlin\nval totpGenerator: TOTPGenerator = TOTPGenerator(\n  clock = Clock.systemUTC(),\n  startTime = 0,\n  timeStepSeconds = 30,\n  otpLength = OTPLength.SIX,\n  digest = HMACDigest.SHA1\n)\n```\n\n- `startTime`: Epoch second from which the counter starts. By default is 0, which points to Thursday, January 1, 1970 0:00:00 GMT.\n- `timeStepSeconds`: The duration of a TOTP. By default, is 30 seconds.\n- `otpLength`: TOTP digit count. By default, is 6 digits.\n- `digest`: HMAC algorithm. By default, is SHA1.\n\n:warning:  We recommend **not to change** these default values since apps such as Google Authenticator will **ignore** the custom values provided in the totp url and default to the agreed standards. \n\nSee Google Authenticator handling of these parameters: \n[Key-Uri-Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format)\n\n#### Default values TOTP service\n\nThese defaults apply for the verifier.\n\nTOTP Service constructor takes the following parameters and corresponding default values:\n\n```kotlin\nval service = DefaultTOTPService(\n  totpGenerator = TOTPGenerator(),\n  totpConfiguration = TOTPConfiguration()\n)\n```\n- `totpGenerator`: The default generator as described before. You could create different instances of `DefaultTOTPService` with different configurations of `totpGenerator`. This may be useful if your solution stores detailed configuration such as TOTP length, time step size and hashing algorithm per enrolment instead of a global configuration. If enrolment will allow the user to choose these parameters, then make sure the prover app supports such configuration.\n- `totpConfiguration`: This configuration allows to tune how the verification behaves in terms of accepted windows and how to generate secret keys.\n  - `secretProvider`: By default we provide `AsciiRangeSecretProvider`. This is a random key generator that generates 20 bytes in the ASCII range. This is useful for when you need the raw secret value to be composed of printable characters. You can provide your own Secret provider by extending `SecretProvider` functional interface.\n  - `allowedPastSteps`: By default, this is 0. As specified by [RFC-6238](https://datatracker.ietf.org/doc/html/rfc6238#section-5.2), the verifier should accept the TOTP from the previous time step to cater for possible network delays. If you want to accept past windows as valid, increase this number. \n  - `allowedFutureSteps`: By default, this is 0. Same as `allowedPastSteps` but to cater for possible clock drifts in which the prover's clock is slightly in the future relative to the verifier's. If you want to accept future time windows as valid, increase this number. More on time windows on the [next section](#prover-clock-delays-and-verifier-allowed-windows).\n\n## Prover clock delays and verifier allowed windows\n\nWhen both the prover's and verifier's clocks are in sync, this is what it would look like when the verifier allows one past window and 1 future window:\n\n![img.png](docs/img/clocks-synced.png)\n\nThe current TOTP for the prover is what the server sees in the middle of the window, since it now accepts TOTPs from 3 time windows; current window, the previous and the next.\n\nBut when the prover's clock is delayed relative to the verifiers, this is what it would look like:\n\n![img.png](docs/img/clock-delay.png)\n\nWhen the verifier generates the list of 3 TOTPs for the user, the one the prover provided is already in the past. \n\nThis can also happen due to network delay. Say the prover submitted the TOTP close to the end of the time window of 30 seconds and given network delays, upon calculation on the verifier, that TOTP value is no longer the current, but the previous. In these cases it makes sense to deem that TOTP as valid to prevent the prover from having to resubmit the TOTP.\n\nSimilarly to the prover's clock being delayed, it can also happen that it is slightly in the future relative to verifier's clock. The opposite to the diagram above would happen and upon calculation on verifier's side, the TOTP provided will be the next to current. It also makes sense to deem that attempt as valid.\n\n## Contributions\n\nContributions to 1time are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.\n\n## License\n\nCopyright (c) 2022 Atlassian and others.\nApache 2.0 licensed, see [LICENSE](LICENSE) file.\n\n\u003cbr/\u003e \n\n\n[![With â¤ï¸ from Atlassian](https://raw.githubusercontent.com/atlassian-internal/oss-assets/master/banner-cheers.png)](https://www.atlassian.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatlassian%2F1time","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fatlassian%2F1time","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatlassian%2F1time/lists"}