Thread safety with RSA Cipher

2019-03-20 21:14发布

问题:

As the titles says, i would like to know how to best use Cipher instance in multi threaded environment given the RSA algorithm.

I have read a couple of questions on the topic, and from what i gathered:

  • Cipher is not thread safe as it maintains internal state while encrypting/decrypting

  • if AES is used, then doFinal() will reset Initialization vector to last known value and therefore new Cipher instance should be generated each time

My Questions

  • Is it alright to invoke cipher.init() only once if chosen algorithm is RSA ? This is contrary to second bullet as shown in the code bellow. I believe so as there is no initialization vector. Also, the cipher will only be used to decrypt data.

  • Should i synchronize only on cipher.doFinal() invocation ?

  • What is the common way to handle multiple threads requesting cryptography services, should i have pool of ciphers as a blocking queue behind some proxy?

Code sample:

public class RsaPrototype {

private static PrivateKey privKey;
private static Cipher cipher;
private static final String PRIVATE_KEY_PATH ="./privK.pem";

/*
 * ALGORITHM/BLOCKING_MODE/PADDING_SCHEMA
 */
private static final String CIPHER_SPECIFICATION = "RSA/None/NoPadding";
private static final String RSA_PROVIDER ="BC";

static {

    Security.addProvider(new BouncyCastleProvider());
    importPrivateKey();
    cipher = getCipher();

}

/**
 * Initializes cipher with RSA algorithm, without blocking mode and padding.
 * Implementation provider is bouncy castle.
 * 
 * @return cipher instance.
 */
private static Cipher getCipher() {

    try 
    {

            Cipher cipher = Cipher.getInstance(CIPHER_SPECIFICATION, RSA_PROVIDER);
            cipher.init(Cipher.DECRYPT_MODE, privKey);
            return cipher;

    } catch (NoSuchAlgorithmException | NoSuchPaddingException | NoSuchProviderException e) {

        throw new RuntimeException(e.getMessage());
    }
}

/**
 * Imports public key from the given .PEM file into application cache.
 */
private static void importPrivateKey() {

    try (BufferedReader reader = 
            new BufferedReader(new FileReader(PRIVATE_KEY_PATH));

        PEMParser pemParser = 
            new PEMParser(reader);) {


        privKey = new JcaPEMKeyConverter().getPrivateKey((PrivateKeyInfo) pemParser.readObject());
    }

    catch (IOException ignorable) {
         // not handled
        }
    }

public static String decrypt(byte[] encryptedText) {

    byte[] plainText;

    synchronized (cipher) {

         plainText = cipher.doFinal(encryptedText);
    }

    return new String(plainText, StandardCharsets.UTF_8);
  }
}

回答1:

In case somebody else reads this, there shouldn't be need to reinitialize cipher for every call with RSA. Although, pool of ciphers could be used for performance boost.

I have written quick load test to verify this.

It seems that it is enough to synchronize on cipher.doInit() and use single Cipher instance for decryption.

private static Queue<String> results = new ConcurrentLinkedQueue<String>();

@Test
public void test() throws InterruptedException {

    String plainText = "some text to verify data integrity";

    String encryptedText = Encrypt.encrypt(plainText);


    for (int i = 0; i < 5000; i++) {

        new Thread( () -> { results.add(Decrypt.decrypt(encryptedText)); })
        .start();;          
    }

    Thread.sleep(5000);

    assertTrue(results.size() == 5000);

    while(!results.isEmpty()) {

        assertTrue(results.poll().equals(plainText));
    }
} 


回答2:

Encryption (and writing messages) is inherently synchronous. Using multiple threads would not be my approach. Consider a message aabb. With multiple threads that might become abba or baba or abab or bbaa. Note that the internal state of the cipher is also synchronous in that manner ... To get aabb out, you must send aabb in.