{"id":37020104,"url":"https://github.com/square/whorlwind","last_synced_at":"2026-01-14T02:15:44.109Z","repository":{"id":57727223,"uuid":"53430304","full_name":"square/whorlwind","owner":"square","description":"Makes fingerprint encryption a breeze.","archived":true,"fork":false,"pushed_at":"2021-04-15T21:00:34.000Z","size":280,"stargazers_count":818,"open_issues_count":7,"forks_count":62,"subscribers_count":36,"default_branch":"master","last_synced_at":"2024-04-13T17:52:17.289Z","etag":null,"topics":["crypto"],"latest_commit_sha":null,"homepage":null,"language":"Java","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/square.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-03-08T17:09:18.000Z","updated_at":"2024-04-02T14:35:59.000Z","dependencies_parsed_at":"2022-09-26T21:51:18.954Z","dependency_job_id":null,"html_url":"https://github.com/square/whorlwind","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/square/whorlwind","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/square%2Fwhorlwind","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/square%2Fwhorlwind/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/square%2Fwhorlwind/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/square%2Fwhorlwind/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/square","download_url":"https://codeload.github.com/square/whorlwind/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/square%2Fwhorlwind/sbom","scorecard":{"id":843235,"data":{"date":"2025-08-11","repo":{"name":"github.com/square/whorlwind","commit":"fdcf740e66d82c99d2e2b3ce89feff916c8a8ed0"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.7,"checks":[{"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":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: gradle/wrapper/gradle-wrapper.jar:1"],"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":"Maintained","score":0,"reason":"project is archived","details":["Warn: Repository is archived."],"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":"Code-Review","score":6,"reason":"Found 19/28 approved changesets -- score normalized to 6","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":"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":"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":"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":"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"}},{"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.txt:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE.txt: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":"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":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 21 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"}}]},"last_synced_at":"2025-08-23T20:55:15.000Z","repository_id":57727223,"created_at":"2025-08-23T20:55:15.000Z","updated_at":"2025-08-23T20:55:15.000Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408711,"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":["crypto"],"created_at":"2026-01-14T02:15:42.920Z","updated_at":"2026-01-14T02:15:44.082Z","avatar_url":"https://github.com/square.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"Whorlwind\n=========\n\nA reactive wrapper around Android's fingerprint API that handles encrypting/decrypting sensitive\ndata using a fingerprint.\n\n**DEPRECATED:** Google has released the [AndroidX Biometric Library][biometric] which supports more\nforms of authentication than fingerprint and should be relied on going forward. See\n[the announcement][announcement] for more information.\n\n[biometric]: https://developer.android.com/jetpack/androidx/releases/biometric\n[announcement]: https://android-developers.googleblog.com/2019/10/one-biometric-api-over-all-android.html\n\nUsage\n-----\n\nCreate an instance of `Whorlwind` by calling:\n\n```java\nWhorlwind.create(context, storage, keyAlias)\n```\n\nYou control where Whorlwind saves your encrypted data by providing a `Storage`. Whorlwind ships with\na `SharedPreferencesStorage` if you want to store your data to shared preferences.\n\n`keyAlias` is used when generating a key pair in the `KeyStore` and should not be shared with any\nother key aliases in your project.\n\nAll attempts to read/write from Whorlwind must be guarded by a call to `canStoreSecurely()`. This\nchecks for necessary permissions and whether or not the fingerprint manager is available for use.\nThe state of these requirements can change over the lifetime of your activity/application so it is\nnot sufficient to check this once during activity/application creation.\n\n### Writing\n\nWhorlwind handles encrypting the value for you so writing a new value is as simple as passing it to\nthe `write()` method. However, be aware that Whorlwind will be performing cryptographic operations\nand may also perform some disk I/O regardless of your `Storage` implementation, so `write()` should\nnot be called on the main thread.\n\n```java\nif (whorlwind.canStoreSecurely()) {\n  Observable.just(\"value\")\n      .observeOn(Schedulers.io())\n      .flatMapCompletable(value -\u003e whorlwind.write(\"key\", ByteString.encodeUtf8(value)))\n      .subscribe();\n}\n```\n\n### Reading\n\nWhorlwind will handle activating the fingerprint reader and decrypting your data for you once you\nsubscribe to the stream returned from `read()`. Similar to `write()`, `read()` should not be\nsubscribed to on the main thread.\n\n```java\nif (whorlwind.canStoreSecurely()) {\n  whorlwind.read(\"key\")\n      .subscribeOn(Schedulers.io())\n      .observeOn(AndroidSchedulers.mainThread())\n      .subscribe(result -\u003e {\n        switch (result.readState) {\n            case NEEDS_AUTH:\n              // An encrypted value was found, prompt for fingerprint to decrypt.\n              // The fingerprint reader is active.\n              promptForFingerprint();\n              break;\n            case UNRECOVERABLE_ERROR:\n            case AUTHORIZATION_ERROR:\n            case RECOVERABLE_ERROR:\n              // Show an error message. One may be provided in result.message.\n              // Unless the state is UNRECOVERABLE_ERROR, the fingerprint reader is still\n              // active and this stream will continue to emit result updates.\n              showFingerprintMessage(result.code, result.message);\n              break;\n            case READY:\n              if (result.value != null) {\n                // Value was found and has been decrypted.\n                showToast(result.value.utf8());\n              } else {\n                // No value was found. Fall back to password or fail silently, depending on\n                // your use case.\n                fingerprintFallback();\n              }\n              break;\n            default:\n              throw new IllegalArgumentException(\"Unknown state: \" + result.readState);\n          }\n      });\n}\n```\n\n### Sample\n\nA sample application is provided with a more comprehensive example.\n\n\n\nDownload\n--------\n\nGradle:\n\n```groovy\nimplementation 'com.squareup.whorlwind:whorlwind:2.1.0'\n```\n\n\nLicense\n--------\n\n    Copyright 2016 Square, Inc.\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsquare%2Fwhorlwind","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsquare%2Fwhorlwind","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsquare%2Fwhorlwind/lists"}