We have a client server system where client(Android phone) and server(spring ) both are using java.security.KeyFactory to get an instance of java.security.KeyFactory as shown below:
KeyFactory factory = KeyFactory.getInstance("RSA");
But if we do that, when we use this factory to encrypt data, the server gives a different output and the client gives different output. When we checked providers, it was coming to SunRsaSign for server and was OpenSSLRSA for the client. So we tried to set the same on the client using the following:
KeyFactory factory = KeyFactory.getInstance("RSA", "SunRsaSign");
But we get java.security.NoSuchProviderException error. Similarly when we try to set OpenSSLRSA on server, they also face the same error.
Complete code to encrypt is same on server and client is following:
String pubKey = "<key here>"
byte[] keyData = DatatypeConverter.parseHexBinary(pubKey);
System.out.println("key data" + Arrays.toString(keyData));
KeyFactory factory = KeyFactory.getInstance("RSA");
//System.out.println("provide = " + factory.getProvider());
PublicKey pub = factory.generatePublic(new X509EncodedKeySpec(keyData));
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, pub);
byte[] secretMessageBytes = msg.getBytes(StandardCharsets.UTF_8);
System.out.println("secret msg" +Arrays.toString(secretMessageBytes));
byte[] encryptedMessageBytes = encryptCipher.doFinal(secretMessageBytes);
System.out.println("enc data" +Arrays.toString(encryptedMessageBytes));
encryptedMessageBytes generated are different. Can that be a problem? I think it is because of different Providers being used by different platform.
Can somebody pls help me on how to set the provider for KeyFactory or how to remove the decryption error(javax.crypto.BadPaddingException)?
The posted code, used on both the Android and Java/Spring sides, only specifies the algorithm and not the padding when instantiating the cipher:
Cipher.getInstance("RSA")
Without explicit specification of the padding, the default padding of the providers of both sides is used. However, different providers generally define different default paddings (e.g. NoPadding, PKCS1Padding or OAEPPadding). The different paddings cause the decryption to fail, because a prerequisite for successful decryption is that the same padding is used as for encryption.
To avoid such things, the padding should always be specified when instantiating the cipher, especially in a cross-platform environment, e.g.:
Cipher.getInstance("RSA/ECB/PKCS1Padding")
On the left is the algorithm and on the right the padding. Note that the middle part (ECB) has no meaning for RSA (it is an artifact of using the scheme of symmetric encryption specifying the operation mode in the middle, which is not defined for asymmetric encryption).
Fixing the issue with the explicit specification of the padding proves that the padding was indeed the problem.
I can only speculate about the default paddings used in your environment.
I could not test the OpenSSLRSA provider, as it is not available in my environment. On my machine Android (API Level 28, P) applies the AndroidOpenSSL provider (aka Conscrypt). This defaults to NoPadding, while Java's SunJCE provider defaults to PKCS1Padding. Encryption on Android and decryption on Java would result in an exception (or non-removed padding for the other direction).
You can determine the default padding of your environment as follows: Encrypt with the default padding (by specifying only the algorithm) and vary the padding on decryption until decryption is successful and the original plaintext is decrypted.
Related
I'm encrypting some data on a Phoenix webserver:
private_key = ExPublicKey.load!("private.pem")
token = %{username: user.username, mobile_phone: user.mobile_phone, email: user.email}
payload = Poison.encode!(token)
{:ok, signature} = ExPublicKey.encrypt_private(payload, private_key)
And decrypting it on the Java (actually Android) client as follows:
try {
byte[] keyBytes = Base64.decode(Constants.RSA_PUBLIC_KEY.getBytes(), Base64.DEFAULT);
X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(encodedKeySpec) ;
Cipher cipher = Cipher.getInstance("RSA") ;
cipher.init(Cipher.DECRYPT_MODE, publicKey) ;
//
Log.e(DEBUG_TAG, jwt) ; // received token
String payload = new String(Base64.decode(jwt, Base64.DEFAULT), "UTF-8") ; // java does UTF16, elixir does UTF8
Log.e(DEBUG_TAG, payload) ; // base64 decoded token
byte[] cipherText = cipher.doFinal(payload.getBytes("UTF-8")) ; // decrypt
String token = new String(Base64.decode(cipherText, Base64.URL_SAFE), "UTF-8") ; // cipher text is urlencoded
Log.e(DEBUG_TAG, token) ;
return null ;
} catch (Exception e) {
e.printStackTrace();
}
There are no exceptions on the Phoenix side but trying to decrypt the token on java results in the exception:
java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block
at com.android.org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.engineDoFinal(CipherSpi.java:459)
at javax.crypto.Cipher.doFinal(Cipher.java:1502
If the input is too large for the RSA modulus it should have resulted in error on the webserver. So I'm wondering what is actually wrong.
UPDATE: seems like there was an issue with library. The output produced by signing the SHA256 digest of some data returns 344 bytes, whereas its supposed to be 256 bytes for the key length used. Reverted to using Erlang's public_key module and it works fine now.
Is not clear the real purpose and that makes things difficult, but if you are trying to issue JSON Web Tokens, as it seems, your implementation is completely wrong
JWT is digitally signed, not encrypted
encrypt with private key != Digital signature
you are "decrypting" the entire token instead of verifying the signature, which should be the last part of a JSON Web Token like this hhhh.pppp.ssss.
#zaph described the error, but it would not occur if you use digital signature. It is not possible to fix your code so consider to re-implement it
Signing is not the same as encrypting using a private key. Although both would be using modular exponentiation with the private exponent signing and encryption use different padding methods. More information here. You should basically not see hashing and signing as separate operations: the hashing is part of the signature generation and verification.
The reason why your code failed is however different: the signature is likely encoded using base64. Base64 will generate an output size of ceiling(256/3)×4. This of course equals 344 characters / bytes. So you first would have to decode the result before decrypting it.
The solution to this problem is to use hybrid encryption. Namely, this involves using RSA to asymmetrically encrypt a symmetric key.
Randomly generate a symmetric encryption (say AES) key and encrypt the plaintext message with it. Then, encrypt the symmetric key with RSA. Transmit both the symmetrically encrypted text as well as the asymmetrically encrypted symmetric key.
The receiver can then decrypt the RSA block, which will yield the symmetric key, allowing the symmetrically encrypted text to be decrypted.
This can be shown more formally as the following. Let MM be the plaintext, KAESKAES be the randomly chosen AES key, and KPuKPu be the receiver's public RSA key you already have.
C1=EAES(M,KAES)
C1=EAES(M,KAES)
C2=ERSA(KAES,KPu)
C2=ERSA(KAES,KPu)
Then, send both C1C1 and C2C2.
Let KPrKPr be the receiver's private RSA key. The receiver can then recover MM as
KAES=DRSA(C2,KPr)
KAES=DRSA(C2,KPr)
M=DAES(C1,KAES)
M=DAES(C1,KAES)
(To allow streaming decryption or large messages, you would usually send C2C2 first and then (the much larger) C1C1.)
I'm trying to sign and validate signature in RSA+SHA1 with PKCS1 v2.0 padding.
I haven't found in the documentation (JDK or JCE) which algorithm/padding I have to use.
It seems that I've to use OAEP padding but I didn't succeed:
For validation, I've tried via Cipher in DECRYPT_MODE with the public key sent by the signer:
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING", "SunJCE");
cipher.init(Cipher.DECRYPT_MODE, pub); //exception !
But I got an exception:
java.security.InvalidKeyException: OAEP cannot be used to sign or verify signatures
at com.sun.crypto.provider.RSACipher.init(RSACipher.java:303)
at com.sun.crypto.provider.RSACipher.engineInit(RSACipher.java:207)
...
(For unknown reason, OAEPWITHSHA1ANDMGF1PADDING is only accepted with ENCRYPT_MODE+PUBLIC KEY or DECRYPT_MODE+PRIVATE KEY, the opposite that I want to do....)
For signature, I'm trying to use Signature but I don't know which algorithm to use:
Signature mySig2 = Signature.getInstance("SHA1withRSAandMGF1");
Doesn't work :
java.security.NoSuchAlgorithmException: SHA1withRSAandMGF1 Signature not available
at java.security.Signature.getInstance(Signature.java:229)
...
Can somebody help me?
Hope I'm not too late to respond to this. Very recently I had to face the same issue with RSA encryption. My Signer wants to do the verification to the data that is sent through POST. This is how I managed to work on that in Java.
Step 1 - Add following into your project
bcprov-ext-jdk14-1.45.jar
commons-codec-1.4.jar
Step 2 - Declare your Cipher
private Cipher cipher;
Step 3 - Call below in your class constructor
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
this.cipher = Cipher.getInstance("RSA/NONE/OAEPWithSHA1AndMGF1Padding");
Step 4 - Get your PublicKey from public key text string
public PublicKey getPublic(String pubKey) throws Exception {
byte[] publicBytes = Base64.decodeBase64(pubKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
Step 5 - Verification method
public boolean verfyRSA(String signature, String data, PublicKey key) throws Exception {
byte[] sign = Base64.decodeBase64(signature);
byte[] dat = Base64.decodeBase64(data);
Signature rsaVerify = Signature.getInstance("SHA1withRSAandMGF1");
rsaVerify.initVerify(key);
rsaVerify.update(dat);
return rsaVerify.verify(sign);
}
Step 6 - Usage
Yourclass yc = new YourClass();
PublicKey publicKey = yc.getPublic("Your PublicKey without leading and trailing -------text------");
boolean b = yc.verfyRSA("POST_Signature", "POST_data", publicKey);
Hope this helps. Mind the getInstance() parameters. These are the ones that do the trick. Cheers
I'm trying to sign and validate signature in RSA+SHA1 with PKCS1 v2.0 padding.
This is a strange requirement.
First of all, RSA is secure even if PKCS#1 v1.5 compatible padding is used. It is however not secure when SHA-1 is used to hash the input/message, for the simple reasons that SHA-1 is not considered secure anymore for that kind of purpose.
Second, although PKCS#1 v2.0 contains a new padding mode for signature generation called PSS or - to be more precise - RSASSA-PSS it also contains the single signature generation mode from 1.5 of the standard called RSASSA-PKCS1-v1_5. So at least in theory your requirements are not clear.
Now PKCS#1 v1.5 padding for encryption is known to be broken against padding oracle attacks. For that reason alone, OAEP encryption introduced in PKCS#1 v2.0 is generally available.
PSS for signature generation has a better security proof, but PKCS#1 v1.5 is still considered secure. This is likely why Oracle / SUN didn't include PSS within Java SE JCA. If you want it you can vote for it here.
I haven't found in the documentation (JDK or JCE) which algorithm/padding I have to use. It seems that I've to use OAEP padding but I didn't succeed.
OAEP stands for Optimal Asymmetric Encryption Padding. Padding for encryption cannot/should not be used for signature generation.
After adding the Bouncy Castle provider the RSA PSS becomes available. You can use your (insecure) scheme with "SHA1withRSA/PSS" or indeed the identical "SHA1withRSAandMGF1". It's likely that Oracle's Java SE will support RSA/PSS once the TLS 1.3 specification is finalized.
Java: Oracle jre1.8.0_45
Provider: BC, BouncyCastle v1.52
Cipher: AES 256bit keys (security policy installed)
AEAD Mode: GCM
Algo: AES/GCM/NoPadding
I have a perfectly working AES encryption/decryption with the parameters shown above. Then during my debug I added a simple TAMPERING simulation by changing data in the Encrypted buffer before decrypting. I expected an AEADBadTagException to be thrown but it didn't happen. I did NOT use any upedateAAD() yet, we are talking about pure ciphered data pay-load.
I do that tampering simply like this, I overwrite the first byte of the byte[] after it already contains the encrypted data and the 16 extra bytes of the authentication tag.
// set-up for encryption, key, IV, etc...
...
try
{
String sPlainText="The non-encrypted (AES) message.";
byte[] baEncrypted=oCIPH.doFinal(sPlainText.getBytes());
MetaLogbook.info(baEncrypted); // Shows well encrypted buffer
// Tampering simulations
baEncrypted[0]=0x67;
// re-initialize for decryption, same key and IV...
String sDecryptedText=new String(oCIPH.doFinal(baEncrypted),"UTF-8");
MetaLogbook.info(sDecryptedText);
// The above log line shows the plain text with a different first letter
// each time that i change 0x67 in other values. The rest of the message
// matches the plain text on input. I can see the 16 extra bytes of the
// authentication tag appended to the clear text.
}
catch(Exception e)
{
// I expected to come here due to a AEADBadTagException but I never
// come here.
MetaLogbook.error(e);
}
The resulting decrypted text changes at the first character as i change the value that i assign while simulating the tampering. It changes in a non-linear way. While 0x65 produces a 'c', 0x67 produces a '?' and so on. The rest of the plain message remains correct, only the first character of the decrypted output seems affected.
I understood from the standard Java 8 doc of the Cipher class that in AEAD GCM mode the authentication tag is created at encryption time (and it was because I see it in my encrypted output byte[] appended at the end) and verified at decryption time (and I provided the full output of encryption including the 16 bytes tag as decryption input) and if the tag would not verify with that data (including AAD data that i don't use now but will) it would throw a AEADBadTagException. In my code it doesn't do that.
I tried this with data that is a multiple of 16 bytes as well as with data that is not. The result is the same for both. If using the same tampering (0x67) value that first letter in the plain text output changes if the message gets longer but that makes sense. The mentioned erroneous first character 'c' becomes a '6' if i add some bytes to the message so that it is not a multiple of 16. In the used AES/GCM/NoPadding the length must not be a multiple of 16 anyway.
Is this a mis-understanding of the documentation, is there some other method that needs to be called to 'enable' this throw behaviour (I could find any), or doesn't BounceyCastle throw it (I understood that a provider needs to implement the crypto classes ISP such that everything behaves as described in the Java 8 Docs Cipher class.
I could not compare with the SunJCE provider because it doesn't support AES/GCM/NoPadding.
Does anyone has some extra info.
TIA
UPDATE 29/AUG: Added code to show that identical code throws with SunJCE and not with BC provider as part of discussion in comments.
private static void testing()
{
try
{
// Unremark these lines to see it work
//Security.addProvider(new BouncyCastleProvider()); // "BC"
//Cipher oCIPH=Cipher.getInstance("AES/GCM/NoPadding", "BC");
// Unremark these lines to see it fail
Cipher oCIPH=Cipher.getInstance("AES/GCM/NoPadding", "SunJCE");
// Make a quick and dirty IV and Symmetric Key
byte[] baIV="EECE34808EF2A9AC".getBytes("UTF-8");
byte[] baKey="010F05E3E0104EB59D10F37EA8D4BB6B".getBytes("UTF-8");
// Make IV and Key (well KeySpec for AES) object. Use IV parspec because
// defaults to 128bit Authentication tag size & works in both GCM & CBC.
IvParameterSpec ps=new IvParameterSpec(baIV);
SecretKeySpec sk=new SecretKeySpec(baKey,"AES");
// Unremakr one line, either shrtline (multiple of 16 bytes) or long line
//String sPlainText="The non-encrypted (AES) message.";
String sPlainText="The non-encrypted (AES) message. Everything after the . makes this NOT a multiple of 16 bytes.";
// Encrypt
oCIPH.init(Cipher.ENCRYPT_MODE, sk, ps);
byte[] baEncrypted=oCIPH.doFinal(sPlainText.getBytes());
// Decrypt
oCIPH.init(Cipher.DECRYPT_MODE, sk, ps);
String sDecryptedText=new String(oCIPH.doFinal(baEncrypted),"UTF-8");
}
catch(Exception e)
{
MetaLogbook.log("Security Tools Exception",e);
}
}
The code above can be run with SunJCE or with BouncyCastle by unremarking the the wanted lines at the top. In BC these code runs and does what it is expected to do. With SunJCE provider unremarked an error is thrown:
class java.security.InvalidAlgorithmParameterException: Unsupported
parameter: javax.crypto.spec.IvParameterSpec#4fccd51b
com.sun.crypto.provider.CipherCore.init (CipherCore.java:509)
com.sun.crypto.provider.AESCipher.engineInit (AESCipher.java:339)
javax.crypto.Cipher.init (Cipher.java:1394) javax.crypto.Cipher.init
(Cipher.java:1327)
The original post had two issues under discussion. One has been solved (the AEADBadTagException) the other remains pending (see UPDATE 29/AUG in original post).
The solved problem:
I had recently to write exception code for the usages of the Key/SecretKey Class with AES to use SecretKeySpec instead. The change induced an error which influence the path the code followed and was corrected now in the context of searching for the non throwing of the AEADBadTagException. The fact that all the rest kept working was because the flow change resulted to initialize both times for Encryption instead for Decryption the second time.
What I don't understand is that decryption worked correctly anyway. AES is a symmetric algorithm but it has an S-Box and Reverse S-Box and one would think that therefore encryption cannot just be used in stead of decryption as in full symmetric ciphers such as DES.
The second issue problem remains pending:
class java.security.InvalidAlgorithmParameterException: Unsupported parameter: javax.crypto.spec.IvParameterSpec#4fccd51b
It can be reproduced by the provided code by just selecting the providers at the top of the code and leave all the rest unchanged. The code works with BC, not with SunJCE.
I see there is a Metalogbook line that may have to be changed in your own logging code.
Although the SunJCE provider isn't one I use for the Ciphering and the GCM problem is solved as far as i am concerned I'll keep following this question to supply more information for the SunJCE throw if needed.
UPDATE: By doing some further digging I found the problem of the IVParamSpec throw. BC accepts this object for both CBC and GCM and defaults the authentication tag to 128bit. The SunJCE on the other hand requires specifically a GCMParamSpec object and an explicit size for the authentication tag for GCM and accepts the IVParamSpec for CBC but not for GCM.
As per my knowledge both are same but one is working on one PC while same code says:
javax.crypto.NoSuchPaddingException: OAEPWITHSHA-256ANDMGF1PADDING unavailable with RSA on another machine.
When I am removing dash - from the name (OAEPWITHSHA256ANDMGF1PADDING) it starts running on another machine but leads error to some other line bad padding exception.
What could be the reason?
Sample code for Hint
I am using jdk1.7.0_71 32bit:
private byte[] decryptSecretKeyData(byte[] encryptedSecretKey, byte[] iv, PrivateKey privateKey) throws Exception
{
try {
Provider provider= new sun.security.pkcs11.SunPKCS11(keyStoreFile1);
Security.addProvider(provider);
LOG.info("**************Inside decryptSecretKeyData***********************");
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING", provider);
// decrypting the session key with rsa no padding.
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
/* The reason is RSA OAEP SHA256 is not supported in HSM. */
byte[] decKey = rsaCipher.doFinal(encryptedSecretKey);
OAEPEncoding encode = new OAEPEncoding(new RSAEngine(), new SHA256Digest(), iv);
LOG.info("******************RSAPublicKey rsaPublickey = (*****************************");
java.security.interfaces.RSAPublicKey rsaPublickey = (java.security.interfaces.RSAPublicKey) publicKeyFile;
RSAKeyParameters keyParams = new RSAKeyParameters(false, rsaPublickey.getModulus(), EXPONENT);
encode.init(false, keyParams);
LOG.info("******************encode.processBlock(decKey, 0, decKey.length);************************");
byte decryptedSecKey[] = encode.processBlock(decKey, 0, decKey.length);
return decryptedSecKey;
} catch (InvalidCipherTextException e) {
LOG.info("*******************Failed to decrypt AES secret key using RSA :**********************");
throw new Exception("Failed to decrypt AES secret key using RSA :" + e.toString());
}
}
RSA/ECB/OAEPWITHSHA256ANDMGF1PADDING and RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING are different alias but both refers to the same algorithms, so in this sense there is not difference at all.
The thing is that you're using a PKCS#11 (cryptographic token interface) to cipher your data. According to java PKCS#11 reference:
The Sun PKCS#11 provider, in contrast to most other providers, does not implement cryptographic algorithms itself.
Instead, it acts as a bridge between the Java JCA and JCE APIs and the
native PKCS#11 cryptographic API, translating the calls and
conventions between the two. This means that Java applications calling
standard JCA and JCE APIs can, without modification, take advantage of
algorithms offered by the underlying PKCS#11 implementations, such as,
for example,
Cryptographic Smartcards, Hardware cryptographic accelerators, and
High performance software implementations. Note that Java SE only
facilitates accessing native PKCS#11 implementations, it does not
itself include a native PKCS#11 implementation. However, cryptographic
devices such as Smartcards and hardware accelerators often come with
software that includes a PKCS#11 implementation, which you need to
install and configure according to manufacturer's instructions.
Summarized, if you're using a PKCS#11, the use of the algorithms depends on vendors native implementation (.dll on windows, .so on linux ...) and some times on specific program connector so: check if in the both PCs you're using the same drivers/program version for your PKCS#11 token and the both are correctly installed because probably there is an error in one of them which doesn't allows you to use RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING correctly.
Hope this helps,
Both algorithms are provided by the different security provider. The
RSA/ECB/OAEPWITHSHA256ANDMGF1PADDING
is provided by the Bouncy Castle provider while
RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING
is provided by the SUN JCE provider. In our case, we are able to successfully use Bouncy Castle provider algorithm but if I replace that with SUN JCE algorithm then it gives following error:
Exception in thread "main" javax.crypto.BadPaddingException: lHash mismatch
at sun.security.rsa.RSAPadding.unpadOAEP(RSAPadding.java:425)
at sun.security.rsa.RSAPadding.unpad(RSAPadding.java:274)
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:356)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:382)
The situation is more complex.
The RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING is nominally provided by SunJCE.
The RSA/NONE/OAEPWITHSHA256ANDMGF1PADDING is provided by BC.
However BC is able to take over and act as a provider of RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING if it is installed before the SunJCE provider (using Security.installProviderAt). Unfortunately, BC does not parametrize this algorithm the same way as SunJCE does it. BC implementation of that algorithm is thus different (!) than one provided by SunJCE, which will be source of padding errors.
SunJCE RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING is using SHA-1 for MGF1 function and SHA-256 for label hash (which is almost always an empty byte array and therefore a static value).
BC RSA/NONE/OAEPWITHSHA256ANDMGF1PADDING is using SHA-256 for MGF1 function and SHA-256 for label hash. BC will also use the same parameters if it is being asked to provide RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING.
Advice: always explicitly specify security provider to make sure that correct provider is used. Alternatively explicitly specify the OAEP parameters and don't rely on defaults (recommended).
I've reported the issue to BC, so maybe it will get fixed.
I am developing a BlackBerry application where the server is in Java. Whenever AES encrypted data is sent to server along with PKCS5 Formatting,The server gets bad padding exception while decrypting it.
Is that the Blackberry or the server code? Have you tried using the standard JCE classes? something like this:
Cipher aes = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec key = new SecretKeySpec(yourKeyBytes, "AES");
aes.init(Cipher.DECRYPT_MODE, key);
byte[] cleartext = aes.update(ciphertext, 0, ciphertext.length);
Make sure you're doing the padding etc in the right order first: pad then encrypt, decrypt then unpad.
Check the transmitted data length and make sure it's a multiple of blocksize.
Make sure blocksize is consistent in all the calls.
Make sure your crypto provider settings are exactly matching.