Bouncy Castle vs Java default RSA with OAEP - java

Can someone explain to me why this code throws javax.crypto.BadPaddingException: Decryption error on the final line when it's decrypting the key?
// Given an RSA key pair...
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keyPair = keyGen.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
// ... and an AES key:
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256);
SecretKey aesKey = keyGenerator.generateKey();
// When I encrypt the key with this Bouncy Castle cipher:
Cipher encryptionCipher = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding", "BC");
encryptionCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedKey = encryptionCipher.doFinal(aesKey.getEncoded());
// Then trying to decrypt the key with this cipher...
Cipher decryptionCipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");
decryptionCipher.init(Cipher.DECRYPT_MODE, privateKey);
// ... throws `javax.crypto.BadPaddingException: Decryption error` here:
decryptionCipher.doFinal(encryptedKey);
Is the following statement from https://stackoverflow.com/a/27886397/66722 also true for RSA with OAEP?
"RSA/ECB/PKCS1Padding" actually doesn't implement ECB mode encryption.
It should have been called "RSA/None/PKCS1Padding" as it can only be
used to encrypt a single block of plaintext (or, indeed a secret key).
This is just a naming mistake of Sun/Oracle.
If so, I would expect these transformations to be equivalent and my test above to pass. The same padding has been specified in both, so why the BadPaddingException?
Either way, I would appreciate a layman's explanation of what the difference is.

For similar Stackoverflow questions with more information please see Maarten Bodewes answers to this and this.
The "mode" part of the transformation string has no effect. The problem is different defaults used by different providers. This is unfortunate and very definitely suboptimal. Should we blame Sun/Oracle? I have no opinion beyond being dissatisfied with the result.
OAEP is a fairly complicated construction with two different hash functions as parameters. The Cipher transform string lets you specify one of these, which you have specified as SHA-256. However, the MGF1 function also is parameterized by a hash function which you cannot specify in the cipher transformation string. The Oracle provider defaults to SHA1 whereas the BouncyCastle provider defaults to SHA-256. So, in effect, there is a hidden parameter that is critical for interoperability.
The solution is to specify more fully what these hidden parameters are by supplying an OAEPParameterSpec to the Cipher.init(...) method as in the following example:
Cipher encryptionCipher = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding", "BC");
OAEPParameterSpec oaepParameterSpec = new OAEPParameterSpec("SHA-256", "MGF1",
MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
encryptionCipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParameterSpec);
// ...
// ...
// ...
Cipher decryptionCipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");
oaepParameterSpec = new OAEPParameterSpec("SHA-256", "MGF1",
MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
decryptionCipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParameterSpec);
The first one is effectively a no-op, because those are already the defaults for Bouncycastle.

Related

AES 256 decryption - Is IV safe to share?

Following on this question and its answer, I am creating an application that given a password string, will convert a plaintext and store its ciphertext, the salt generated and the initialization vector in a text file.
In the following code :
public String decrypt(CryptGroup cp) throws Exception {
String plaintext = null;
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, cp.getSalt(), ITERATIONS, KEY_SIZE);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(cp.getIv()));
plaintext = new String(cipher.doFinal(cp.getCipher()), "UTF-8");
return plaintext;
}
public CryptGroup encrypt(String plainText) throws Exception {
byte[] salt = generateSalt();
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_SIZE);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal(plainText.getBytes("UTF-8"));
return new CryptGroup(ciphertext, salt, iv);
}
The CryptGroup object contains those 3 parameters (ciphertext, salt, iv : byte arrays for that matter).
Is it safe to store the initialization vector?
The answer in that question clearly states that the salt doesn't need to be secret, obviously the ciphertext can be also available, but what about the iv parameter?
Edit
If it is not safe to share, is it possible to retrieve the original iv from the salt alone?
Yes, IV's can be public information. You can use a calculation as IV, as long as you never use the combination of key and IV twice. In other words, you should be able to share just the salt, as long as you change the salt for each encryption.
Furthermore, for CBC it is required that IV "looks like random" to an attacker, especially when used for the same key. So a usual scheme is to use some output of PBKDF2 as IV data. Those particular bits should of course not be used to create the key as well, but it is OK to split the output size.
This has some drawbacks as PBKDF2 will use many more rounds if you request more than 160 bits of information (for SHA1). So you may concatenate the output of PBKDF2 and a counter (0 for the key, 1 for the IV) and use e.g. SHA256 to generate a 128 bit key (the 16 leftmost bytes) and 128 bit IV (the 16 rightmost bytes).
Let's explore some variants to this scheme:
You could also use a different hash such as SHA-512 to create a larger output and split it to get a key and IV, but beware that such a function may not be available everywhere. Java 8 should have "PBKDF2WithHmacSHA512" though (for the SecretKeyFactory).
You can also generate one PBKDF2 output and then use HKDF or HKDF-Expand to derive both a key and IV. Trouble is that HKDF / HKDF-Expand is not directly available from Java. Bouncy Castle does have that method though, because I send in an implementation of various KDF's.
Yet another way is to generate a new IV using SecureRandom, but in that case you need to store both the salt and IV. This might be useful if you need to encrypt multiple messages using the same key. In that case you can generate and store an IV for each separate message. This is a fine method if you are able to simply store 16 additional bytes.
In principle you could also use an all zero IV as long as you never reuse the same key (i.e. never reuse the same password/salt combination). However, in that case you might want to use AES-256 to avoid multi-target attacks.

What is precise name of the following AES-128 algorithm?

I have the following decryption algorithm implemented in Java. Is information "AES128" enough for other people to implement it in other languages or a more accurate definition is required?
// input data
byte[] secretKey = { ... };
byte[] encryptedData = { ... };
// decryption
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(secretKey);
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128, random);
SecretKey sk = keyGen.generateKey();
SecretKeySpec keySpec = new SecretKeySpec(sk.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec IV16 = new IvParameterSpec(keySpec.getEncoded());
cipher.init(Cipher.DECRYPT_MODE, keySpec, IV16);
byte[] decrypted = cipher.doFinal(encryptedData);
The name of the encryption algorithm itself is AES-128. It's being used in the cipher block chaining mode (this describes how to connect the encryption of multiple sequential blocks), with PKCS #5 padding (this describes how to pad messages up to the size of a full block). Other languages might have different techniques for specifying mode and padding.
AES is the algo, and 128bit the key size. I would say it's pretty clear ...
Essential properties of that algorithm are:
AES
128-bit key
CBC blocks
PKCS-5 aka PKCS-7
Also the following custom tactics used:
custom IV generation from a pass phrase used (bad style)
custom key generation from a pass phrase
Changing any point, changes everything: bytes generated and decryption code.

init of a Cipher object in Java

When encrypting data in java as following:
SecureRandom sr = new SecureRandom();
DESKeySpec dks = new DESKeySpec(rawKeyData);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey key = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key, sr);
// why the sr is necessary to init a Cipher object?
byte data[] = str.getBytes();
byte[] encryptedData = cipher.doFinal(data);
Why the a SecureRandom object sr is necessary to init a Cipher object? As for the decryption side there is also need an SecureRandom object which is not the same as the one generated in the encryption size. Is theseSecureRandom objects just for parity digit?
The SecureRandom object is optional. If you don't select one, Java will use a default source of randomness. See the documentation of this method to understand how one is selected.
The Cipher Javadocs explains the purpose of this argument:
If this cipher (including its underlying feedback or padding scheme) requires any random bytes (e.g., for parameter generation), it will get them from random.
So, in your particular case, you are probably not making use of this item at all. Nothing in your DES usage will require random data (since you appear to be using ECB mode). If you were using CBC-mode, then I assume the IV would be randomly generated from the SecureRandom object.

Why does my AES Cipher throw an InvalidKeyException on init of DECRYPT_MODE

Why would this init succeed:
Cipher AESCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AESCipher.init(Cipher.ENCRYPT_MODE, secretKey, secRandom);
while this fails:
Cipher AESCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AESCipher.init(Cipher.DECRYPT_MODE, secretKey, secRandom);
Throwing an
Exception in thread "main" java.security.InvalidKeyException: Parameters missing
The secretKey is generated by a KeyGenerator, and the secureRandom by SecureRandom.getInstance("SHA1PRNG") with a random static seed set.
Thanks
As correctly surmised by CodeInChaos, the SecureRandom instance is used to derive a random IV when the AESCipher instance is created with Cipher.ENCRYPT_MODE. However, you supply it as a parameter when creating a Cipher instance in decrypt mode. This little pointless code fragment shows an example.
public static void main(String[] args) throws Exception {
SecureRandom secRandom = SecureRandom.getInstance("SHA1PRNG");
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(128, secRandom);
Key secretKey = kg.generateKey();
Cipher AESCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AESCipher.init(Cipher.ENCRYPT_MODE, secretKey, secRandom);
IvParameterSpec iv = new IvParameterSpec(AESCipher.getIV());
AESCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AESCipher.init(Cipher.DECRYPT_MODE, secretKey,iv, secRandom);
}
Also, your claim that you are initializing your SecureRandom instance with a static seed suggest a misunderstanding of that class. SecureRandom does not guarantee that you will get the same output when you provide the same seed. If you look carefully at the Javadocs you'll see that it attempts to provide some true entropy from other sources if at all possible.
EDIT 1:
Thanks to owlstead for his usual thoroughness in reviewing answers. See his answer to a related question for additional discussion. The source code for the SHA1PRNG is available online here. It is a little tricky to follow but if you provide a seed before asking the instance for any random bytes then the output will be completely deterministic. So my earlier statement is incorrect.
Just read the JavaDoc of the init method with SecureRandom that you are applying:
If this cipher requires any algorithm parameters that cannot be
derived from the given key, the underlying cipher implementation is
supposed to generate the required parameters itself (using
provider-specific default or random values) if it is being initialized
for encryption or key wrapping, and raise an InvalidKeyException if
it is being initialized for decryption or key unwrapping. The
generated parameters can be retrieved using getParameters or getIV
(if the parameter is an IV).
You will have to transfer the encrypted IV to the decryption method, e.g. by prepending it to the cipher text. The IV may be transferred in the clear. Use IvParameterSpec instead of SecureRandom to set the IV for decryption.

Java: How to create a RSA Public Key from the String

I have the byte array of the RSA Public Key. I found on the internet that I can create a real PublicKey object of it by using this code:
PublicKey publicKey =
KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(bytes));
But every time I run this code, I'm getting another result for the encrypted data using that key. I'm sure the data I want to encrypt is always the same, so does the byte array representing the key.
Is this normal?
Here is my code always producing another output:
byte[] keyBytes = Base64.decodeBase64(rsa_1024_public_key);
// rsa_1024_public key is a constant String
Cipher c = Cipher.getInstance("RSA");
PublicKey publicKey =
KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(keyBytes));
c.init(Cipher.ENCRYPT_MODE, publicKey);
return c.doFinal(password.getBytes());
This is probably a part of the asymmetric encryption algorithm?
Thanks.
RSA is non-determinstic.
You can make it deterministic by selecting a non-random padding mode; however, that will not be secure.

Categories

Resources