I am trying to decrypt a saml response using AES and RSA, and I could decrypt the saml assertion properly. But, the decrypted text is being embedded in to some junk characters, which is causing parsing exceptions.
Below is my code
InputStream privateKeyFileInputStream = Check.class.getClassLoader().getResourceAsStream("rsa_privatekey.key");
rsaPrivateKey = new byte[privateKeyFileInputStream.available()];
privateKeyFileInputStream.read(rsaPrivateKey);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
PrivateKey privKey = keyFactory.generatePrivate(privateKeySpec);
Cipher cipher1 = Cipher.getInstance("RSA/NONE/OAEPWithSHA1AndMGF1Padding", "BC");
cipher1.init(Cipher.DECRYPT_MODE, privKey);
byte[] encryptedMessage = Base64.decodeBase64(aesPrivateKeyEnc.getBytes());
aesPrivateKey = cipher1.doFinal(encryptedMessage);
IvParameterSpec ivSpec = new IvParameterSpec(new byte[16]);
SecretKeySpec key = new SecretKeySpec(aesPrivateKey, "AES");
Cipher cipher2 = Cipher.getInstance("AES/CBC/NoPadding", "BC");
cipher2.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] cipherTextBytes = Base64.decodeBase64(cipherText);
byte[] decryptedMessage = cipher2.doFinal(cipherTextBytes);
String message = new String(decryptedMessage, "UTF8");
Now, the message has
R����=2�W���?<saml:Assertion ...... </saml:Assertion>��fE]����
It seems that your IV value is prefixed to the ciphertext. Instead of a zero IV you should use the first 16 bytes of your ciphertext for cipher2. Don't forget to exclude them from encryption. This explains the garbage at the start.
It also seems that your cipher2 should be configured for padding. This is probably PKCS#7 padding. Please try "AES/CBC/PKCS5Padding" instead of "/NoPadding". If that doesn't work you'll need to update your question with the plaintext in hexadecimals so we can determine which padding is used. That should explain the garbage at the end.
Note that "PKCS5Padding" does perform PKCS#7 padding in Java.
Related
I am trying to generate a key using Java. To be honest I am not that experienced with keys, password, ciphers and encryption.
And from whatever I have searched from this site, I see it as a very common problem. I did some reading and came up with this code that I wrote:
Security.setProperty("crypto.policy", "unlimited");
String valueToEncode = "some_random_text";
SecureRandom secureRandom = new SecureRandom();
byte[] salt = new byte[256];
secureRandom.nextBytes(salt);
KeySpec keySpec = new PBEKeySpec("some_random_password".toCharArray(), salt, 65536, 256); // AES-256
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBEWITHHMACSHA512ANDAES_256");
byte[] key = secretKeyFactory.generateSecret(keySpec).getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] ivBytes = new byte[16];
secureRandom.nextBytes(ivBytes);
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encValue = cipher.doFinal(valueToEncode.getBytes(StandardCharsets.UTF_8));
byte[] finalCiphertext = new byte[encValue.length + 2 * 16];
System.arraycopy(ivBytes, 0, finalCiphertext, 0, 16);
System.arraycopy(salt, 0, finalCiphertext, 16, 16);
System.arraycopy(encValue, 0, finalCiphertext, 32, encValue.length);
System.out.println(finalCiphertext.toString());
This is modified from an answer that I saw on another post. But I still get the "invalid length" error.
The error that I get is:
Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 20 bytes
at com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:87)
at com.sun.crypto.provider.CipherBlockChaining.init(CipherBlockChaining.java:93)
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:591)
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:346)
at javax.crypto.Cipher.implInit(Cipher.java:805)
at javax.crypto.Cipher.chooseProvider(Cipher.java:863)
at javax.crypto.Cipher.init(Cipher.java:1395)
at javax.crypto.Cipher.init(Cipher.java:1326)
at com.att.logicalprovisioning.simulators.Trial.main(Trial.java:47)
Trial.java:47 being the line: cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
Is there a one-size fits all solution to this? Or is it just my lack of understanding?
Any help would be appreciated.
Your key is 20 bytes long because secretKeyFactory.generateSecret(keySpec).getEncoded() returns the password some_random_password.
An easy way to fix the code is to use the key derivation PBKDF2WithHmacSHA512 instead of PBEWITHHMACSHA512ANDAES_256. This generates a key of the specified length based on the password and salt:
...
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
...
However, PBEWITHHMACSHA512ANDAES_256 can also be applied. This algorithm specifies a key derivation with PBKDF2WithHmacSHA512 and subsequent AES encryption. The implementation is functionally identical to yours, but requires a few more changes to the code:
...
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, 65536, ivParameterSpec);
PBEKeySpec keySpec = new PBEKeySpec("some_random_password".toCharArray());
SecretKeyFactory kf = SecretKeyFactory.getInstance("PBEWITHHMACSHA512ANDAES_256");
SecretKey secretKey = kf.generateSecret(keySpec);
Cipher cipher = Cipher.getInstance("PBEWITHHMACSHA512ANDAES_256");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] encValue = cipher.doFinal(valueToEncode.getBytes(StandardCharsets.UTF_8));
...
Two other issues are:
You are using a 256 bytes salt, but only storing 16 bytes when concatenating. To be consistent with the concatenation, apply a 16 bytes salt: byte[] salt = new byte[16].
The output finalCiphertext.toString() returns only class and hex hashcode of the object, s. here. For a meaningful output use a Base64 or hex encoding of the byte[] instead, e.g. Base64.getEncoder().encodeToString(finalCiphertext).
I want to decrypt the EncryptedAssertion. I tried with OpenSaml Decrypter but its not working for me.I am getting Failed to decrypt EncryptedData
I have already ask that question - EncryptedAssertion Decryption failing
While I am waiting for any solution I am trying to decrypt it manually. Its a Hybrid encryption
I tried below code
CipherValue cv = encryptedAssertion.getEncryptedData().getKeyInfo().getEncryptedKeys().get(0).getCipherData().getCipherValue();
String cvalue = cv.getValue();
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey());
String decryptedValue = new String(cipher.doFinal(DatatypeConverter.parseBase64Binary(cvalue)));
I am not sure if I am on the right path, but above decryptedValue is the decryption key for my Encrypted Data.This decryptedValue is not in readable format. Not sure what to do next.
getPrivateKey method
public PrivateKey getPrivateKey(){
Key key = null;
PrivateKey privateKey = null;
try {
KeyStore ks = KeyStore.getInstance("pkcs12", "SunJSSE");
ks.load(new FileInputStream("prvkey.pfx"),"".toCharArray());
Enumeration<String> aliases = ks.aliases();
while(aliases.hasMoreElements()){
String alias = aliases.nextElement();
key = ks.getKey(alias, "".toCharArray());
privateKey = (PrivateKey)key;
}
} catch (Exception e) {
e.printStackTrace();
}
}
Based on the suggestion I coded like below. Not sure if I am doing it correct also I am getting errors
`CipherValue cv = encryptedAssertion.getEncryptedData().getKeyInfo().getEncryptedKeys().get(0).getCipherData().getCipherValue();
String cvalue = cv.getValue();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.UNWRAP_MODE, getPrivateKey());
Key decryptionKey = cipher.unwrap(DatatypeConverter.parseBase64Binary(cvalue), "RSA/ECB/PKCS1Padding", Cipher.SECRET_KEY);
CipherValue cdata = encryptedAssertion.getEncryptedData().getCipherData().getCipherValue();
String cdataValue = cdata.getValue();
byte[] iv = new byte[256 / 16];
IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
Cipher cipher2 = Cipher.getInstance("AES/CBC/PKCS5PADDING");
SecretKeySpec spec = new SecretKeySpec(decryptionKey.getEncoded(), "AES");
cipher2.init(Cipher.DECRYPT_MODE, spec, ivParamSpec );
String decryptedValue = new String(cipher2.doFinal(DatatypeConverter.parseBase64Binary(cdataValue)));`
Error -
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:966)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2121)
UPDATE ::
hope I am doing it correctly based on the comments.
byte[] iv = new byte[256/16];
iv = Arrays.copyOfRange(DatatypeConverter.parseBase64Binary(cdataValue), 0, 16);
byte[] cipherBlock = Arrays.copyOfRange(DatatypeConverter.parseBase64Binary(cdataValue), 16, DatatypeConverter.parseBase64Binary(cdataValue).length);
IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
Cipher cipher2 = Cipher.getInstance("AES/CBC/PKCS5PADDING");
SecretKeySpec spec = new SecretKeySpec(decryptionKey.getEncoded(), "AES");
cipher2.init(Cipher.DECRYPT_MODE, spec, ivParamSpec );
String decryptedValue = new String(cipher2.doFinal(cipherBlock)); // Same error - Given final block not properly padded
I won't provide you a complete answer but I hope to get you on the right track
You should not just simply decrypt the calue with the private key.
First decrypt the KeyInfo value (unwrap the aes key) using RSA/ECB/PKCS1Padding (according to the provided saml snippet)
It should give you a 256 bit (32 bytes) random key used to encrypt data itself
then use the AES key to decrypt the data . Please note that first bytes (128 bit / 16 bytes, aes block size) is used as IV.
further reading
https://www.w3.org/TR/2002/REC-xmlenc-core-20021210/Overview.html#sec-Processing-Encryption
https://gusto77.wordpress.com/2017/10/30/encryption-reference-project/
public static byte[] decrypt(byte[] cryptoBytes, byte[] aesSymKey)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
// https://github.com/onelogin/java-saml/issues/23
String cipherMethod = "AES/CBC/ISO10126Padding"; // This should be derived from Cryptic Saml
AlgorithmParameterSpec iv = new IvParameterSpec(cryptoBytes, 0, 16);
// Strip off the the first 16 bytes because those are the IV
byte[] cipherBlock = Arrays.copyOfRange(cryptoBytes,16, cryptoBytes.length);
// Create a secret key based on symKey
SecretKeySpec secretSauce = new SecretKeySpec(aesSymKey, "AES");
// Now we have all the ingredients to decrypt
Cipher cipher = Cipher.getInstance(cipherMethod);
cipher.init(Cipher.DECRYPT_MODE, secretSauce, iv);
// Do the decryption
byte[] decrypedBytes = cipher.doFinal(cipherBlock);
return decrypedBytes;
}
ISO10126Padding should work....
I want to encrypt an arbitrary text with RSA, but as I read, RSA dont allow to long texts, so firsts, I need to encrypt with AES-256 (for example), then encrypt the AES key with RSA public, add the encrypted text(with AES), and send the message.
At this moment, I'm doing the AES enc-dec. But I'm doing something wrong because is not decrypting the message properly:
First I generate the AES Key:
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(KEY_SIZE_AES);
this.secretKey_AES = keyGenerator.generateKey();
return this.secretKey_AES;
then I encrypt the message:
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey_AES);
byte[] encrypted = cipher.doFinal(message.getBytes("UTF-8"));
String encryptedMessage = Base64.encodeToString(encrypted, Base64.DEFAULT);
return encryptedMessage;
and finally I decrypt it:
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey_AES);
byte[] decrypted = cipher.doFinal(Base64.decode(message,Base64.DEFAULT));
String decryptedMessage = new String(Base64.encode(decrypted, Base64.DEFAULT));
return decryptedMessage;
But the decrypted text is not the same as the original. I'm missing somthing?
Or I forget some step?
Example:
Your code is working properly, but you are encoding the result in BASE64. ("Elias" is "RWxpYXM" in base64). Just change
String decryptedMessage = new String(Base64.encode(decrypted, Base64.DEFAULT));
with
String decryptedMessage = new String(decrypted, "UTF-8");
Note that this method will only work for text strings
i have a AESkey which encrypted by a public key, and later decrypted by a private key
Cipher cipher = Cipher.getInstance("RSA");
PrivateKey privateKey = keyPair.getPrivate();
// decrypt the ciphertext using the private key
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedText = cipher.doFinal(theBytes);
theBytes is a byte[] containing a encrypted AESkey, the question is how to convert the decryptedText back to the AESkey?
I believe you're receiving an RSA-encrypted AES key along with some AES-encrypted data, and you still need to perform the second of 2 encryptions. Right?
So, anyway, you can load a key from the byte array.
SecretKeySpec secretKeySpec = new SecretKeySpec(decryptedText, "AES");
Subsequently you'd do something like this, to decrypt the AES-encrypted data, 'encrypted':
Cipher cipherAes = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipherAes.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] decryptedBytes = cipherAes.doFinal(encrypted);
String decryptedString = new String(decryptedBytes);
The /CBC/PKCS7Padding specification may vary, depending on how it was specified during encryption.
Hope this helps.
I've been stuck on a bug in my code, it will not let me decrypt properly!
I am only passing eight bytes of data to dataBytes and I am passing
a 24 byte key to keyBytes.
I am trying to return the decrypted data as an array of bytes.
I keep getting the bad padding exception.
Thanks!
Here is the code snippet:
private static byte[] DESEdeDecrypt(byte[] keyBytes, byte[] dataBytes){
byte[] decryptedData = null;
try{
DESedeKeySpec keySpec = new DESedeKeySpec(keyBytes, 0);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey key = keyFactory.generateSecret(keySpec);
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.DECRYPT_MODE, key);
decryptedData = cipher.doFinal(dataBytes);
}
catch(Exception e){System.out.println(e);}
return decryptedData;
You must use the same padding to decrypt as you did to encrypt. It is better to set it explicitly rather than to rely on defaults. Best also to specify the mode at both ends as well:
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
DESede is slow and obsolescent. You shouldn't use it except for compatibility with old code. For new work it is better to use AES.