dsa_nonce_recover.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. #!/usr/bin/env python3
  2. '''
  3. Recover the nonce value k used in integer DSA or NIST-style ECDSA,
  4. starting from the private key and the signature.
  5. _Without_ the private key, recovering the nonce is equivalent to
  6. recovering the private key itself. But with it, it's a trivial piece
  7. of modular arithmetic.
  8. This script generates a load of test signatures from various keys,
  9. recovers the nonces used, and prints them. This allows an eyeball
  10. check of whether they're evenly distributed.
  11. '''
  12. import argparse
  13. from base64 import b64decode as b64
  14. from eccref import *
  15. from testcrypt import *
  16. from ssh import *
  17. from agenttest import agent_query
  18. def recover_nonce(order, hashalg, privint, transform_hash, r, s, message):
  19. w = int(mp_invert(s, order))
  20. h = ssh_hash_new(hashalg)
  21. ssh_hash_update(h, message)
  22. z = int(mp_from_bytes_be(ssh_hash_final(h)))
  23. z = int(transform_hash(z))
  24. return w * (z + r * privint) % order
  25. def dsa_decode_sig(signature):
  26. _, signature = ssh_decode_string(signature, return_rest=True)
  27. signature = ssh_decode_string(signature)
  28. assert len(signature) == 40
  29. r = int(mp_from_bytes_be(signature[:20]))
  30. s = int(mp_from_bytes_be(signature[20:]))
  31. return r, s
  32. def ecdsa_decode_sig(signature):
  33. _, signature = ssh_decode_string(signature, return_rest=True)
  34. signature = ssh_decode_string(signature)
  35. r, signature = ssh_decode_string(signature, return_rest=True)
  36. s, signature = ssh_decode_string(signature, return_rest=True)
  37. r = int(mp_from_bytes_be(r))
  38. s = int(mp_from_bytes_be(s))
  39. return r, s
  40. class SignerBase:
  41. def test(self, privkey, decode_sig, transform_hash, order, hashalg,
  42. algid, obits):
  43. print("----", algid)
  44. print("k=0x{{:0{}b}}".format(obits).format(order))
  45. privblob = ssh_key_private_blob(privkey)
  46. privint = int(mp_from_bytes_be(ssh_decode_string(privblob)))
  47. self.setup_key(privkey)
  48. for message in (f"msg{i}".encode('ASCII') for i in range(100)):
  49. signature = self.sign(privkey, message)
  50. r, s = decode_sig(signature)
  51. nonce = recover_nonce(order, hashalg, privint, transform_hash,
  52. r, s, message)
  53. print("k=0x{{:0{}b}}".format(obits).format(nonce))
  54. self.cleanup_key(privkey)
  55. def test_dsa(self, pubblob, privblob):
  56. privkey = ssh_key_new_priv('dsa', pubblob, privblob)
  57. _, buf = ssh_decode_string(pubblob, return_rest=True)
  58. p, buf = ssh_decode_string(buf, return_rest=True)
  59. q, buf = ssh_decode_string(buf, return_rest=True)
  60. g, buf = ssh_decode_string(buf, return_rest=True)
  61. p = int(mp_from_bytes_be(p))
  62. q = int(mp_from_bytes_be(q))
  63. g = int(mp_from_bytes_be(g))
  64. transform_hash = lambda h: h
  65. self.test(privkey, dsa_decode_sig, transform_hash, q, 'sha1', 'dsa',
  66. 160)
  67. def test_ecdsa(self, algid, curve, hashalg, pubblob, privblob):
  68. privkey = ssh_key_new_priv(algid, pubblob, privblob)
  69. obits = int(mp_get_nbits(curve.G_order))
  70. def transform_hash(z):
  71. shift = max(0, mp_get_nbits(z) - obits)
  72. return mp_rshift_safe(z, shift)
  73. self.test(privkey, ecdsa_decode_sig, transform_hash, curve.G_order,
  74. hashalg, algid, obits)
  75. class TestcryptSigner(SignerBase):
  76. def setup_key(self, key):
  77. pass
  78. def cleanup_key(self, key):
  79. pass
  80. def sign(self, key, message):
  81. return ssh_key_sign(key, message, 0)
  82. class AgentSigner(SignerBase):
  83. def setup_key(self, key):
  84. alg = ssh_decode_string(key.public_blob())
  85. msg = (ssh_byte(SSH2_AGENTC_ADD_IDENTITY) +
  86. ssh_string(alg) +
  87. key.openssh_blob() +
  88. ssh_string(b"dsa_nonce_recover test key"))
  89. result = agent_query(msg)
  90. assert result == ssh_byte(SSH_AGENT_SUCCESS)
  91. def cleanup_key(self, key):
  92. msg = (ssh_byte(SSH2_AGENTC_REMOVE_IDENTITY) +
  93. ssh_string(key.public_blob()))
  94. result = agent_query(msg)
  95. assert result == ssh_byte(SSH_AGENT_SUCCESS)
  96. def sign(self, key, message):
  97. msg = (ssh_byte(SSH2_AGENTC_SIGN_REQUEST) +
  98. ssh_string(key.public_blob()) +
  99. ssh_string(message))
  100. rsp = agent_query(msg)
  101. t, rsp = ssh_decode_byte(rsp, True)
  102. assert t == SSH2_AGENT_SIGN_RESPONSE
  103. sig, rsp = ssh_decode_string(rsp, True)
  104. assert len(rsp) == 0
  105. return sig
  106. def main():
  107. parser = argparse.ArgumentParser(
  108. description=__doc__,
  109. formatter_class=argparse.RawDescriptionHelpFormatter)
  110. parser.add_argument("--agent", action="store_true",
  111. help="Test an SSH agent instead of testcrypt. "
  112. "(Still needs testcrypt.)")
  113. args = parser.parse_args()
  114. signer = AgentSigner() if args.agent else TestcryptSigner()
  115. signer.test_dsa(b64('AAAAB3NzaC1kc3MAAABhAJyWZzjVddGdyc5JPu/WPrC07vKRAmlqO6TUi49ah96iRcM7/D1aRMVAdYBepQ2mf1fsQTmvoC9KgQa79nN3kHhz0voQBKOuKI1ZAodfVOgpP4xmcXgjaA73Vjz22n4newAAABUA6l7/vIveaiA33YYv+SKcKLQaA8cAAABgbErc8QLw/WDz7mhVRZrU+9x3Tfs68j3eW+B/d7Rz1ZCqMYDk7r/F8dlBdQlYhpQvhuSBgzoFa0+qPvSSxPmutgb94wNqhHlVIUb9ZOJNloNr2lXiPP//Wu51TxXAEvAAAAAAYQCcQ9mufXtZa5RyfwT4NuLivdsidP4HRoLXdlnppfFAbNdbhxE0Us8WZt+a/443bwKnYxgif8dgxv5UROnWTngWu0jbJHpaDcTc9lRyTeSUiZZK312s/Sl7qDk3/Du7RUI='), b64('AAAAFGx3ft7G8AQzFsjhle7PWardUXh3'))
  116. signer.test_ecdsa('p256', p256, 'sha256', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHkYQ0sQoq5LbJI1VMWhw3bV43TSYi3WVpqIgKcBKK91TcFFlAMZgceOHQ0xAFYcSczIttLvFu+xkcLXrRd4N7Q='), b64('AAAAIQCV/1VqiCsHZm/n+bq7lHEHlyy7KFgZBEbzqYaWtbx48Q=='))
  117. signer.test_ecdsa('p384', p384, 'sha384', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBMYK8PUtfAlJwKaBTIGEuCzH0vqOMa4UbcjrBbTbkGVSUnfo+nuC80NCdj9JJMs1jvfF8GzKLc5z8H3nZyM741/BUFjV7rEHsQFDek4KyWvKkEgKiTlZid19VukNo1q2Hg=='), b64('AAAAMGsfTmdB4zHdbiQ2euTSdzM6UKEOnrVjMAWwHEYvmG5qUOcBnn62fJDRJy67L+QGdg=='))
  118. signer.test_ecdsa('p521', p521, 'sha512', b64('AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFrGthlKM152vu2Ghk+R7iO9/M6e+hTehNZ6+FBwof4HPkPB2/HHXj5+w5ynWyUrWiX5TI2riuJEIrJErcRH5LglADnJDX2w4yrKZ+wDHSz9lwh9p2F+B5R952es6gX3RJRkGA+qhKpKup8gKx78RMbleX8wgRtIu+4YMUnKb1edREiRg=='), b64('AAAAQgFh7VNJFUljWhhyAEiL0z+UPs/QggcMTd3Vv2aKDeBdCRl5di8r+BMm39L7bRzxRMEtW5NSKlDtE8MFEGdIE9khsw=='))
  119. if __name__ == '__main__':
  120. main()