5

Is there a way to deterministically create an RSA private-key for an X.509 certificate, ideally through a library which has been vetted already?

i.e. user enters some phrase like "this is my private group", and that seed value is used to generate a private RSA key which I can then use as a certificate authority. But in such a way I can get the same key predictably on different machines from just that seed value.

My use case is I would like to support pre-shared keys in my application, but use the existing X.509 support it uses for keying. If I could create a predictable X.509 certificate from some initial value, then this would be trivial since users could verify each other's certificates against the generated key.

I would like to avoid hand-rolling a library to do this for obvious reasons, but this doesn't seem to be a commonly done thing - even though obviously various systems which support PSK must, internally, be doing something quite similar.

4 Answers 4

4

At the moment, I see no big problem here, at least, from the PoV of the algorithm itself.

  1. Seed a PRNG with e.g. a hash of your key phrase.
  2. Use the PRNG to generate the RSA key pair.
  3. Use a library to generate the certificate, putting your key material in it.

Determinism is achieved through the use of PRNG seeded with the same key phrase (likely salted and put through e.g. SHA-256), so the output is the same for same input. All the required algorithms can be easily implemented in Python, or existing code can be reused, from e.g. Python-RSA. The only required adjustment would be using your PRNG instead of what it's using now.

Point 3 is the hardest part as I'm no big expert in libraries, but this seems to be a part of another problem (generating a certificate from existing key material). This falls into the "what is the best library for..." category.

As usual: whipping your own crypto is generally discouraged and frowned upon, unless you are Bruce Schneier. ;-)

2

As @Dmitry indicates, what you envision is feasible. RSA key pair generation, actually any key pair generation, feeds on a PRNG; the PRNG itself is a deterministic algorithm that "extends" an initial random seed into an arbitrarily long sequence of pseudorandom bytes. You could use a strong password hashing function and use the resulting hash value as seed for a PRNG.

However, there are some side issues which can be bothersome. For instance:

  • Password hashing works best when there is a salt, which is not secret but must be stored somewhere.
  • Changing your password implies changing your private key, which is usually quite inconvenient.
  • The public key (which is public) can be used as a test for an offline dictionary attack: attackers can try passwords "at home" until a match is found with the known public key.

I expanded more on that subject in this answer.

Remember that the certificate (which contains the public key) must be stored somewhere, since you cannot recreate it from the password (the certificate is signed by the CA, and you cannot recompute that signature, since you are not your own CA). You could generate your private key "normally", encrypt it with some password-based encryption system, and store it along with the certificate; this would be equivalent, from a security point of view, to password-to-RSA-key derivation; but at least it would work with existing formats and tools (you could use PKCS#12, aka "pfx", as the password-protected archive format for both the certificate and the private key).

1

I did some investigation and found some interesting notes I thought worth sharing, but not a definitive answer. I generated several certificates and compared them. I was using this line:

Windows:

openssl req -new -x509 -key priv.pem -out pub.pem -h -subj '//CN=test'

Linux (not tested):

openssl req -new -x509 -key priv.pem -out pub.pem -h -subj '/CN=test'

Then I compared the contents and they did not match.

I used Python to get more information about the certificates:

from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.backends import default_backend
pub_str = open(pub_fname).read().encode()
cert_obj = load_pem_x509_certificate(pub_str, default_backend())
public_key = cert_obj.public_key()

I found that cert_obj.serial_number, cert_obj.not_valid_after, cert_obj.not_valid_before differed (also cert_obj.signature differed but I assume this is a result of the other fields not matching).

I was able to specify the serial number via command line (Windows):

openssl req -new -x509 -key priv.pem -out pubX.pem -set_serial 219363692778776146107430278182615926685007820639  -subj '//CN=test'

but NOT the dates. At this point I thought I should just store the certificate file. There are some other possible avenues unexplored. Perhaps using Python to generate the certificate instead of openssl might make it possible to specify those values. There is also alternative commands that allow the dates to be specified (I saw gossl mentioned online but was unable to confirm).

2
  • I can't believe you, unless you made up your own program. No released openssl has had an option -h on req, and if that is omitted -subj //CN=test is wrong and results in empty Subject and Issuer which is invalid and unusable. However, if you did create certs, you could see those (and all) fields with openssl x509 -text without needing python code. openssl ca (reading a CSR created with openssl req -new without -x509) can control start/end times, and uses serial from a file you can set, but is more effort to set up than req -new -x509 (or x509 -req -selfsign) Jun 22, 2022 at 4:07
  • Finally, traditional 'PKCS1v1.5' RSA signatures are deterministic, but RSA-PSS (now gradually becoming more common) aren't, and DSA and ECDSA aren't unless you use SIV which OpenSSL doesn't. EdDSA are. Jun 22, 2022 at 4:10
-1

X509 certificate are, basically, made of several kind of information:

  • The certificate declarative data which includes everything that the certificates claims about the key owner (mostly what you find in the certificate subject fields but also commonly additional OIDs like )
  • The certificate scoping data (for instance: key usage, validity period, etc.)
  • The public half of the key pair associated with the certificate.
  • The certificate trust chain information (who guarantee that the declarative and scoping data is correct) and revocation data
  • Some technical fields (which algorythm is used, what's the certificate serial number, etc.)

You can put anything you want in both the declarative and scoping data and you can control what's in the technical fields (mostly) as long as your CA accepts to validate your cert as you submit it (or as long as you're using self-signed certificates).

The public key part of the certificate is dictated by whatever key algorythm you decided to use. You could use the same key for all your certificates although I wouldn't suggest you do so: there is no valid reason to do so in practice and it could threaten the security of the whole system depending on how the keys are used.

Finally, the trust information includes mostly the issuer reference (which you mostly can decide or, at least, know in advance in most case) and the digital signature of the whole certificate (which you have no way of knowing or control over). Using a self-signed certificate would not help you, in this case, since the only way for the two signature to be 100% identical would be for every part of the signature data to be identical as well: you might as well make a copy of the original certificate instead of signing it twice.

The good news, however, is that while you shouldn't use the same key for several certificate, you do not have to. You can simply create your own CA and have your application verify all certificates against that CA root: that will allow all your users to verify the keys you're distributing - including future keys - while not requiring your to weakening the infrastructure thing by reusing a fixed set of keys.

Pinning the certificate authority - instead of the leaf certificate - is a bit more complex to implement but it's also more flexible and allow for more complex usage scenarii.

2
  • My problem is I want to generate a private RSA-key for such a certificate in a deterministic way. That could then become the signing CA for user certificates, and as long as all the users shared the same key then they could verify each other's certificates. So I want to take some seed value (the user PSK) -> transform it to a private key. But then not need to necessarily tell users to share unwieldy certificate/key files. Jun 26, 2014 at 9:46
  • 1
    That is pointless. You can, of course, generate several time the same key pair by controlling the input data used by the key generation algorythm but it wouldn't provide you with what you want or need: you'd just make sure your whole crypto is of bad quality because it stems from a very limited entropy source. What you need to do is generate a CA of your own, have your users generate key pairs of their own and the use them to sign certificates with your CA that contain an application-specific OID
    – Stephane
    Jun 26, 2014 at 12:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .