C# AES Decryption - mac check in GCM failed - java

Trying to do AES decrypt the data using BounceCastle, getting mac check in GCM failed error at line: output = cipher.DoFinal(cipherData);
https://github.com/psraju1/CSharpApplePayDecrypter for full code
Error:
mac check in GCM failed
BouncyCastle.Crypto
at Org.BouncyCastle.Crypto.Modes.GcmBlockCipher.DoFinal(Byte[] output, Int32 outOff)
at Org.BouncyCastle.Crypto.BufferedAeadBlockCipher.DoFinal(Byte[] output, Int32 outOff)
at Org.BouncyCastle.Crypto.BufferedAeadBlockCipher.DoFinal(Byte[] input, Int32 inOff, Int32 inLen)
at Org.BouncyCastle.Crypto.BufferedCipherBase.DoFinal(Byte[] input)
at ApplePayDecrypter.ApplePay.DoDecrypt(Byte[] cipherData, Byte[] encryptionKeyBytes) in ApplePayDecrypter.cs:line 107
Code:
protected byte[] RestoreSymmertricKey(byte[] sharedSecretBytes)
{
byte[] merchantIdentifier = GetHashSha256Bytes("");//applePayRequest.MerchantIdentifier);
ConcatenationKdfGenerator generator = new ConcatenationKdfGenerator(new Sha256Digest());
byte[] COUNTER = { 0x00, 0x00, 0x00, 0x01 };
byte[] algorithmIdBytes = Encoding.UTF8.GetBytes((char)0x0d + "id-aes256-GCM");
byte[] partyUInfoBytes = Encoding.UTF8.GetBytes("Apple");
byte[] partyVInfoBytes = merchantIdentifier;
byte[] otherInfoBytes = Combine(Combine(algorithmIdBytes, partyUInfoBytes), COUNTER);//, partyVInfoBytes);
generator.Init(new KdfParameters(sharedSecretBytes, otherInfoBytes));
byte[] encryptionKeyBytes = new byte[16];
generator.GenerateBytes(encryptionKeyBytes, 0, encryptionKeyBytes.Length);
return encryptionKeyBytes;
}
private byte[] DoDecrypt(byte[] cipherData, byte[] encryptionKeyBytes)
{
byte[] output;
try
{
KeyParameter keyparam = ParameterUtilities.CreateKeyParameter("AES", encryptionKeyBytes);
ParametersWithIV parameters = new ParametersWithIV(keyparam, symmetricIv);
IBufferedCipher cipher = GetCipher();
cipher.Init(false, parameters);
try
{
output = cipher.DoFinal(cipherData);
}
catch (Exception ex)
{
throw new ApplicationException("Invalid Data");
}
}
catch (Exception ex)
{
throw new ApplicationException("There was an error occured when decrypting message.");
}
return output;
}
public IBufferedCipher GetCipher()
{
return CipherUtilities.GetCipher("AES/GCM/NoPadding");
}
private static byte[] GetHashSha256Bytes(string text)
{
byte[] bytes = Encoding.UTF8.GetBytes(text);
SHA256Managed hashstring = new SHA256Managed();
byte[] hash = hashstring.ComputeHash(bytes);
return hash;
}
protected static byte[] Combine(byte[] first, byte[] second)
{
byte[] ret = new byte[first.Length + second.Length];
Buffer.BlockCopy(first, 0, ret, 0, first.Length);
Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
return ret;
}
Actually, I'm trying to decrypt this for ApplePay, and converting sample Java code to C#. Let me know, if you want to take a look at JAVA code
Here is the full code in JAVA & C#. Please check it.
https://github.com/psraju1/CSharpApplePayDecrypter

I had the same problem, so I forked the repo above, made the necessary changes, cleaned it up and pushed it to GitHub.
https://github.com/fscopel/CSharpApplePayDecrypter
Happy Coding!

had the same problem. Please try not to hash the merchant identifier. It is already hashed. And ignore the first two bytes of it, before set to partyVInfo. When you get merchantIdentifier from certifcate there is ".#" at the beginning of the hash. This must be deleted
byte[] partyVInfo = ExtractMIdentifier();
private byte[] ExtractMIdentifier()
{
X509Certificate2 merchantCertificate = InflateCertificate();
byte[] merchantIdentifierTlv = merchantCertificate.Extensions["1.2.840.113635.100.6.32"].RawData;
byte[] merchantIdentifier = new byte[64];
Buffer.BlockCopy(merchantIdentifierTlv, 2, merchantIdentifier, 0, 64);
return Hex.Decode(Encoding.ASCII.GetString(merchantIdentifier));
}
Party V Info
The SHA-256 hash of the merchant identifier. This value is a fixed-length bit string.
I don't use SHA-256 hash and deleted the first two bytes of the merchant identifier. Now it works.

After following Lou's advice, I needed two more changes to get it to work.
I got rid of COUNTER and used partyVInfoByes
I changed encryptionKeyBytes to be an array of 32 instead of 16 bytes, since it's 256 bit ECC (32 * 8 = 256)
RestoreSymmetricKey now looks like this:
protected byte[] RestoreSymmertricKey(byte[] sharedSecretBytes)
{
byte[] merchantIdentifier = ExtractMIdentifier();
ConcatenationKdfGenerator generator = new ConcatenationKdfGenerator(new Sha256Digest());
byte[] algorithmIdBytes = Encoding.UTF8.GetBytes((char)0x0d + "id-aes256-GCM");
byte[] partyUInfoBytes = Encoding.UTF8.GetBytes("Apple");
byte[] partyVInfoBytes = merchantIdentifier;
byte[] otherInfoBytes = Combine(Combine(algorithmIdBytes, partyUInfoBytes), partyVInfoBytes);
generator.Init(new KdfParameters(sharedSecretBytes, otherInfoBytes));
byte[] encryptionKeyBytes = new byte[32];
generator.GenerateBytes(encryptionKeyBytes, 0, encryptionKeyBytes.Length);
return encryptionKeyBytes;
}

Related

How to handle BadPaddingException During AES256 encryption in C# and decryption in Java

I don't know why an error is coming up.
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
I understand that this error occurs when the incorrect key is used during the decryption. However, if you look at the test results result below, you can see that both C# and Java are the same (Key, IV, Salt is Base64 encoded).
C# Test Result
Java Test Result
It's the same!(Key, IV, Salt)
But the current BadpaddingException error is generated. What could be the problem?
I am attaching my source file.
C# (Encryption)
class AES {
private readonly static string keyStr = "This is Key";
private readonly static string vector = "This is Vector";
public static Rfc2898DeriveBytes MakeKey(string password){
byte[] keyBytes = System.Text.Encoding.UTF8.GetBytes(password);
byte[] saltBytes = SHA512.Create().ComputeHash(keyBytes);
Rfc2898DeriveBytes result = new Rfc2898DeriveBytes(keyBytes, saltBytes, 65536);
return result;
}
public static Rfc2898DeriveBytes MakeVector(string vector){
byte[] vectorBytes = System.Text.Encoding.UTF8.GetBytes(vector);
byte[] saltBytes = SHA512.Create().ComputeHash(vectorBytes);
Rfc2898DeriveBytes result = new Rfc2898DeriveBytes(vectorBytes, saltBytes, 65536);
return result;
}
public static void Encrypt(String inputFile, String outputFile) {
using (RijndaelManaged aes = new RijndaelManaged()){
//Create Key and Vector
Rfc2898DeriveBytes key = AES.MakeKey(AES.keyStr);
Rfc2898DeriveBytes vector = AES.MakeVector(AES.vector);
//AES256
aes.BlockSize = 128;
aes.KeySize = 256;
// It is equal in java
// Cipher _Cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = key.GetBytes(32); //256bit key
aes.IV = vector.GetBytes(16); //128bit block size
//processing Encrypt
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
byte[] encrypted;
using (MemoryStream msEncrypt = new MemoryStream()) {
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
byte[] inputBytes = File.ReadAllBytes(inputFile);
csEncrypt.Write(inputBytes, 0, inputBytes.Length);
}
encrypted = msEncrypt.ToArray();
}
string encodedString = Convert.ToBase64String(encrypted);
File.WriteAllText(outputFile, encodedString);
}
}
}
Java (Decryption)
public class AES256File {
private static final String algorithm = "AES";
private static final String blockNPadding = algorithm+"/CBC/PKCS5Padding";
private static final String password = "This is Key";
private static final String IV = "This is Vector";
private static IvParameterSpec ivSpec;
private static Key keySpec;
public static void MakeKey(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException{
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
MessageDigest digest = MessageDigest.getInstance("SHA-512");
byte[] keyBytes = password.getBytes("UTF-8");
// C# : byte[] saltBytes = SHA512.Create().ComputeHash(keyBytes);
byte[] saltBytes = digest.digest(keyBytes);
//256bit
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 65536, 256);
Key secretKey = factory.generateSecret(pbeKeySpec);
byte[] key = new byte[32];
System.arraycopy(secretKey.getEncoded(), 0, key, 0, 32);
SecretKeySpec secret = new SecretKeySpec(key, "AES");
setKeySpec(secret);
}
public static void MakeVector(String IV) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException{
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
MessageDigest digest = MessageDigest.getInstance("SHA-512");
byte[] vectorBytes = IV.getBytes("UTF-8");
byte[] saltBytes = digest.digest(vectorBytes);
// 128bit
PBEKeySpec pbeKeySpec = new PBEKeySpec(IV.toCharArray(), saltBytes, 65536, 128);
Key secretIV = factory.generateSecret(pbeKeySpec);
byte[] iv = new byte[16];
System.arraycopy(secretIV.getEncoded(), 0, iv, 0, 16);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
setIvSpec(ivSpec);
}
public void decrypt(File source, File dest) throws Exception {
Cipher c = Cipher.getInstance(blockNPadding);
c.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
fileProcessing(source, dest, c);
}
public void fileProcessing(File source, File dest, Cipher c) throws Exception{
InputStream input = null;
OutputStream output = null;
try{
input = new BufferedInputStream(new FileInputStream(source));
output = new BufferedOutputStream(new FileOutputStream(dest));
byte[] buffer = new byte[input.available()];
int read = -1;
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
byte[] deryptedBytes = c.doFinal(buffer); // -----------------------> Error!! Showing!
byte[] decodedBytes = Base64.getDecoder().decode(deryptedBytes);
String decodeString = new String(decodedBytes, "UTF-8");
decodedBytes = decodeString.getBytes(StandardCharsets.UTF_8);
output.write(decodedBytes);
}finally{
if(output != null){
try{output.close();}catch(IOException e){}
}
if(input != null){
try{input.close();}catch(IOException e){}
}
}
}
I have verified as below.
Verification Key and IV in C#
//Key Verification
var salt = Convert.ToBase64String(saltBytes);
Console.Write("Salt Result : ");
Console.WriteLine(salt);
var result_test = Convert.ToBase64String(result.GetBytes(32));
Console.Write("Key Test Result: ");
Console.WriteLine(result_test);
//IV Verification (Salt is Using same code)
var result_test = Convert.ToBase64String(result.GetBytes(16));
Console.Write("IV Test Result: ");
Console.WriteLine(result_test);
Verification Key and IV in Java
//Key Verification
/* print Salt */
String base64 = Base64.getEncoder().encodeToString(saltBytes);
System.out.println("Salt Result : " + base64);
/* print Key */
String result_test = Base64.getEncoder().encodeToString(key);
System.out.println("Key Test Result : " + result_test);
/* print generated Key */
System.out.println("Secret Key Result : " + Base64.getEncoder().encodeToString(secret.getEncoded()));
//IV Verification (Salt is Using same code)
/* print IV */
String result_test = Base64.getEncoder().encodeToString(iv);
System.out.println("IV Test Result : " + result_test);
/* print generated IV */
System.out.println("IV Result : " + Base64.getEncoder().encodeToString(ivSpec.getIV()));
Updated
c# .netframework 4.5 / Java8 modified what #Topaco said and confirmed that it worked well.
I want to say thank you very much to #Topaco and #Gusto2, and I'm going to make changes to the parts that have been modified in security, just as #Gusto2 said!
1) In the C# Encrypt-method the plain text is encrypted first and then Base64-encoded. Thus, in the decryption process the data must be Base64-decoded first and then decrypted. Currently this is handled in the wrong order i.e. the data are decrypted first and then decoded. Therefore, in the Java fileProcessing-method replace
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
with
while((read = input.read(buffer)) != -1) {
byte[] bufferEncoded = buffer;
if (read != buffer.length) {
bufferEncoded = Arrays.copyOf(buffer, read);
}
byte[] bufferDecoded = Base64.getDecoder().decode(bufferEncoded);
output.write(c.update(bufferDecoded));
}
2) It's not necessary to pass buffer (or bufferDecoded) to the doFinal-method, since that was already done in the update-method. Thus,
byte[] deryptedBytes = c.doFinal(buffer);
must be replaced with
output.write(c.doFinal());
3) Since the Base64-decoding is already done in 1) in the try-block all lines following the doFinal-statement have to be removed. Overall, this results in
try {
input = new BufferedInputStream(new FileInputStream(source));
output = new BufferedOutputStream(new FileOutputStream(dest));
byte[] buffer = new byte[input.available()];
int read = -1;
while((read = input.read(buffer)) != -1) {
byte[] bufferEncoded = buffer;
if (read != buffer.length) {
bufferEncoded = Arrays.copyOf(buffer, read);
}
byte[] bufferDecoded = Base64.getDecoder().decode(bufferEncoded);
output.write(c.update(bufferDecoded));
}
output.write(c.doFinal());
}
4) The size of the buffer has to be a multiple of 4 in order to ensure a proper Base64-decoding. Thus, it's more reliable to replace
byte[] buffer = new byte[input.available()];
with
byte[] buffer = new byte[4 * (input.available() / 4)];
As long as the data are read in one chunk (which is not guaranteed, see e.g. https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/InputStream.html#available()) there is no problem. However, if the data are read in several chunks it's important to read a multiple of 4 bytes, otherwise the Base64-decoding will fail. That can be easily proved by using a buffer size which isn't a multiple of 4. This point must also be considered if the buffer size is explicitly defined with regard to larger files.
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
byte[] deryptedBytes = c.doFinal(buffer)
you are decrypting the input to a file, then you are using the same cipher instance to decrypt the the last read chunk (again) into a separate array not to the file
quick fix:
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
output.write(c.doFinal()); // write the padded block
if you want to create and print a decrypted String, you need to create a new Cipher instance (or maybe it will be enough to reinitialize the instance, I am not sure) assuming the buffer contains the whole input
c.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// assuming the buffer contains the whole input again
byte[] deryptedBytes = c.doFinal(buffer); // decrypting the whole file again
correct approach:
IV is used to securely reuse the same encryption key for multiple encryptions. So if your key is not random, you should generate new random IV for each encryption (and pass the IV along the ciphertext, most often prepended). Otherwise the encryption is not semantically secure and you may create opening for the two pad attack. So deriving IV from the key may not be very secure.
I advice to use any MAC (authentication code) passed along the ciphertext to ensure integrity (e.g. HMAC)
you are still reading all the file input fully into memory, what would not work for REALLY LARGE files. You may initialize the buffer to an arbitrary length (a few MB?) and process the input file as chunked

Decipher data in Java from custom AES-256-CBC encryption in PHP

I've the task to migrate a php code to Java, but I'm not able to decrypt the information as these functions do. I've read many similar messages, but no solution solve the problem.
static function pkcs7_unpad($data) {
return substr($data, 0, -ord($data[strlen($data) - 1]));
}
static function decrypt($enc_name) {
$encryption_key = 'secret_key';
$iv = 'inizialization_vector';
$name = Encryptation::pkcs7_unpad(openssl_decrypt(
$enc_name,
'AES-256-CBC',
$encryption_key,
0,
$iv
));
return $name;
}
UPDATE
Update with encryption code:
static function pkcs7_pad($data, $size) {
$length = $size - strlen($data) % $size;
return $data . str_repeat(chr($length), $length);
}
static function encrypt($name) {
$encryption_key = 'secret_key';
$iv = 'inizialization_vector';
$enc_name = openssl_encrypt(
Encryptation::pkcs7_pad($name, 16), // padded data
'AES-256-CBC', // cipher and mode
$encryption_key, // secret key
0, // options (not used)
$iv // initialisation vector
);
return $enc_name;
}
The following Java code focus on the encryption-/decryption-part without a sophisticated exception handling etc. You should adapt it to your requirements. Since the PHP-code is the reference, the double padding has to be taken into account. Analogous to the PHP-methods the default and a custom PKCS7-padding are used in the Java-methods (another approach would be to use no default padding (i.e. AES/CBC/NoPadding) but a completely custom padding).
Custom PKCS7-Padding:
private static byte[] addPKCS7Padding(byte[] data, int size) {
byte pad = (byte)(size - (data.length % size));
byte[] output = new byte[data.length + pad];
System.arraycopy(data, 0, output, 0, data.length);
for (int i = data.length; i < output.length; i++)
output[i] = (byte)pad;
return output;
}
Unpadding:
private static byte[] removePKCS7Padding(byte[] data, int size) {
byte pad = data[data.length - 1];
byte[] output = new byte[data.length - pad];
System.arraycopy(data, 0, output, 0, data.length - pad);
return output;
}
A possible counterpart for the PHP encrypt-method is:
public static String encrypt(byte[] plainData) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] plainDataPKCS7Padded = addPKCS7Padding(plainData, 16);
byte[] encryptedData = cipher.doFinal(plainDataPKCS7Padded);
return Base64.getEncoder().encodeToString(encryptedData);
}
A possible counterpart for the PHP decrypt-method is:
public static byte[] decrypt(String encodedAndEncryptedData) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encryptedData = Base64.getDecoder().decode(encodedAndEncryptedData);
byte[] decryptedData = cipher.doFinal(encryptedData);
return removePKCS7Padding(decryptedData, 16);
}
Test:
public class Cryptography {
private static byte[] key = "01234567890123456789012345678901".getBytes(StandardCharsets.UTF_8);
private static byte[] iv = "0123456789012345".getBytes(StandardCharsets.UTF_8);
public static void main(String[] args) throws Exception {
String plainText = "This is a plain text which needs to be encrypted...";
String encrypedData = encrypt(plainText.getBytes(StandardCharsets.UTF_8));
System.out.println(encrypedData);
byte[] decryptedData = decrypt(encrypedData);
String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);
System.out.println(decryptedText);
}
...
}
Output:
cPF/JWAwp8G9xkUhHIMHLaS8WVfJM2UCxf2bphgOuJ6JVBmMFWAc5rwZzS/hNpAUx3+94UEEwXso2v/LkXVeXJmmfSgIaIvc9oDtGDUoQVo=
This is a plain text which needs to be encrypted...
which is equal to the output of the PHP-methods when the same plain text, key and IV are used.
EDIT:
Since AES-256-CBC is used in the PHP-methods, the key-length is 32 byte. The number denotes the key-length in bits (a 16 byte key would be needed for e.g. AES-128-CBC). The PHP-openssl-methods automatically pad too short keys with 0x0-values. In Java this doesn't happen automatically, but has to be implemented explicitly, e.g. with
private static byte[] padKey(byte[] key) {
byte[] paddedKey = new byte[32];
System.arraycopy(key, 0, paddedKey, 0, key.length);
return paddedKey;
}
For a test replace
private static byte[] key = "01234567890123456789012345678901".getBytes(StandardCharsets.UTF_8);
with
private static byte[] shortLengthKey = "012345678".getBytes(StandardCharsets.UTF_8);
private static byte[] key = padKey(shortLengthKey);
where shortLengthKey represents your 9 byte key.
The output is for both, PHP and Java:
CLPqFawQclb9PPOzcKowWBCi2gsaBJPXpl+kbGD8xYmTgL3WOtGUKpAskhXxhU4SKFZheEWiz+xkUEzLsKht3YzL9ZkSTuYtYwaZ0BjuWLM=
This is a plain text which needs to be encrypted...

3DES (DESede)- Decrypt encrypted text (done by JAVA) in C#

The encrypted text is done in JAVA (which we have no JAVA background at all)
The decryption will be in C#, and here is the code
public static string DecryptString(string Message, string Passphrase)
{
byte[] Results;
UTF8Encoding UTF8 = new UTF8Encoding();
MD5CryptoServiceProvider HashProvider = new MD5CryptoServiceProvider();
byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(Passphrase));
// byte[] TDESKey = UTF8.GetBytes(Passphrase);
TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();
TDESAlgorithm.Key = TDESKey;
// TDESAlgorithm.Mode = CipherMode.CTS;
TDESAlgorithm.Padding = PaddingMode.Zeros;
byte[] DataToDecrypt = Convert.FromBase64String(Message);
try
{
ICryptoTransform Decryptor = TDESAlgorithm.CreateDecryptor();
Results = Decryptor.TransformFinalBlock(DataToDecrypt, 0, DataToDecrypt.Length);
}
finally
{
TDESAlgorithm.Clear();
HashProvider.Clear();
}
return Encoding.UTF8.GetString(Results);
}
Encrypted Java code is
public String encryptData(String privateKey, String rawData)
{
Cipher cipher = null;
try
{
cipher = Cipher.getInstance(DESEDE_ENCRYPTION_SCHEME);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(privateKey));
byte[] plainText = rawData.getBytes(UNICODE_FORMAT);
byte[] encryptedText = cipher.doFinal(plainText);
return new String(Base64.encodeBase64(encryptedText));
}
}
However, when tried to decrypt, got the error message: BAD DATA
Where am I missing here?
You are not using MD5 in Java, so you should not be using it in your .NET for computing the hash.
Your key should have been generated using a specific encoding and same you should use in .NET.
Please note, there is some fundamental difference in java KeySpec and the Key being used for TripleDESCryptoServiceProvider. As mentioned by Microsfot https://msdn.microsoft.com/en-us/library/system.security.cryptography.tripledescryptoserviceprovider.aspx
Triple DES only supports "key lengths from 128 bits to 192 bits in increments of 64 bits"
So you need to convert your key appropriately before assigning. To do this you can use the Array.Resize method as following.
byte[] TDESKey = Encoding.UTF8.GetBytes(Passphrase);
System.Array.Resize(ref TDESKey , 192 / 8);
Hope this will help.

RSA keyblock wrapper: javax.crypto.BadPaddingException: Decryption error

In a larger application doing other things - I need to encrypt and decrypt a file. So I have been looking around and have implemented these two core functions that basically use RSA keys to wrap a random AES key that encrypts a file. The symmetric key and iv are written to the start of the file.
I'm getting an exception ("javax.crypto.BadPaddingException: Decryption error") in the decrypt function portion of below. On the unpackKeyandIV line -- the doFinal. Specifically this line is the Exception point:
Object[] keyIv = unpackKeyAndIV(xCipher.doFinal(keyBlock));
I've checked and remade the RSA key pairs. I've also checked the save/load of the keyBlock.
My gut is the problem has something to do with how I write/read the keyBlock --- or encoding perhaps?
One goal is to keep the RSA/AES instance as generic as possible so as not to need Bouncy Castle or extra Java security unlimited strength extensions.
Any thoughts on where I might be going wrong.
Thanks in advance.
[Final update: This code below is working. Error was passing in a corrupted privKey]
// RSA_INSTANCE = "RSA";
// ASSYM_CRYPTO_STR = 1024;
// SYM_CRYPTO_STR = 128;
// SYM_CRYPTO = "AES";
// AES_INSTANCE = "AES/CTR/NoPadding";
//
// File in = plain input file
// File out = encrypted output file
// Key pubKey = public Key (that wraps a random AES key)
public static void encryptFile(File in, File out, Key pubKey) throws Exception {
FileInputStream fin;
FileOutputStream fout;
int nread = 0;
byte[] inbuf = new byte[1024];
fout = new FileOutputStream(out);
fin = new FileInputStream(in);
SecureRandom random = new SecureRandom();
// symmetric wrapping
Key sKey = createKeyForAES(Config.SYM_CRYPTO_STR, random);
IvParameterSpec sIvSpec = createCtrIvForAES(0, random);
// encrypt symmetric key with RSA/pub key
Cipher xCipher = Cipher.getInstance(Config.RSA_INSTANCE);
xCipher.init(Cipher.ENCRYPT_MODE, pubKey, random);
byte[] keyBlock = xCipher.doFinal(packKeyAndIv(sKey, sIvSpec));
fout.write(keyBlock);
// encrypt data with symmetric key
Cipher sCipher = Cipher.getInstance(Config.AES_INSTANCE);
sCipher.init(Cipher.ENCRYPT_MODE, sKey, sIvSpec);
// Now read our file and encrypt it.
while((nread = fin.read(inbuf)) > 0) {
fout.write(sCipher.update(inbuf, 0, nread)); // cannot be null, by construction
}
// NB doFinal() cannot return null, but can return a zero-length array, which is benign below.
fout.write(sCipher.doFinal());
fout.flush();
fin.close();
fout.close();
}
// Decrypt File
public static void decryptFile(File in, File out, Key privKey) throws Exception {
FileInputStream fin;
FileOutputStream fout;
int nread = 0;
byte[] inbuf = new byte[1024];
fout = new FileOutputStream(out);
fin = new FileInputStream(in);
byte[] keyBlock = new byte[128];
nread = fin.read(keyBlock);
Cipher xCipher = Cipher.getInstance(Config.RSA_INSTANCE);
Cipher sCipher = Cipher.getInstance(Config.AES_INSTANCE);
// symmetric key/iv unwrapping step
xCipher.init(Cipher.DECRYPT_MODE, privKey);
Object[] keyIv = unpackKeyAndIV(xCipher.doFinal(keyBlock));
// decryption step
sCipher.init(Cipher.DECRYPT_MODE, (Key)keyIv[0], (IvParameterSpec)keyIv[1]);
while((nread = fin.read(inbuf)) >0) {
fout.write(sCipher.update(inbuf,0,nread));
}
fout.write(sCipher.doFinal());
fout.flush();
fin.close();
fout.close();
}
public static byte[] packKeyAndIv(Key key, IvParameterSpec ivSpec) throws IOException {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
bOut.write(ivSpec.getIV());
bOut.write(key.getEncoded());
return bOut.toByteArray();
}
public static Object[] unpackKeyAndIV(byte[] data) {
byte[] keyD = new byte[16];
byte[] iv = new byte[data.length - 16];
return new Object[] {
new SecretKeySpec(data, 16, data.length - 16, "AES"),
new IvParameterSpec(data, 0, 16)
};
}
Per edits and comments. Error was a corrupted privKey being passed into the decrypt function. Above code works fine.
try adding the following under your constructor -
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

javax.crypto.IllegalBlockSizeException using AES

Despite I found lot's of posts talking about the same issue I couldn't fix my issue.
I am using a c# server and a java client (in the future will be android). The curious stuff is that I am sending/receiving many json strings longer and more complex and just in the shortest and easiest fails.
This is the code for the client:
private static byte[] asegurarCapacidad(byte[] inicial, int tamano){
if(inicial.length<tamano){
return Arrays.copyOf(inicial, tamano);
}
return inicial;
}
private static String leer(Socket s){
byte[] buffer=new byte[4092];
byte[] bufferFinal = new byte[8092];
int leido=0;
int posicion=0;
String salida = null;
try {
SecretKey key = new SecretKeySpec("1212121212121212".getBytes("UTF-8"),"AES");
DataInputStream dis=new DataInputStream(s.getInputStream());
while((leido=dis.read(buffer))>0){
bufferFinal=asegurarCapacidad(bufferFinal,posicion+leido);
System.arraycopy(buffer, 0, bufferFinal, posicion, leido);
posicion+=leido;
}
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key,iv);
salida=decodificar(cipher.doFinal(bufferFinal));
} catch (Throwable e) {
System.out.println("CLIENTE: ERROR AL LEER: "+e);
e.printStackTrace();
}
return salida;
}
And this is the code of the server:
private static byte[] codificar(string mensaje)
{
return System.Text.Encoding.UTF8.GetBytes(mensaje);
}
private static void escribir(Socket s, string mensaje)
{
byte[] buffer = new byte[mensaje.Length];
buffer = codificar(mensaje);
AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
aes.KeySize = 128;
aes.BlockSize = 128;
aes.Key = bytKey;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.IV = iv;
ICryptoTransform AESEncrypt = aes.CreateEncryptor();
buffer = AESEncrypt.TransformFinalBlock(buffer, 0, buffer.Length);
s.Send(buffer);
}
Any suggestion will be welcome.
You have a buffer of 8092 bytes (bufferFinal). Suppose the input stream contains only 1000 bytes. Your code reads these 1000 bytes and calls
asegurarCapacidad(bufferFinal, posicion + leido);
where posicion is 0 and leido is 1000.
The method asegurarCapacidad() does the following:
if (inicial.length < tamano) {
return Arrays.copyOf(inicial, tamano);
}
return inicial;
where inicial.length is 8092 and tamano is 1000. So it returns the buffer unmodified.
So you end up with a buffer of 8092 bytes containing the 1000 bytes read from the input stream, and 7092 bytes set to 0. And that's what you're passing to the Cipher, instead of only passing the 1000 first bytes.
It works when you have a larger text as input, because the asegurarCapacidad() ends up returning a buffer larger than 8092 bytes containing only the encrypted bytes. But your code is inefficient (it reallocates a new array every time), and could be much simpler:
byte[] buffer = new byte[8092];
try {
InputStream in = s.getInputStream();
SecretKey key = new SecretKeySpec("1212121212121212".getBytes("UTF-8"),"AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, iv);
while ((leido = in.read(buffer)) > 0) {
cipher.update(buffer, 0, leido);
}
salida = decodificar(cipher.doFinal());

Categories

Resources