{"id":21423261,"url":"https://github.com/quintessence-sec/pgp-mfa","last_synced_at":"2025-09-03T07:33:52.946Z","repository":{"id":263154673,"uuid":"889508299","full_name":"quintessence-sec/pgp-mfa","owner":"quintessence-sec","description":"proof-of-concept to use pgp as a MFA method","archived":false,"fork":false,"pushed_at":"2024-11-16T15:05:14.000Z","size":12,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-16T20:26:31.274Z","etag":null,"topics":["2fa","benchmark","cryptography","golang","gpg","gpg-encryption","mfa"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/quintessence-sec.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2024-11-16T14:05:46.000Z","updated_at":"2024-11-16T15:05:17.000Z","dependencies_parsed_at":null,"dependency_job_id":"296d5b91-d0ad-4e12-a3c9-27eaea91a559","html_url":"https://github.com/quintessence-sec/pgp-mfa","commit_stats":null,"previous_names":["quintessence-sec/pgp-mfa"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/quintessence-sec/pgp-mfa","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quintessence-sec%2Fpgp-mfa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quintessence-sec%2Fpgp-mfa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quintessence-sec%2Fpgp-mfa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quintessence-sec%2Fpgp-mfa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/quintessence-sec","download_url":"https://codeload.github.com/quintessence-sec/pgp-mfa/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quintessence-sec%2Fpgp-mfa/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273409365,"owners_count":25100442,"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-09-03T02:00:09.631Z","response_time":76,"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":["2fa","benchmark","cryptography","golang","gpg","gpg-encryption","mfa"],"created_at":"2024-11-22T21:15:25.199Z","updated_at":"2025-09-03T07:33:52.906Z","avatar_url":"https://github.com/quintessence-sec.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pgp-mfa\n\na proof of concept for a multi-factor authentication system using PGP, possible alternative to TOTP (RFC 6238).\n\n## usage\n\n```bash\n$ go build -v -o pgp-mfa\n$ ./pgp-mfa import-key \u003ckey-file\u003e # armored / binary format supported, - for stdin\n$ gpg --export \u003ckey-id\u003e | ./pgp-mfa import-key - # import from stdin\n$ ./pgp-mfa challenge \u003clength\u003e [key-id]    # if no key-id is provided, you'll be prompted to select one\n```\n\n## what's the point?\n\nthe idea is not to replace RFC 6238, or any other MFA system, but to provide an alternative that could be used in production.\n\n- still works offline\n- not time based: it is not necessary to have a clock on the device, nor for it to be in sync with current real time.\n- no shared secrets: TOTP relies on a shared secret between the server and the client, which could be compromised, by using PGP, only the public key is shared.\n- emailable challenges: PGP keys contains an email address, the server could use this information to send the challenge to the user by email.\n- benefits from expirability: PGP keys can expire, allowing a 0 interaction self-destruction of the mean of access to the account.\n\n## how does it work?\n\n1. the server generates a random string, and encrypts it with the public key of the user.\n2. the encrypted message is sent to the user.\n3. user has to decrypt the message using their private key (and passphrase if one is set).\n4. the server checks whether the decrypted message is the same one as the one originally sent\n5. (optional) the server can check whether the challenge has expired, and reject the solution if it has.\n\n## performance\n\nrun benchmark with `go test -bench=.` and see the results. uses go's crypto/rand package to generate random bytes.\n\n### tests\n\n| test name | description |\n| --- | --- |\n| Ed25519_16 | uses a public ed25519 key to encrypt / decrypt a 16 bytes challenge |\n| Ed25519_32 | 32 bytes challenge |\n| Ed25519_64 | 64 bytes challenge |\n| Ed25519_128 | 128 bytes challenge |\n| Ed25519_256 | 256 bytes challenge |\n| Ed25519_512 | 512 bytes challenge |\n| Rsa3072-16 | uses a public rsa3072 key to encrypt / decrypt a 16 bytes challenge |\n| Rsa3072-32 | 32 bytes challenge |\n| Rsa3072-64 | 64 bytes challenge |\n| Rsa3072-128 | 128 bytes challenge |\n| Rsa3072-256 | 256 bytes challenge |\n| Rsa4092-16 | uses a public rsa4092 key to encrypt / decrypt a 16 bytes challenge |\n| Rsa4092-32 | 32 bytes challenge |\n| Rsa4092-64 | 64 bytes challenge |\n| Rsa4092-128 | 128 bytes challenge |\n| Rsa4092-256 | 256 bytes challenge |\n| ChallengesGeneration_16 | measures how fast can the machine can generate challenges of 16 bytes |\n| ChallengesGeneration_32 | 32 bytes |\n| ChallengesGeneration_64 | 64 bytes |\n| ChallengesGeneration_128 | 128 bytes |\n| ChallengesGeneration_256 | 256 bytes |\n| ChallengesGeneration_512 | 512 bytes |\n\n### results\n\n```\ngoos: linux\ngoarch: amd64\npkg: github.com/quintessence-sec/pgp-mfa\ncpu: AMD Ryzen 9 7950X 16-Core Processor            \nBenchmarkEd25519_16-32                             27735             42647 ns/op\nBenchmarkEd25519_32-32                             27698             43268 ns/op\nBenchmarkEd25519_64-32                             27820             42442 ns/op\nBenchmarkEd25519_128-32                            28976             41584 ns/op\nBenchmarkEd25519_256-32                            28774             42483 ns/op\nBenchmarkEd25519_512-32                            27616             43069 ns/op\nBenchmarkRsa4092_16-32                              6246            178434 ns/op\nBenchmarkRsa4092_32-32                              6500            181093 ns/op\nBenchmarkRsa4092_64-32                              6232            182861 ns/op\nBenchmarkRsa4092_128-32                             6586            180314 ns/op\nBenchmarkRsa4092_256-32                             6598            181688 ns/op\nBenchmarkRsa4092_512-32                             6369            181804 ns/op\nBenchmarkRsa3072_16-32                             10000            109607 ns/op\nBenchmarkRsa3072_32-32                             10000            110057 ns/op\nBenchmarkRsa3072_64-32                             10000            110457 ns/op\nBenchmarkRsa3072_128-32                            10000            109201 ns/op\nBenchmarkRsa3072_256-32                            10000            110007 ns/op\nBenchmarkRsa3072_512-32                             9853            111029 ns/op\nBenchmarkChallengesGeneration_16-32              3557168               334.9 ns/op\nBenchmarkChallengesGeneration_32-32              3463393               345.9 ns/op\nBenchmarkChallengesGeneration_64-32              2635272               446.3 ns/op\nBenchmarkChallengesGeneration_128-32             1996668               605.8 ns/op\nBenchmarkChallengesGeneration_256-32             1361889               874.7 ns/op\nBenchmarkChallengesGeneration_512-32              730452              1443 ns/op\nPASS\nok      github.com/quintessence-sec/pgp-mfa     36.111s\n```\n\naccording to the results, we can deduce that the most optimal configuration is to use an ed25519 key, with a challenge length of 128 bytes.\n\n### resistance to brute-force attacks\n\nthe charset of challenges is, by default: `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_+/\\'\"!@#$%^\u0026*()[]{}\u003c\u003e?,.;:`, which is a total of 90 characters.\n\nif we consider an ed25519 key we can process 128 bytes in 41.584 microseconds (≈ 3.078 MB/s ~ 24046.875 attempts/s).\n\n| challenge length | number of possible solutions | probability of 1st try success | number of years to brute-force |\n| --- | --- | --- | --- |\n| 16 bytes | 1.853E+31 | 5.397E-30% | 7.706E+26\n| 32 bytes | 3.434E+62 | 2.912E-61% | 1.428E+58\n| 64 bytes | 1.179E+125 | 8.482E-124% | 4.903E+120\n| 128 bytes | 1.390E+250 | 7.194E-249% | 5.781E+245\n| 256 bytes | 1.932E+500 | 5.175E-499% | 8.036E+495\n| 512 bytes | 3.734E+1000 | 2.678E-999% | 1.553E+996\n\nthis is, of course, assuming that:\n\n1. the randomness of the challenge is perfectly random\n2. the brute-force attacker has no access to the server\n3. there are no delays between retries, and there are no maximum number of attempts\n4. the challenge does not expire","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquintessence-sec%2Fpgp-mfa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fquintessence-sec%2Fpgp-mfa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquintessence-sec%2Fpgp-mfa/lists"}