9

I want to be able to generate and re-generate the same RSA keys from a password (and salt) alone in python.

Currently I was doing it using pycrypto, however, it does not seem to generate the same exact keys from the password alone. The reason seems to be that when pycrypto generates a RSA key it uses some sort of random number internally.

Currently my code looks as follows:

import DarkCloudCryptoLib as dcCryptoLib #some costume library for crypto
from Crypto.PublicKey import RSA

password = "password"


new_key1 = RSA.generate(1024) #rsaObj
exportedKey1 = new_key1.exportKey('DER', password, pkcs=1)
key1 = RSA.importKey(exportedKey1)

new_key2 = RSA.generate(1024) #rsaObj
exportedKey2 = new_key2.exportKey('DER', password, pkcs=1)
key2 = RSA.importKey(exportedKey2)
print dcCryptoLib.equalRSAKeys(key1, key2) #wish to return True but it doesn't

I don't really care if I have to not use pycrypto, as long as I can generate these RSA keys from passwords and salts alone.

Thanks for the help in advance.

Just for reference, this is how dcCryptoLib.equalRSAKeys(key1, key2) function looks like:

def equalRSAKeys(rsaKey1, rsaKey2):
    public_key = rsaKey1.publickey().exportKey("DER") 
    private_key = rsaKey1.exportKey("DER") 
    pub_new_key = rsaKey2.publickey().exportKey("DER")
    pri_new_key = rsaKey2.exportKey("DER")
    boolprivate = (private_key == pri_new_key)
    boolpublic = (public_key == pub_new_key)
    return (boolprivate and boolpublic)

NOTE: Also, I am only using RSA for authentication. So any solution that provides a way of generating secure asymmetric signatures/verifying generated from passwords are acceptable solutions for my application. Though, generating RSA keys from passwords I feel, is a question that should also be answered as it seems useful if used correctly.

6
  • 2
    It appears RSA.generate accepts a randfunc argument. I suspect passing something like lambda n: 'a'*n is a start, but I don't know how pycrypto uses the byte length. Also, I'm sure you realize but using a non-random random function will greatly drop security. Commented Dec 10, 2013 at 0:29
  • Why do you want to generate an RSA key from a passphrase? If you're going to start out with a known passphrase, why not use something like PKCS#1 and a faster, stronger scheme like AES? Commented Dec 10, 2013 at 0:43
  • These are good questions. I am using AES-CBCB for encryption (because I don't need the asymmetry for encryption), but I need some non-symmetric algorithm for the signatures. So I am trying to use RSA for signatures, though, technically, any asymmetric algorithm would do if I can generate the signatures from the passwords. The passphrase is hopefully, only know to the user. However, I still find it a little odd that its so hard to generate these keys from passwords (and salts) alone, regardless of the application I am doing. Commented Dec 10, 2013 at 0:58
  • Thanks for the suggestion! But the problem is that the authentication has to be asymmetric! Because I don't want people that have the "verification key" to be able to sign things too. I will look into your second suggestion more in detail right now. Commented Dec 10, 2013 at 1:16
  • @kalhartt unfortunately, I cannot make the user remember a random number for usability reasons. The user will still need the same level of security regardless of what machine he is using. Therefore, it would be ideal if he only needs to remember his password and the system can remember the salt for him. Commented Dec 10, 2013 at 1:22

2 Answers 2

6

If you're trying to implement an authenticated encryption scheme using a shared password, you don't really need an RSA key: all you need is an AES key for encryption and an HMAC key for authentication.

If you do need to generate an asymmetric signature than can be verified without knowing the password, you're going to have to somehow generate RSA (or DSA, etc.) keys in a deterministic manner based on the password. Based on the documentation, this should be possible by defining a custom randfunc, something like this:

from Crypto.Protocol.KDF import PBKDF2
from Crypto.PublicKey import RSA

password = "swordfish"   # for testing
salt = "yourAppName"     # replace with random salt if you can store one

master_key = PBKDF2(password, salt, count=10000)  # bigger count = better

def my_rand(n):
    # kluge: use PBKDF2 with count=1 and incrementing salt as deterministic PRNG
    my_rand.counter += 1
    return PBKDF2(master_key, "my_rand:%d" % my_rand.counter, dkLen=n, count=1)

my_rand.counter = 0
RSA_key = RSA.generate(2048, randfunc=my_rand)

I've tested this, and it does generate deterministic RSA keys (as long as you remember to reset the counter, at least). However, note that this is not 100% future-proof: the generated keys might change, if the pycrypto RSA key generation algorithm is changed in some way.

In either case, you'll almost certainly want to preprocess your password using a slow key-stretching KDF such as PBKDF2, with an iteration count as high as you can reasonably tolerate. This makes breaking your system by brute-force password guessing considerably less easy. (Of course, you still need to use strong passwords; no amount of key-stretching is going to help if your password is abc123.)

Sign up to request clarification or add additional context in comments.

6 Comments

I tried your suggestion on giving it a costume deterministic function but any function so far I have given it, makes the key generation process ridiculously slow for some reason. I was wondering if you have tried this yourself or if it worked? Thanks for providing me things to try out btw :) I think I will try to checkout DSA now (in pycrypto) and see if it works.
How did you check that it was deterministic? I am having a slight problem because when I modify your script just slightly, it takes to long to generate the key. How did you made it to generate keys fast enough and still be deterministic.
master_key = PBKDF2(password, salt, count=10000) def my_rand(n): return PBKDF2(master_key, salt, dkLen=n, count=1) #return PBKDF2(master_key, "my_rand:%d" % my_rand.counter, dkLen=n, count=1) RSA_key1 = RSA.generate(2048, randfunc=my_rand)
I apologize if the code is not formatted nicely, I am having problems formatting it.
You need to include the counter when calling PBKDF2() inside my_rand(), otherwise every call to my_rand() will return the exact same output, which will cause the prime number generation algorithm to get stuck, since it'll keep generating the same numbers over and over. To check that it's deterministic, just generate two keys (and don't forget to reset the counter between them!) and compare key1.n with key2.n.
|
1
  • Pass "randfunc" to the RSA.generate, and randfunc should return the output bytes, in order, of a well-known key derivation function that has been configured with enough output bits for RSA to "always complete" without needing more bits.

  • Argon2, scrypt, PBKDF2 are examples of KDFs designed for this purpose.

  • It may be possible to use Keccak directly as a KDF by specifying a high number of output bits.

  • If your generation function follows a well known standard closely, it should work across multiple implementations.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.