2

I'm calculating two 2048-bit prime numbers using BigInteger's probablePrime method, as follows: BigInteger.probablePrime(2048, new Random());. Respectively, let's call those primes p and q. I'm calculating the private exponent, using the following code: BigInteger.TWO.multiply(r).add(BigInteger.ONE).divide(e); where e is equivalent to BigInteger.valueOf(3) and r is equivalent to a BigInteger whose value is: (p - 1)(q - 1).

Creating the encrypted BigInteger goes as follows: message.modPow(e, r), where message is a BigInteger.

Let's say that I wish to encrypt 774356626352684872522728355634287624183747537718011900969524254770659766752605764866132228010801740792162094. This large integer is "The quick brown fox jumped over the lazy dog." converted to binary, then converted to decimal. My result is 464326058229369014486528960945777245568243099145851675968955902027904135435059026247893552949145149936678174588724345105141605583511438062567406913039976998983678282605288609470234530610515268764924240227134432014767865301287496131771559993377618477929696113174968779730288058725125905006272019930686696412137679303439126584.

No matter how many times I run the above code, it always encrypts to the same exact value. It does not seem to matter which primes it generates- the encrypted value is always the above for that particular message value.

Now here's where it becomes particularly peculiar, if I generate 512-bit primes, the result is unique. Each time I run the above code, generating 512-bit primes instead of 2048 or even 1024-bit primes, it generates a unique result every time it's run. However, if I wish to generate 1024 or 2048-bit primes, the result is always the same, regardless of the primes that are generated.

Can anyone explain why this occurs, or what changes would need to be made for the code to generate unique encrypted integers using 2048-bit primes? Specifically, why does it work for 512 or lower-bit primes, but not 1024 or larger-bit primes? I apologize if this wasn't the most well constructed question, so please do not hesitate to ask for clarification if something is confusing.

Thanks.

EDIT: Here is code to produce the problem:

import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;

public class Yeet {
    public static void main(String[] args) throws IOException {
        int t = (int) (System.currentTimeMillis() / 1000);

        byte[] date = new byte[]{
          (byte) (t >> 24),
          (byte) (t >> 16),
          (byte) (t >> 8),
          (byte) t,
        };  

        BigInteger p = BigInteger.probablePrime(2048, new SecureRandom(date));
        BigInteger q = BigInteger.probablePrime(2048, new SecureRandom(date));
        BigInteger e = BigInteger.valueOf(3);
        BigInteger r = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));

        BigInteger message = new BigInteger("774356626352684872522728355634287624183747537718011900969524254770659766752605764866132228010801740792162094");

        System.out.println(message.modPow(e, r));
    }
}

Run it as many times as you'd like. It always produces 464326058229369014486528960945777245568243099145851675968955902027904135435059026247893552949145149936678174588724345105141605583511438062567406913039976998983678282605288609470234530610515268764924240227134432014767865301287496131771559993377618477929696113174968779730288058725125905006272019930686696412137679303439126584. Now, if we swap 2048 for 512 on lines 16 & 17, each run produces a unique value...

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
broment
  • 76
  • 6
  • 1
    The docs for `Random` make quite clear that it's not suitable for this kind of application and suggest you use `SecureRandom` instead. – David Schwartz Jan 16 '20 at 01:51
  • Thank you for the response. I have swapped Random for SecureRandom. Unfortunately, the problem persists. EDIT: Will try seeding with current time. – broment Jan 16 '20 at 01:54
  • @DavidSchwartz Folks, I updated the post to include a sample of the code I'm working with that reproduces the issue. Thanks! – broment Jan 16 '20 at 02:04
  • @Jason `SecureRandom` is seeded by the OS and doesn't require seeding with the time. That would add negligible entropy. – Maarten Bodewes Jan 16 '20 at 02:34
  • RSA encryption should be `m.modPow(e,n)` where `n` is p * q -- _not_ `r`. Decryption ditto, once you compute `d` correctly, which as below you don't. – dave_thompson_085 Jan 16 '20 at 05:01
  • https://security.stackexchange.com/questions/183179/what-is-rsa-oaep-rsa-pss-in-simple-terms/183330#183330 may also help (partially it may help to understand Maarten's answer). – bartonjs Jan 17 '20 at 17:26

1 Answers1

4

You are performing raw / textbook RSA, where the encryption is just modular exponentiation with the public exponent. Well, that and some conversion or change or interpretation to / from integers, of course.

Now the public exponent in your case is very small: 3. So it is likely that a small input plaintext is smaller after exponentiation than the modulus. The quick brown fox jumped over the lazy dog. is 45 characters / bytes, or 360 bits. If you have a 360 bit number and you perform exponentiation with 3 then you get a value of 360 x 3 = 1080 bits, far below the 2 x 2048 = 4096 bit modulus, so no modular reduction will be performed. 2 x 512 = 1024, so with those size of primes your value is just "a few bits" larger than the modulus, so the modulus value matters.

Of course you could use the fifth prime of Fermat (F4) with value 65537 instead. That will result in modular reduction as 360 x 65537 > 4096. However, to be secure, you should use a padding method such as the ones specified in PKCS#1 v2.2. These will expand the plaintext value so the number representation that depends on the plaintext will be much closer to the modulus, in bits. The result of modular exponentiation will then actually perform at least a few modulus reductions, and is therefore dependent on the different modulus values.

Even more importantly, the PKCS#1 v1.5 or OAEP padding specified in PKCS#1 v2.2 is random itself, so encrypting the same plaintext will result in a different ciphertext even if the same key pair / modulus is used. For larger key sizes even exponent 3 is considered secure although F4 is still preferred by the majority of RSA implementations (OpenSSL, Java, C#/.NET etc. etc.).

Community
  • 1
  • 1
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • @Marteen Bodewes, Thank you very much for your insightful answer! I will certainly look into PKCS#1 v2.2. If I wanted to replace 3 with F4 for the time being, what other changes would need to be made to the code? For example, if I simply swap 3 with 65537, upon decrypting it returns a different value than what I started with. – broment Jan 16 '20 at 02:27
  • Um, you never said anything about key size I see now, just a second. Still, you need to make sure your RSA implementation is correct before you see your values return, e.g. N = P x Q where N is the modulus. Probably best to compare it with a known good implementation. – Maarten Bodewes Jan 16 '20 at 02:41
  • See [this](https://en.wikipedia.org/wiki/RSA_(cryptosystem)#Key_generation) (Wikipedia) for a decent description of key generation. You're not there yet. – Maarten Bodewes Jan 16 '20 at 02:50
  • Thanks for the reply. Based on that article, I reckon I might be calculating the private exponent incorrectly, though I'm not sure. As mentioned in the OP, the current formula that I'm using is: `BigInteger d = BigInteger.TWO.multiply(r).add(BigInteger.ONE).divide(e);` This was working for the small public exponent (3), but it doesn't decrypt the message encrypted using F4 properly. – broment Jan 16 '20 at 03:05
  • 1
    @broment: your formula for `d` is not correct in general, but it has a $1/e$ chance of accidentally giving a correct result. For e=3 you might have been lucky -- but even so decryption must be modulo n not r; see Q. e=65537 would require hugely more luck to get correct d. – dave_thompson_085 Jan 16 '20 at 05:00
  • Ended up using `BigInteger#modInverse`. Thanks for the helpful resources, everyone. – broment Jan 16 '20 at 14:33