2

I'm trying to use RSA encryption in Java.

I'm generating a public key and using it to encrypt text. My problem is that when I pass in the same text and the same key two time, the encrypted results are different. This means I can't use my encryption to test if entered text is equal to a stored result of a previous encryption.

This is my encryption class:

   import java.security.InvalidKeyException;
   import java.security.KeyPair;
   import java.security.KeyPairGenerator;
   import java.security.NoSuchAlgorithmException;
   import java.security.PublicKey;
   import java.util.Arrays;

   import javax.crypto.BadPaddingException;
   import javax.crypto.Cipher;
   import javax.crypto.IllegalBlockSizeException;
   import javax.crypto.NoSuchPaddingException;
   /**
    * The class encrypts text using an RSA algorithm.
    *
    */
   public class RSAEncryption {
       //RSA algorithm
       private final String ALGORITHM = "RSA";

/**
 * The generateKey method generates a public key for use in RSA encryption.
 * @return key a PublicKey for use in RSA encryption.
 */
public PublicKey generateKey(){
    KeyPair key = null;
    KeyPairGenerator keyGen;
    try {
        keyGen = KeyPairGenerator.getInstance(ALGORITHM); //gets instance of the alogrithm
        keyGen.initialize(1024); //a 1021 bit key
        key = keyGen.generateKeyPair(); //makes a pair
    } catch (NoSuchAlgorithmException e) {

        e.printStackTrace();
    }
    return key.getPublic(); //returns the public key. Private key never stored.
}

    /**
    * The encrypt method takes in text and a key and encrypts the text using the RSA encryption algorithm.
    * @params text a String, the text to encrypt.
    * @params key a PublicKey to use in encryption.
    * @returns encryptedText a byte array representing the result of the encryption.

public byte[] encrypt(String text, PublicKey key){
    byte[] encryptedText = null;
    Cipher cipher;
    try {
        cipher = Cipher.getInstance(ALGORITHM); //gets instance of RSA
        cipher.init(Cipher.ENCRYPT_MODE, key); //in encryption mode with the key
        encryptedText = cipher.doFinal(text.getBytes()); //carry out the encryption
    } catch (NoSuchAlgorithmException e) {          
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {            
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {         
        e.printStackTrace();
    } catch (BadPaddingException e) {           
        e.printStackTrace();
    } catch (InvalidKeyException e) {       
        e.printStackTrace();
    }
    return encryptedText; //return encrypted result
}

/**
 * The authenticate method checks if entered text, once encrypted, matches the stored byte[].
 * @param text a String, the text to encrypt.
 * @param stored a byte[], the result of a prior encryption.
 * @param key a PublicKey, a result of the generateKey method.
 * @return boolean, true if the text is valid, false otherwise.
 */

public boolean authenticate(String text, byte[] stored, PublicKey key){
    byte[] encryptText = encrypt(text,key); //encrypt the entered text
    return Arrays.equals(stored, encryptText); //check if the stored and entered byte[] are the same.
}
} 

I've written JUnit tests for this:

import static org.junit.Assert.*;

import java.security.PublicKey;
import java.util.Arrays;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;


public class RSAEncryptionTest {

RSAEncryption cipher;
String text;

@Before
public void setUp(){
    cipher = new RSAEncryption();
    text = "text";
}

@Test
public void testEncryptionGenerateKeyGeneratesANewKeyWhenCalled(){

    PublicKey key = cipher.generateKey();

    assertEquals(false,key.equals(cipher.generateKey()));
}

@Test
public void testEncryptionEncryptMethodRepeatablyEncrypts(){

    PublicKey key = cipher.generateKey();

    byte[] encrypted = cipher.encrypt(text,key);
    Assert.assertArrayEquals(encrypted, cipher.encrypt(text,key));
    //test fails
}


@Test
public void testEncryptionAuthenticateMethodReturnsTrueWhenValidTextPassedIn(){

    PublicKey key = cipher.generateKey();

    byte[] encrypted = cipher.encrypt(text,key);

    assertEquals(true,cipher.authenticate(text,encrypted,key));
            //test fails
}


@Test
public void testEncryptionAuthenticateMethodReturnsFalseWhenInvalidTextPassedIn(){

    PublicKey key = cipher.generateKey();

    byte[] encrypted = cipher.encrypt(text,key);

    assertEquals(false,cipher.authenticate("text1",encrypted,key)); 
} 

}

The second and third tests fail.

Any ideas how to repeatably encrypt text using RSA?

2 Answers 2

3

RSA is a public-key encryption scheme. It sounds like you actually want to use a hashing algorithm (e.g. SHA-256 or SHA-512). I say this because you say:

This means I can't use my encryption to test if entered text is equal to a stored result of a previous encryption.

If this is your goal, you should use a hashing algorithm. By design, RSA encryption should include a padding step to ensure that the ciphertext differs:

To avoid these problems, practical RSA implementations typically embed some form of structured, randomized padding into the value m before encrypting it. This padding ensures that m does not fall into the range of insecure plaintexts, and that a given message, once padded, will encrypt to one of a large number of different possible ciphertexts.

-- http://en.wikipedia.org/wiki/RSA_%28algorithm%29

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

4 Comments

Thanks. I'd written a similar class using SHA1 but wanted to try out RSA. I thought I'd probably got a fundamental misunderstanding of how the algorithm works, thanks for the explanation.
Public key schemes are generally the most resource-intensive and you should only use them if you absolutely must. A good set of classes to write for RSA testing/understanding would be something like digital signatures.
I'm pretty sure that you have got a fundamental misunderstanding of the algorithm. It looks like you are looking for RSA Signature generation. I would suggest you read the PKCS#1 specifications. They are not that hard to read and you need to understand them if you want to create your own protocol around RSA.
@mikey if you use PKCS#1 v1.5 signing then you would get the same result for identical messages. That is however more or less a side effect of using the older v1.5 signature scheme. If you would use PKCS#1 PSS scheme then the signature would be different each time.
2

The output of an RSA cipher is not the same each time for a given plaintext when using an appropriate padding scheme (generally PKCS#1 or OAEP padding). Encrypting a given plaintext will result in different ciphertext each time. If the cipher generated the same output for a given input every time it would be a security vulnerability.

That being said you can force Java to use a non padded RSA cipher by using the spec "RSA/ECB/NOPADDING" for Cipher.getInstance(String). Doing so will result in your tests passing, but as I said earlier this is not very secure.

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.