0

There is a REST API service. An endpoint is responsible for creation of user accounts. Each user is supposed to have an RSA keypair generated and stored on a server. To protect the private keys from being read by a possible adversary, private keys are encoded to PEM. PEM format is encrypted with AES-256-CBC using a passphrase. The passphrase is same as the one a user is using to log in.

So far I have been generating a keypair within a request handler. But that in some cases takes 10 seconds which is undesirable.

What would be a safe, performance-wise solution to this problem? I want to avoid storing a passphrase in plain-text for later encryption.

2
  • It takes a long time to generate an RSA key pair for even a moderate number of bits. Don't think you will be able to speed that up much.
    – John Wu
    Commented Jun 20, 2017 at 20:55
  • @JohnWu I am aware that this problem cannot be tackled directly i.e. by lowering number of bits/primes used in generation of a key-pair. I see it more as a challenge to my existing system architecture.
    – antagon
    Commented Jun 21, 2017 at 16:38

3 Answers 3

3

If I understood you correctly, you are generating RSA keys on a server, keeping the public key on the server and sending the private keys to the users. I would recommend instead to generate the keys on the client side and send the public key to the server. This is how asymmetric encryption is supposed to be used.

The time needed to generate RSA keys is inherent to the RSA algorithm, there is nothing much you can do about it.

If you must generate the keys on the server, I would suggest you to switch from RSA to ECC. The key generation process is much faster. Many libraries exist to use ECC. The Curve25519 algorithm is a good choice, but there are alternatives.

Also, if you just need to protect communications between the client and the server, using TLS 1.2 (or 1.3) with client certificates is a better solution than rolling your own crypto.

6
  • A key-pair is stored on a server, a private key encrypted with a passphrase. When a user decides to send another user a message, he grabs his PK, encrypts a message and stores it in DB. Then a recipient comes along, uses his login password to decrypt a SK so he can use it to decrypt the message. A key-pair is never exposed to a user. The reason to speed up generation of key-pairs is not to open myself to potential DOS. I thought about a dedicated daemon generating every few minutes fresh SKs in a pool from which I could grab one.
    – antagon
    Commented Jun 21, 2017 at 16:34
  • Does the server decrypts the private keys? If so, what is the point of asymmetric encryption, if the server has all the private keys? Even if they are encrypted with a password, they will at one point be decrypted on the server-side. What would asymmetric encryption achieve or protect from that symmetric encryption could not?
    – A. Hersean
    Commented Jun 22, 2017 at 7:44
  • The reason to opt-in for asym. crypto is to avoid storing secrets in plain-text. If a user wants to say something in secret to another, he grabs his PK and encrypts that message. Then if a recipient wants to read the message, he decrypts his SK on demand by providing a passphrase. Decrypted SK is isolated in process' memory. The scheme is designated to protect against SQLi as it is the main attack vector on web applications. I am assuming an adversary does not have a shell access.
    – antagon
    Commented Jun 22, 2017 at 12:32
  • 1
    I suggest you look at HSMs. Also, from an outside look, your implement has a huge potential for implementation bugs in the cryptography (even if you used libraries to handle it), so I would strongly recommend you to do an audit by a specialist in cryptography. Other than that, to improve the performance of your application while keeping your design, I stand by my recommendation to replace RSA by ECC.
    – A. Hersean
    Commented Jun 23, 2017 at 10:03
  • It's a pet project. I am far from having a budget of an enterprise. The solution I have now is adapted from existing solutions (think gnupg). I am looking for good enough solution, that doesn't go in way of users. Implementation is based on golang crypto libraries and everything I do is part of their interface. Since I am not a cryptographer I just have to give way to my gut feeling. Thanks for your suggestion even though I'll stick with RSA for now.
    – antagon
    Commented Jun 23, 2017 at 11:02
1

Your description of the service, even in comments, is not entirely clear, so I'm going to describe some general guidelines for what you need to be doing for a system of this sort, along with why and how.

  1. Do not generate asymmetric keys on the server. That is, whenever possible, the client's job. Not only does this avoid tying up server resources and opening yourself to denial-of-service attacks, it avoids the server ever, even briefly, having access to the client's private key.
  2. Encrypt the private key on the client, before sending to the server. A system in which the server can ever see the client's private key, and could even theoretically store it or decrypt it, cannot be trusted.
  3. Hash the user's password though a secure password hashing function (or key derivation function) on the client, before sending to the server. When using a scheme like this, where users have private keys that are encrypted using their passwords, the server cannot be allowed to know the user's password even at account creation or login.
  4. Use different salts in the key derivation function for client-side encryption of the key and client-side hashing of the password for authentication. It's fine to use the same algorithm and even the same work factor for both the password-derived encryption key and the client-side password hashing, but they should have different salts so that, given one hash (which the client does when it sends the password hash to the server), the encryption key cannot be determined by any operation.
  5. Hash the client-supplied password hash again before storing/comparing on the server. Even though it's actually derived from the user-entered password rather than being the password itself, you still need to re-hash the password hash before storing it, otherwise a malicious actor that gets ahold of your password table could authenticate as any user (though they wouldn't have enough info to decrypt the private keys). This final round of hashing needn't be particularly slow, as the input to it is already the hard-to-brute-force output of a password hashing/key derivation function.

A few other notes:

  • I endorse A. Hersean's suggestion to use ECC instead of RSA, if you can. RSA is an older and more widely-implemented asymmetric algorithm, but it's also slower to generate and use the keys and the keys themselves are longer, necessitating more storage.
  • You mention (in a comment) using crypto to protect against SQL injection. This is an over-engineered non-solution to an already solved problem. It's like trying to use a cheese grater to slice bread, when you have a bread knife right there: it's a lot of effort for something that probably won't even work right. Use the right tool for the job; the solution to SQLi is to use parameterized queries / stored procedures for database access and never concatenate user-supplied strings into the queries (and, if that fails because for some reason you need to let a user-supplied string be used for some part of the query that can't be parameterized, use a well-implemented and trusted input sanitization function rather than encrypting the string or trying any other way to make it safe yourself).
  • There are many, many more risks to web apps and web services than SQLi. Since your system apparently involves message passing, some of the obvious ones to consider are XSS and CSRF, although truly RESTful services should be safe from those. Others are XML recursive or external entity attacks (if you use XML), deserialization attacks (if you use any kind of serialized objects), and of course the myriad forms of denial-of-service attacks (of which you've already exposed yourself to one, by letting clients tell the server to perform an expensive operation).
  • Crypto is really, really hard to get right. You should have somebody who really knows what they're doing look at your full design (not just the partial summary you've given us here), and then take another look at your code, because crypto can be amazingly fragile and if you get one little thing wrong, it can become worthless.
0

I just have a queue in my REST API service for performance, something like this:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class RSAQueue {

    private final int queueCapacity;

    private final Queue<KeyPair> queue = new LinkedBlockingQueue<>();

    private final KeyPairGenerator generator;

    @SneakyThrows
    public RSAQueue(@Value("${rsa.queue.capacity:1000}") int queueCapacity) {
        this.queueCapacity = queueCapacity;
        this.generator = KeyPairGenerator.getInstance("RSA");
        generator.initialize(4096);
    }

    @Scheduled(initialDelayString = "PT1S", fixedDelayString = "PT5S")
    public void generate() {
        if (queue.size() < queueCapacity) {
            queue.offer(generator.genKeyPair());
        }
    }

    public KeyPair genKeyPair() {
        KeyPair keyPair = queue.poll();
        return keyPair == null ? generator.genKeyPair() : keyPair;
    }
}

You must log in to answer this question.

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