backdoored_rsa_with_x25519.sf 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. #!/usr/bin/ruby
  2. # RSA key generation, backdoored using curve25519.
  3. # Inspired by:
  4. # https://gist.github.com/ryancdotorg/18235723e926be0afbdd
  5. # See also:
  6. # https://eprint.iacr.org/2002/183.pdf
  7. # https://www.reddit.com/r/crypto/comments/2ss1v5/rsa_key_generation_backdoored_using_curve25519/
  8. require('Crypt::PK::X25519')
  9. struct RSAKey {
  10. e, p, q, d, n
  11. }
  12. func generate_rsa_key (Num bits = 2048, Str ephem_pub = "", Num pos = 80, Str seed = nil) {
  13. if (defined(seed)) {
  14. iseed(seed.ascii2hex.hex)
  15. }
  16. var (p, q) = 2.of { 1<<(bits >> 1) -> irand.next_prime }...
  17. if (p > q) {
  18. (p, q) = (q, p)
  19. }
  20. var n = (p * q)
  21. # Embed the public key into the modulus
  22. n = n.as_hex.insert(ephem_pub, pos, ephem_pub.len).hex
  23. # Recompute n, reusing p in computing a new q
  24. q = idiv(n,p).next_prime
  25. n = (p * q)
  26. var phi = ((p - 1) * (q - 1))
  27. var e = 0
  28. for (var k = 16 ; gcd(e, phi) != 1 ; ++k) {
  29. e = (2**k + 1)
  30. }
  31. var d = invmod(e, phi)
  32. RSAKey(e, p, q, d, n)
  33. }
  34. func recover_rsa_key (Num bits, Num n, Str master_private_key, Num pos) {
  35. var ephem_pub = n.as_hex.substr(pos, 64) # extract the embeded public key
  36. # Import the public key
  37. var ephem_pub_key = %O<Crypt::PK::X25519>.new.import_key(Hash(curve => "x25519", pub => ephem_pub))
  38. # Import the master private key
  39. var master_priv_key = %O<Crypt::PK::X25519>.new.import_key(Hash(curve => "x25519", priv => master_private_key))
  40. # Recover the shared secret that was used as a seed value for the random number generator
  41. var recovered_secret = master_priv_key.shared_secret(ephem_pub_key)
  42. # Recompute the RSA key, given the embeded public key and the seed value
  43. generate_rsa_key(bits, ephem_pub, pos, recovered_secret)
  44. }
  45. var BITS = 2048 # must be >= 1024
  46. var POS = (BITS >> 5)
  47. # Public and private master keys
  48. var MASTER_PUBLIC = "c10811d4e424305c6696f9b5f787efb67f80530e6115e367bd7967ba05093e3d"
  49. var MASTER_PRIVATE = "3a35b10511bcd20bcb9b12bd73ab9ad0bf8f7f469ffb70d2ae8fb110b761df97"
  50. # Generate a random ephemeral key-pair. The private key will be used in creating
  51. # the shared secret, while the public key will be embeded in the RSA modulus.
  52. var random_ephem_key = %O<Crypt::PK::X25519>.new.generate_key
  53. # Import the master public key
  54. var master_public_key = %O<Crypt::PK::X25519>.new.import_key(Hash(curve => "x25519", pub => MASTER_PUBLIC))
  55. var ephem_pub = random_ephem_key.key2hash(){:pub}
  56. var shared_secret = random_ephem_key.shared_secret(master_public_key)
  57. # Generate the backdoored RSA key, using the ephemeral random public key, which will be embeded
  58. # in the RSA modulus, and pass the shared secret value as a seed for the random number generator.
  59. var rsa_key = generate_rsa_key(BITS, ephem_pub, POS, shared_secret)
  60. var m = "Hello, world!".ascii2hex.hex
  61. if (m >= rsa_key.n) {
  62. die "Message is too long!"
  63. }
  64. var c = powmod(m, rsa_key.e, rsa_key.n) # encoded message
  65. var M = powmod(c, rsa_key.d, rsa_key.n) # decoded message
  66. say M.as_hex.hex2ascii
  67. # Recover the RSA key, given the RSA modulus n and the private master key.
  68. var recovered_rsa = recover_rsa_key(BITS, rsa_key.n, MASTER_PRIVATE, POS)
  69. # Decode the encrypted message, using the recovered RSA key
  70. var decoded_message = powmod(c, recovered_rsa.d, rsa_key.n)
  71. # Print the decoded message, decoded with the recovered key
  72. say decoded_message.as_hex.hex2ascii