pdns icon indicating copy to clipboard operation
pdns copied to clipboard

auth: Speed up ECDSA and RSA signatures

Open rgacogne opened this issue 2 years ago • 4 comments

Short description

For ECDSA, and likely for RSA, computing the public key is not a cheap operation. So instead of computing it twice to get the lookup key for our signatures cache, reuse the computed public key and only compute its digest. In addition, since ed* algorithms were already using the whole key instead of a digest, place the cut off at public keys larger than 64 bytes, meaning that only RSA ones (128+ bytes) will be hashed. This provides an additional speedup for ECDSA keys (32 or 48 bytes) since they no longer need to be hashed, and simplifies the signers code as the hashing can be moved to signature key cache now that it only depends on they key size. For reference the size of a SHA-1 digest is 20 bytes.

In my tests this reduces by 30% the cost of calling addRRSigs() for ECDSA signatures when the signature is already present in the cache.

Checklist

I have:

  • [x] read the CONTRIBUTING.md document
  • [x] compiled this code
  • [x] tested this code
  • [ ] included documentation (including possible behaviour changes)
  • [x] documented the code
  • [ ] added or modified regression test(s)
  • [ ] added or modified unit test(s)

rgacogne avatar May 02 '22 12:05 rgacogne

My understanding is that because ECDSA keys are "short" (< 64 bytes), hashing them is a waste so we can save on that.

Yes, but that's only a part of the gain in this pull request (1/5th, if I recall correctly). The biggest win is that we no longer compute the public key from the private key twice:

  • in DNSKEYRecordContent drc = dpk.getDNSKEY();
  • in getPubKeyHash()

Instead we now use the public key copy contained in drc.

But for RSA keys which may or may not be short, we now decide to hash depending on the key length, which seems reasonable. Is my understanding correct? If it is, doesn't that also offer an occasional performance improvement for RSA keys?

It would for very small RSA keys, but 512-bit keys are already just above the 64-byte limit so I don't think it will actually help in practice. But the other optimization, computing the public key once, should help.

One last point, is SHA1 still a good thing for hashing? I assume yes, but just not good for "cryptographically-secure hashing" anymore, correct?

SHA-1 is clearly broken for cryptographic applications indeed. In that case the likelihood of getting a collision between the keys that are in our database combined with a collisions on the content to sign seems small. I guess the threat model would be an authenticated user deciding to compute a collision on a key from a different zone hosted on the same server to cause a denial of service of that zone? If we are really worried about that we could add a random salt computed on startup, or something like that, I suppose?

rgacogne avatar May 04 '22 13:05 rgacogne

If we are really worried about that we could add a random salt computed on startup, or something like that, I suppose?

That would be a solution but I don't know how likely (or how dangerous) the scenario you describe is.

fredmorcos avatar May 04 '22 15:05 fredmorcos

Just a note as I was myself confused about that recently while discussing this PR: the computation that this PR is making faster happens before the lookup into the signature cache, as this is the hash that we actually use as (part of) the key into the signature cache.

rgacogne avatar May 21 '22 12:05 rgacogne

I rebased this branch on master to avoid the CI failure due to python's protobuf update. I also added a new commit pre-computing the DNSKEYRecordContent (which involves computing the public key) once, when DNSSECPrivateKey::setKet() is called, instead of doing it every time. This results in a very nice speed-up in my tests, but one big drawback is that setKey() should NO LONGER be used before the algo and flags have been set. Perhaps it would make sense to modify setKey()'s signature so that it takes these as arguments, making it clear that they are required to be set?

rgacogne avatar Jun 01 '22 19:06 rgacogne

one big drawback is that setKey() should NO LONGER be used before the algo and flags have been set. Perhaps it would make sense to modify setKey()'s signature so that it takes these as arguments, making it clear that they are required to be set?

Fixed that by in a new commit:

  • adding a comment to the setKey declaration
  • requiring the flags as a setKey parameter

rgacogne avatar Nov 29 '22 10:11 rgacogne

I just noticed that this PR is conflicted, I'll fix that in a bit.

rgacogne avatar Jan 12 '23 13:01 rgacogne

I just noticed that this PR is conflicted, I'll fix that in a bit.

Done

rgacogne avatar Jan 12 '23 13:01 rgacogne

This will require quite the rebase for my openssl-3.0 branch but it's OK. Let's get it merged and I'll deal with it.

fredmorcos avatar Jan 20 '23 16:01 fredmorcos

@Habbie Is it OK to merge this now, or shall we wait?

rgacogne avatar Jan 20 '23 16:01 rgacogne

I merged it, thanks

Habbie avatar Jan 23 '23 09:01 Habbie