What's wrong with IBM's JCE provider? - java

I have a JCE test that works fine with all Sun JDKs I have tried, but fails with various IBM J9 JDKs (e.g. 1.6.0 build pwi3260sr8-20100409_01(SR8)). The exception below happens when the cipher is initialized in encrypt mode. Why can the IBM JCE not use its own private key? Am I missing something in my code?
public void testBasicKeyGeneration() throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
BadPaddingException, NoSuchProviderException, SignatureException {
KeyPairGenerator generator = KeyPairGenerator.getInstance( "RSA" );
generator.initialize( 2048 );
KeyPair pair = generator.generateKeyPair();
String data1 = "123456789012345678901234567890123456789012345678901234567890";
Cipher cipher = Cipher.getInstance( "RSA" );
cipher.init( Cipher.ENCRYPT_MODE, pair.getPrivate() );
byte[] encrypted = cipher.doFinal( data1.getBytes() );
cipher.init( Cipher.DECRYPT_MODE, pair.getPublic() );
byte[] decrypted = cipher.doFinal( encrypted );
String data2 = new String( decrypted );
assertEquals( "en/decryption failed", data1, data2 );
}
Here is the stack trace:
java.security.InvalidKeyException: Private key cannot be used to encrypt.
at com.ibm.crypto.provider.RSA.engineInit(Unknown Source)
at javax.crypto.Cipher.a(Unknown Source)
at javax.crypto.Cipher.a(Unknown Source)
at javax.crypto.Cipher.init(Unknown Source)
at javax.crypto.Cipher.init(Unknown Source)
at test.Test.testBasicKeyGeneration(LicenseHelperTest.java:56)

There is a solution, see http://www-01.ibm.com/support/docview.wss?uid=swg1IV18625
with the property
-Dcom.ibm.crypto.provider.DoRSATypeChecking=false
you can use private keys to encrypt data.

I don't know this for sure but I believe that the JCE has an embedded policy limiting encryption to the public key and decryption to the private key.
In the example code the encryption was done with the private key. This would require the public key to decrypt, meaning that anyone with the public key could access the encoded data. Although this has it's uses it is not the accepted pattern and the IBM implementation may be "protecting" you from accidentally creating encrypted data that was publicly readable.
The fact that it tested properly when these were reversed tends to confirm my suspicions but I haven't yet found an official document stating as much.

IBM insists private keys cannot be used for encryption and public keys cannot be used for decryption, so they either see this artificial restriction as a feature, or someone is seriously confused here.
Here is how I worked around this problem:
RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) ks.getKey(keyAlias, ksPassword.trim().toCharArray());
RSAPublicKeySpec spec = new RSAPublicKeySpec(privateKey.getModulus(),privateKey.getPrivateExponent());
Key fakePublicKey = KeyFactory.getInstance("RSA").generatePublic(spec);
encryptCipher.init(Cipher.ENCRYPT_MODE, fakePublicKey);
Essentially, I created a public key object with private key's crypto material. You will need to do the reverse, create a private key object with public key's crypto material, to decrypt with public key if you want to avoid the "Public key cannot be used to decrypt" exception.

I recently ran in to the same problem. This was eventually solved by using the bouncy castle implementation and adding this line to the java.security file
security.provider.1=org.bouncycastle.jce.provider.BouncyCastleProvider

#T.Rob commented that you may have made a mistake in encrypting with the private key. If "everyone" knows the public key, then anyone can decrypt your file. IBM's JCE behaviour is thus protecting people against this mistake.
I can see the logic of that.
However, there may be cases where you really do need to encrypt with the private key; e.g. as part of a protocol that needs to prove that you know the private key corresponding to a published public key.
If this is really what you want to do, you probably need to use a recent Sun JCE implementation (older Sun JCEs didn't implement RSA), or Bouncy Castle.

#Stephen C / #FelixM: IBM seems to be completely clueless about how RSA cryptography works and how it is intended to be used. Basically both operations (encrypt / decrypt) must be available for the public AND private key.
Encrypt with public key is needed to transmit the client-side part of the pre master secret in SSL/TLS handshakes. The server needs to decrypt with its private key. But if they negotiate something like ECDHE_RSA the server needs to SIGN parts of the handshake with the private key - thats encrypt with PrivateKey. Vice versa the client needs to decrypt with the public key from the certificate of the server to verify the hash value of the signature. (proving authenticity of the message)
So if I try to run ECDHE_RSA (server-side) on latest IBM JDK 7 the following happens:
java.security.InvalidKeyException: Private key cannot be used to encrypt.
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614)
at java.lang.Thread.run(Thread.java:777)
at com.ibm.crypto.provider.RSASSL.engineInit(Unknown Source)
at javax.crypto.Cipher.init(Unknown Source)
at javax.crypto.Cipher.init(Unknown Source)
at java.security.Signature$CipherAdapter.engineInitSign(Signature.java:1239)
at java.security.Signature$Delegate.init(Signature.java:1116)
at java.security.Signature$Delegate.chooseProvider(Signature.java:1076)
at java.security.Signature$Delegate.engineInitSign(Signature.java:1140)
at java.security.Signature.initSign(Signature.java:522)
at net.vx4.lib.tls.core.TLSSignature.createSignature(TLSSignature.java:120)
As you can see we're using "Signature" and call "initSign", which requires indeed a PrivateKey. This proves IBM being clueless about this fact and obviously they don't even
have valid regression tests!
Use another crypto provider and don't believe IBM until they change their mind.
Best regards,
Christian

Related

Java 8 using ECIES cipher with a SealedObject fails with 'NullPointerException' - string cannot be null

I am trying to use a ECIES cipher to instantiate a SealedObject, but it fails with a NullPointerException. I am using Java JDK1.8.0_72 with Bouncy Castle bcprov-jdk15on v1.53 running on Windows 10. The code looks like this:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ECIES");
kpg.initialize(new ECGenParameterSpec("secp256r1"));
KeyPair keyPair = kpg.generateKeyPair();
Cipher cipher = Cipher.getInstance("ECIES");
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
String toEncrypt = "Hello";
// Check that cipher works ok
cipher.doFinal(toEncrypt.getBytes());
// Using a SealedObject to encrypt the same string fails with a NullPointerException
SealedObject sealedObject = new SealedObject(toEncrypt, cipher);
The code successfully calls 'cipher.doFinal()' but fails when instantiating the SealedObject. The stack trace is:
java.lang.NullPointerException: string cannot be null
at org.bouncycastle.asn1.ASN1OctetString.<init>(Unknown Source)
at org.bouncycastle.asn1.DEROctetString.<init>(Unknown Source)
at org.bouncycastle.jcajce.provider.asymmetric.ies.AlgorithmParametersSpi.engineGetEncoded(Unknown Source)
at java.security.AlgorithmParameters.getEncoded(AlgorithmParameters.java:362)
at javax.crypto.SealedObject.<init>(SealedObject.java:179)
I'm trying to avoid specifying a particular provider (i.e. Bouncy Castle) and avoiding any provider-specific classes such as IESParameterSpec because the component uses external configuration to specify the algorithms to be used. The component is intended to be used as part of a messaging library in a fluid cluster of nodes where each node may use a different algorithm for encryption, so a SealedObject seems like a reasonable choice because it can be used to pass the algorithm used (any message that uses encryption uses the receiver's public key so the receiver must have the corresponding private key to decrypt the message).
Any thoughts or suggestions would be most welcome.
David Hook at Bouncy Castle had a look and identified an issue in org.bouncycastle.jcajce.provider.asymmetric.ies.AlgorithmParametersSpi.engineGetEncoded and provided a fix in 1.55b04. I tested this out and it has resolved this issue.
Thanks again for your help Maarten.

How to store a SecretKey within a Java KeyStore protected by a PublicKey (RSAPasswordProtection)?

I want to store a SecretKey within a Java KeyStore protected by a PublicKey. When loading the protected KeyEntry i would like to
get the protected key byte-array to manually unwrap it later on with a PrivateKey.
let the KeyStore handle the unwrapping when handing over the PrivateKey.
Using the setEntry()-Method with an already wrapped byte-Array is possible. Also getting the wrapped byte-Array back can be done by using the getEntry()-Method. To encrypt a SecretKey the setEntry()-Method supports the usage of a ProtectionParameter. The only ProtectionParameter i could find was the PasswordProtection parameter.
Does anyone know about a RsaProtection for Java KeyStore? Or is there another way around to be able to wrap SecretKeys using a PublicKey and getting it back using a PrivateKey?
The Java key stores are certainly not able to handle this; they primarily use symmetric encryption to protect the key stores. It is possible to wrap and unwrap keys though. I've shown this using OAEP instead of the less safe "RSA" (PKCS#1) encryption:
Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
rsa.init(Cipher.WRAP_MODE, keyPair.getPublic());
byte[] wrapped = rsa.wrap(aesKey);
rsa.init(Cipher.UNWRAP_MODE, keyPair.getPrivate());
SecretKey unwrappedAESKey = (SecretKey) rsa.unwrap(wrapped, "RSA", Cipher.SECRET_KEY);

Wrong key usage in encryption

This class encrypt data values, to do this first I get the cipher as follows,
KeyStore primaryKeyStore = getKeyStore(keyStoreFile, password, keyType, provider);
java.security.cert.Certificate certs = primaryKeyStore.getCertificate(aliasName);
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, certs);
But the last line of the above code gives the following error.
What does the wrong key usage means?
Can't I use this certificate for encryption purposes?
Is there separate certificate for encryption?
Caused by: java.security.InvalidKeyException: Wrong key usage
at javax.crypto.Cipher.init(Cipher.java:1640)
at javax.crypto.Cipher.init(Cipher.java:1549)
at my.test.ciphertool.CipherTool.initCipher(CipherTool.java:167)
... 1 more
It means exactly what it says it means in the Javadoc:
If the certificate is of type X.509 and has a key usage extension field marked as critical, and the value of the key usage extension field implies that the public key in the certificate and its corresponding private key are not supposed to be used for the operation represented by the value of opmode, an InvalidKeyException is thrown.

How to avoid using BouncyCastle as provider with jCryption

I have been using jCryption for a secure login. On the client i am using the JavaScript package and on the Java decryption i am using BouncyCastle jar to decrypt.
The problem is that it works OK in Tomcat but when i take the same webapp and deploy on Jboss i am having problems loading the BouncyCastle jar.
My question is: is there a way to encrypt using jCryption that will produce a more standardized RSA output which will allow me to use other security providers?
jcryption isn't as secure as you might think:
http://www.securityfocus.com/archive/1/520683
My recommendation... do something similar to this:
http://www.frostjedi.com/terra/dev/rsa/index.php
The following URL elaborates:
http://area51.phpbb.com/phpBB/viewtopic.php?f=84&t=33024&start=0
Generally speaking, if you want security, avoid JavaScript cryptography, use SSL/TLS instead.
The main problems are:
insufficient quality of implementation of cryptographic routines (e.g. random numbers)
the client has no idea whether the script may have been tampered with by a MITM attacker, even if the JavaScript library is of sufficient quality.
You're not actually adding much security by using JavaScript cryptography on your website unfortunately.
Here is the snippet for RSA decoding compatible with jCryption. We assume that encExternalKey is what jCryption send in key parameter on handshake call. modulus and secretExponent are taken from 100_1024_keys.inc.php file that comes with jCryption.
RSAPrivateKeySpec privateKeySpec =
new RSAPrivateKeySpec(new BigInteger(modulus, 10), new BigInteger(secretExponent, 10));
RSAPrivateKey privateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(privateKeySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
StringBuilder externalKeyBuf =
new StringBuilder(new String(cipher.doFinal(new BigInteger(encExternalKey, 16).toByteArray())));
String externalKey = externalKeyBuf.reverse().toString().trim();

What is the proper way to perform authenticated encryption in Java?

Authenticated encryption requires that we use some accepted standard for encrypting and authenticating a message. So we both encrypt the message and compute a MAC on the message to verify it has not been tampered with.
This question outlines a way to perform password based key strengthening and encryption:
/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
/* Encrypt the message. */
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("Hello, World!".getBytes("UTF-8"));
But as far as I can tell, this does not compute any MAC on the ciphertext and so would be insecure. What is the accepted standard for performing authenticated encryption in Java?
I would recommend using GCM mode encryption. It is included in the latest JDK (1.7) by default. It uses a counter mode encryption (a stream cipher, no padding required) and adds an authentication tag. One big advantage is that it requires only a single key, whereas HMAC adds another key to the mix. Bouncy Castle has an implementation as well, which is moslty compatible with one provided by Oracle.
GCM mode encryption is also features in a TLS RFC, and in XML encrypt 1.1 (both not final). GCM mode provides all three security features: confidentiality, integrity and authenticity of the data send. The String would be "AES/GCM/NoPadding" instead of the CBC one you are now deploying. As said, make sure you have the latest JDK from Oracle, or have Bouncy Castle provider installed.
Also check out my answer here, which is mostly about String encoding, but I've succesfully tried GCM mode too - see the comment.
When transferring files from one server to another through secure ftp, I use private/public key pairs with the private key residing on the "from" server and the public key residing on the "to" server.
Using private/public key pairs is a secure standard when transferring files.
I believe it would also be a secure means in the context of a Java application.
Check out Generating and Verifying Signatures and Generate Public and Private Keys
for more details on using a private/public key pair setup for digital signatures in Java.

Categories